Aller au contenu

Architecture

La fiche client de la page précédente porte les valeurs. Cette page montre où elles s'insèrent : la vue de déploiement cible, les couches qui la composent et leurs dépendances, la résolution de noms selon l'origine de la requête, et la frontière entre ce qu'un script règle et ce qu'un humain doit régler.

Vue d'ensemble

La première figure montre qui parle à qui : trois acteurs, chacun par une route distincte, et les couches internes qu'ils atteignent. L'utilisateur public traverse l'edge Cloudflare puis le tunnel jusqu'à Traefik ; l'agent interne passe par WireGuard jusqu'à la résolution DNS interne ; un tiers externe (un système douanier, dans l'exemple du dépôt) atteint directement un microservice avec une requête signée, sans passer par Traefik ni par les deux autres routes. Elle ne montre pas le trajet détaillé d'une requête ni ce qui est vérifié à chaque étape : c'est l'objet de la figure suivante. Elle ne montre pas non plus le placement physique des machines, déjà vu à la page précédente.

Qui parle à qui : acteurs, exposition, cluster, couche de donnees

Trois routes entrent dans le système, et aucune des trois ne ressemble aux deux autres : c'est la troisième, celle du tiers externe qui atteint un microservice directement, sans passer par Traefik, qui casse l'idée d'un point d'entrée unique et mérite d'être repérée en premier.

La seconde figure suit une requête publique, étape par étape, avec chaque point où elle peut s'arrêter : résolution DNS, filtrage à l'edge Cloudflare (blocage de la console d'administration, limitation de débit, cache), sortie du tunnel, vérification du host déclaré, Traefik, le portail, puis l'API .NET jusqu'à sa base. Elle place aussi, en trait pointillé, le raccourci qu'emprunte un poste du LAN ou du VPN : son résolveur renvoie directement l'adresse de Traefik, sans passer ni par l'edge ni par le tunnel. Elle ne montre pas la requête émise depuis un pod interne au cluster, couverte par le tableau de résolution de noms plus bas.

Le trajet d'une requete publique et ce qui est verifie a chaque etape

Le raccourci en pointillé est le piège de vérification le plus courant : une requête envoyée depuis le LAN ou le VPN saute les deux premières étapes de la chaîne, si bien qu'un test réussi depuis ce réseau ne prouve rien sur l'exposition publique. Vérifier depuis un réseau extérieur, jamais depuis le LAN ou le VPN.

Les six couches et leurs dépendances

L'installation cible s'organise en six couches. Chacune dépend de celle qui la précède : installer une couche avant celle dont elle dépend produit un composant qui démarre sans pouvoir fonctionner, ou qui ne démarre pas du tout. La page suivante détaille pourquoi cet ordre ne se permute pas ; celle-ci en fixe la liste et les dépendances.

# Couche Contenu Dépend de
1 Talos Bootstrap du cluster : control-plane, workers, kube-vip côté Talos rien, c'est le socle
2 Secrets Vault (secrets/vault.yaml, StatefulSet de 3 répliques dans le namespace vault), External Secrets Operator, le ClusterSecretStore Talos seulement
3 Certificats cert-manager et le certificat wildcard Secrets, car le ClusterIssuer résout son jeton Cloudflare via un ExternalSecret alimenté par Vault
4 Réseau, ingress et DNS interne Calico, kube-vip côté Kubernetes, Traefik et son TLSStore, CoreDNS Certificats, car le TLSStore ne peut servir un certificat qui n'est pas Ready
5 Couche de données Bases et files internes au cluster, accès à la couche de données externe Secrets, car les identifiants de connexion sont injectés par ESO
6 Applications et exposition Keycloak et son realm, microservices .NET, portails Next.js, tunnel Cloudflare, VPN Couche de données, Secrets, et le DNS interne pour la résolution de *.${TV_DOMAIN}

Le point qui mérite d'être détaillé est le passage de la couche 2 à la couche 3 : à première vue, les secrets et les certificats semblent se référencer l'un l'autre, puisque l'UI de Vault elle-même est exposée sous le domaine wildcard, par un Ingress posé à la couche 4. La boucle ne se ferme pourtant pas, parce que Vault n'a jamais besoin de cet Ingress pour fonctionner : son Service vault.vault.svc:8200, dans le namespace vault, est couvert par kubectl kustomize . et se résout par le DNS interne standard de Kubernetes dès que Talos et le CNI sont en place, sans dépendre du CoreDNS personnalisé de la couche 4 ni d'aucun certificat. C'est ce Service, joignable depuis le poste de l'opérateur par kubectl port-forward sans DNS public ni certificat, qui sert à désceller Vault et à y écrire le jeton Cloudflare que le ClusterIssuer de la couche 3 attend. L'Ingress de la couche 4 ne fait qu'ajouter un accès web plus confortable à une UI déjà fonctionnelle.

Résolution de noms : le split-horizon

Le même nom *.${TV_DOMAIN} ne résout pas pareil selon l'origine de la requête.

Origine Résolution Par quoi
Dans le cluster vers le service Traefik, ${TV_TRAEFIK_CLUSTERIP} CoreDNS, par une règle de réécriture
Sur le VPN ou le LAN vers ${TV_VIP} le résolveur DNS de LAN, poussé aux clients WireGuard ou délégué par le DHCP du site
Depuis internet vers l'edge Cloudflare, jamais directement vers le cluster trois enregistrements publics seulement (hub, portal, auth), via le tunnel Cloudflare

Ce qui est fixe, ce qui est paramétrable

scripts/render-config.sh substitue dix familles de valeurs dans les manifests du dépôt. Il annonce lui-même leur nombre au lancement, sur la ligne Substitutions actives. En dehors de cette liste, une opération reste soit fixe (la même chez tous les clients), soit à régler à la main, sans qu'aucune substitution de texte ne puisse le faire.

Paramétrable par render-config.sh Fixé à la main par l'opérateur
Le domaine ${TV_DOMAIN}, y compris sa forme échappée dans la règle CoreDNS La zone et le jeton API Cloudflare (Zone:DNS:Edit et Zone:Zone:Read) pour le challenge DNS-01
La plage kube-vip ${TV_VIP_RANGE} Les clés Vault qui portent l'issuer OIDC et les URLs d'API des services .NET
La VIP ${TV_VIP} Le realm Keycloak, ses Valid Redirect URIs et ses Web Origins
La ClusterIP de Traefik ${TV_TRAEFIK_CLUSTERIP} La reconstruction des images frontend pour les variables NEXT_PUBLIC_*, figées au build
L'hôte du registre applicatif ${TV_REGISTRY} La publication des onze images applicatives dans le registre du client
L'adresse du DNS de LAN ${TV_LAN_DNS_IP} L'UUID du tunnel Cloudflare, à remplacer à la main après sa création
L'adresse du concentrateur WireGuard ${TV_VPN_IP}
L'adresse de contact ACME ${TV_ACME_EMAIL} Le nom de l'interface réseau de kube-vip, vip_interface, figé à ens34 dans core/kube-vip/kube-vip-ds.yaml
Le sous-réseau des pods ${TV_POD_SUBNET}, dans la configuration du tunnel WireGuard
Le sous-réseau des services ${TV_SVC_SUBNET}, dans la même configuration

Le tag de chaque image suit une règle à part, sans variable dédiée dans la fiche client : les dix images applicatives du dépôt portent un tag immuable par build, et keycloak-tradeval porte prod épinglé par son digest, propre au registre qui a construit l'image. Une variable unique appliquée à l'ensemble fabriquerait des références qui n'existent dans aucun registre. Le tag de chaque image se fixe donc au moment où l'opérateur publie ses propres images, en même temps que leur nom.

${TV_CP_ENDPOINT} reste hors de portée de render-config.sh pour une raison différente : il ne vit pas dans un manifest à substituer, mais dans la configuration machine Talos, générée par talosctl. ${TV_POD_SUBNET} et ${TV_SVC_SUBNET} vivent dans cette même configuration, et en plus dans core/vpn/peer-officer.template.conf, où le script les substitue : les deux endroits doivent porter la même valeur, sans quoi le tunnel monte et ne route rien, sans erreur. Voir Prérequis.