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.

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 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.