Aller au contenu

Validation

Cette page est la recette de conformité qui suit l'installation. Elle ne remplace aucune vérification du Pas-à-pas : chaque étape y porte déjà la sienne, sur place, au moment où elle produit son effet. Ce que la recette ajoute, c'est une relecture globale de l'installation terminée, en une commande, par huit contrôles qui constatent des effets observables et non l'exécution d'une commande.

Chaque contrôle en échec désigne l'étape du pas-à-pas qui l'a produit. C'est la question utile quand un KO apparaît : non pas « qu'est-ce qui est cassé maintenant », mais « laquelle des étapes précédentes a échoué sans le dire ». Les renvois sont donnés contrôle par contrôle plus bas.

La commande unique

scripts/validate-install.sh --env scripts/install.env

Le script ne modifie rien, à une exception près : le contrôle 4 crée un pod temporaire dans le namespace default, décrit avec ce contrôle. Il n'écrit aucun fichier et ne touche à aucun manifeste.

Sortie Signification
ok <message> sur la sortie standard le contrôle a constaté l'effet attendu
KO <message> sur la sortie d'erreur le contrôle a constaté autre chose, ou n'a pas pu conclure
-- <message> (saute) sur la sortie standard ce point n'a pas pu être exercé ; le KO qui précède en donne la cause
VALIDATION : tous les controles passent., code de sortie 0 installation conforme au périmètre de la recette
VALIDATION : N controle(s) en echec. sur la sortie d'erreur, code de sortie 1 au moins un contrôle a échoué

Le script ne s'arrête pas au premier KO. Il déroule les huit contrôles, compte les échecs et ne sort en 1 qu'à la fin. Une seule exécution donne donc la liste complète des défauts, pas seulement le premier. Ne pas relancer la recette après chaque correction tant que la liste n'est pas traitée en entier.

Sur une installation conforme, chaque contrôle porte au moins une ligne ok : une seule pour chacun des contrôles 1 à 6, puis une ligne par host pour les contrôles 7 et 8. Ces deux listes de hosts se dérivent des manifests livrés, elles ne sont écrites ni dans le script ni dans cette page, et leur longueur suit donc les manifests sans que cette page la fige. Le contrôle le plus rapide est de vérifier que chacun des huit titres numérotés est suivi d'au moins une ligne : un contrôle qui n'affiche ni ok ni KO n'a pas tourné.

Une ligne -- n'apparaît jamais sur une installation conforme. Elle accompagne toujours un KO qui en donne la cause et nomme un point que la recette n'a pas pu exercer, cas des hosts internes quand le résolveur public est injoignable. Aucun point ne disparaît donc silencieusement de la sortie.

Pour lister les huit contrôles et leurs commandes sans rien exécuter, avant même que le cluster existe :

scripts/validate-install.sh --dry-run --env scripts/install.env

La sortie reprend les valeurs déjà substituées depuis la fiche client, ce qui permet de vérifier d'un coup d'oeil que la recette interroge bien le domaine, la VIP et le realm du client :

1. noeuds prets
   kubectl get nodes
2. certificats emis
   kubectl get certificate -A
3. Traefik sur la VIP
   kubectl get svc -n traefik-ingress
4. CoreDNS resout
   kubectl run ... -- getent hosts hub.${TV_DOMAIN}
5. ExternalSecret synchronises
   kubectl get externalsecret -A
6. issuer OIDC
   curl https://auth.${TV_DOMAIN}/realms/${TV_KC_REALM}/.well-known/openid-configuration
7. hosts publics
   curl -sSI -m 10 https://hub.${TV_DOMAIN}
8. hosts internes non publies
   dig +short <host>.${TV_DOMAIN} @1.1.1.1

Avant de lancer la recette

La recette s'exécute depuis le poste de l'opérateur, pas depuis le cluster. Elle a donc besoin de ce poste autant que du cluster.

Prérequis Contrôles concernés Ce qui se passe s'il manque
kubectl dans le PATH, kubeconfig pointé sur le cluster du client 1 à 5 KO kubectl absent du PATH, ... sur chacun des cinq contrôles
curl dans le PATH 6 et 7 KO curl absent du PATH, ...
dig dans le PATH 8 KO dig absent du PATH, impossible de verifier le cloisonnement public/interne, suivi d'une ligne -- par host interne dérivé
Le poste résout publiquement hub, portal et auth sous ${TV_DOMAIN} 7 les trois hosts remontent injoignables, alors que la plateforme peut être saine
Le poste joint un résolveur DNS public (1.1.1.1, port 53 sortant) 8 KO resolveur public injoignable, ..., suivi d'une ligne -- par host interne dérivé : ils ne sont pas testés du tout

Aucun contrôle n'est jamais sauté : un outil absent ou un réseau fermé produit un KO, pas un avertissement. C'est délibéré, une recette qui saute silencieusement la moitié de ses contrôles donnerait une conformité qu'elle n'a pas vérifiée. Les lignes -- du contrôle 8 ne font pas exception : elles suivent le KO, elles ne le remplacent pas, et elles servent à nommer un par un les hosts que ce KO a laissés non testés. La contrepartie est qu'un KO ne dit pas à lui seul si le défaut est dans la plateforme ou dans le poste : lire le message, il distingue toujours les deux.

Une fiche incomplète est refusée avant le premier contrôle

Le script dérive ses valeurs de la fiche client. Il vérifie d'abord que TV_DOMAIN, TV_KC_REALM, TV_VIP, TV_TRAEFIK_CLUSTERIP et TV_NODE_COUNT y sont présentes et non vides. Si l'une manque, il sort en 1 sur ERREUR : fiche incomplete, variable(s) absente(s) ou vide(s) : <noms>, avant d'exécuter le moindre contrôle et sans écrire de ligne VALIDATION :. Aucun contrôle ne tourne à moitié, et le message donne d'un coup toutes les variables à compléter (Paramètres). TV_NODE_COUNT est en plus refusée si elle n'est pas un entier positif, sur ERREUR : TV_NODE_COUNT=<valeur> n'est pas un nombre de noeuds valide. La vérification est la même en --dry-run.

La fiche est lue par le lecteur strict du guide, jamais exécutée. Une ligne qui n'est ni un commentaire, ni une ligne vide, ni une affectation simple fait sortir le script en 1 avec le numéro de la ligne fautive, avant tout contrôle.

Les huit contrôles

Ils sont numérotés ici comme dans le script et sortent dans cet ordre.

1. Noeuds prêts

Vérifie que chaque noeud du cluster porte la condition Ready=True, et que le cluster en compte autant que ${TV_NODE_COUNT}
Commande kubectl get nodes en jsonpath, une ligne <nom>=<statut Ready> par noeud
Succès aucune ligne ne diffère de =True et le compte vaut ${TV_NODE_COUNT}, message ok les <n> noeuds sont Ready
Échec noeuds non Ready : <nom>=False nomme les noeuds fautifs ; <n> noeud(s) Ready, <attendu> attendus par la fiche (TV_NODE_COUNT) quand tous sont prêts mais que le compte ne tombe pas juste ; aucun noeud retourne, resultat inexploitable signifie que kubectl a répondu sans lister de noeud ; kubectl injoignable, l'etat des noeuds n'a pas pu etre verifie : <sortie> reprend l'erreur brute de kubectl

Renvoi en cas d'échec : étape 1 du pas-à-pas, « Amorcer le cluster Talos ». Un noeud NotReady juste après une installation pointe le plus souvent install.disk ou les extensions du profil worker ; un noeud absent de la liste n'a jamais reçu sa configuration machine.

Le contrôle compte les noeuds, et le nombre attendu vient de la fiche, pas du script : TV_NODE_COUNT (Paramètres). L'architecture de référence en pose six, trois control-plane et trois workers, et c'est la valeur d'exemple de la fiche ; un client qui dimensionne autrement écrit son propre nombre et le contrôle passe au vert sur son cluster à lui. Un noeud de moins que ce nombre, tous Ready, produit un KO, et c'est l'effet recherché : un noeud qui n'a jamais reçu sa configuration machine ne se voit d'aucune autre façon dans cette recette.

2. Certificats émis

Vérifie qu'aucun Certificate du cluster n'est en attente ou en échec
Commande kubectl get certificate -A en jsonpath, une ligne <namespace>/<nom>=<statut Ready> par certificat
Succès aucune ligne ne diffère de =True, message ok tous les Certificate sont Ready
Échec certificat(s) non Ready : <namespace>/<nom>=False ; aucun certificat retourne, resultat inexploitable ; kubectl injoignable, l'etat du certificat n'a pas pu etre verifie : <sortie>

Le titre et le message parlent de tous les certificats parce que le contrôle porte sur tous les Certificate de tous les namespaces, pas seulement sur le wildcard. Depuis le retrait du CA interne le 2026-09-10, il n'en reste que deux, le wildcard et celui de trust-manager, mais la portée du contrôle est volontairement large : un certificat ajouté demain sera couvert sans que personne n'ait à y penser. Lire le nom que le message donne avant de conclure.

Renvoi en cas d'échec :

  • =False sur le wildcard : étape 9, « Émettre les certificats ». La cause est presque toujours en amont, à l'étape 8, « Matérialiser le jeton Cloudflare dans cert-manager » (le Secret cloudflare-dns-token n'existe pas dans cert-manager), ou à l'étape 6.4, « Écrire le secret d'amorçage » (le jeton n'a jamais été écrit dans Vault). kubectl describe certificate sur le certificat nommé détaille le challenge DNS-01 en échec.
  • aucun certificat retourne : soit certs/ n'a jamais été appliqué, soit les CRD de cert-manager sont absentes, donc la couche de base de l'étape 3, « Poser la couche de base », n'a pas abouti.

3. Traefik sur la VIP

Vérifie que le service LoadBalancer du namespace traefik-ingress porte bien ${TV_VIP} comme EXTERNAL-IP
Commande kubectl get svc -n traefik-ingress en jsonpath, une ligne <nom>=<EXTERNAL-IP> par service de type LoadBalancer
Succès une des lignes porte exactement l'adresse de la fiche, message ok Traefik EXTERNAL-IP=<VIP> sur le service <nom>
Échec aucun service LoadBalancer de traefik-ingress ne porte <VIP de la fiche>, trouve : <nom>=<adresse> ; aucune EXTERNAL-IP retournee pour Traefik, resultat inexploitable quand l'adresse est encore <pending> ; kubectl injoignable, le service Traefik n'a pas pu etre verifie : <sortie>

Renvoi en cas d'échec :

  • aucune EXTERNAL-IP : étape 5, « Publier la VIP de service : kube-vip ». La VIP demandée sort de ${TV_VIP_RANGE}, ou kube-vip n'a pas démarré.
  • aucun service LoadBalancer ... ne porte <VIP> : kube-vip a bien attribué une adresse, mais pas celle-là, et le message donne celles qu'il a trouvées. Soit la fiche a changé après l'étape 2, « Appliquer la fiche client aux manifests », sans que render-config.sh ait été rejoué, soit une autre ressource occupait déjà la VIP. Comparer avec la valeur TV_VIP de la fiche (Paramètres) avant de toucher au cluster.

La commande lit chaque service LoadBalancer du namespace sur sa propre ligne, et le contrôle compare ligne à ligne. Si le namespace en porte plusieurs, il conclut au succès dès qu'une ligne correspond à la VIP, et nomme les autres dans le message pour que leur présence reste visible. Sur une installation conforme, ce namespace n'a qu'un seul service LoadBalancer.

4. CoreDNS résout

Vérifie que depuis l'intérieur du cluster, hub.${TV_DOMAIN} se résout vers la ClusterIP de Traefik, et non vers l'adresse publique
Commande kubectl run validate-install-dns -n default --rm -i --restart=Never --image=debian:stable-slim --quiet -- getent hosts hub.${TV_DOMAIN}
Succès la première adresse renvoyée est exactement ${TV_TRAEFIK_CLUSTERIP}, message ok hub.${TV_DOMAIN} resout vers la ClusterIP Traefik
Échec hub.${TV_DOMAIN} n'est resolu par aucune adresse depuis le cluster : getent sort en 2, CoreDNS ne connait pas ce nom, c'est le défaut que le contrôle existe pour attraper ; hub.${TV_DOMAIN} resout vers <adresse>, attendu <ClusterIP de la fiche> quand la résolution aboutit sur autre chose ; aucune reponse de getent, resultat inexploitable ; echec de kubectl run, la resolution CoreDNS n'a pas pu etre verifiee : <sortie> pour tout autre code non nul, un kubectl injoignable ou un pod qui n'a pas démarré, la sortie brute distinguant les deux

C'est le seul contrôle qui écrit dans le cluster : il crée un pod nommé validate-install-dns dans le namespace default, quel que soit le namespace courant du kubeconfig, puis le supprime. Deux conséquences pratiques. Le cluster doit pouvoir tirer l'image debian:stable-slim, ce qui suppose un accès sortant au registre public ou un miroir configuré. Et si une exécution est interrompue avant la suppression, la suivante échoue sur un pod déjà existant : supprimer le reliquat avec kubectl delete pod validate-install-dns -n default avant de relancer.

L'image est une image glibc, la même que celle prescrite à l'étape 10, « Le reste de la couche réseau, ingress et DNS interne ». getent est une commande de la libc : une image qui ne l'embarque pas ferait échouer le contrôle quelle que soit la santé réelle de CoreDNS.

Renvoi en cas d'échec : étape 10 du pas-à-pas, « Le reste de la couche réseau, ingress et DNS interne ». La règle CoreDNS n'a pas été appliquée, ne respecte pas le format answer auto, ou a déjà été écrasée par Talos, cas explicitement documenté à cette étape. Une résolution qui rend l'adresse publique au lieu de la ClusterIP est le symptôme le plus courant, et il ne se voit d'aucune autre façon.

5. ExternalSecret synchronisés

Vérifie que chaque ExternalSecret du cluster a effectivement matérialisé son Secret depuis Vault
Commande kubectl get externalsecret -A en jsonpath, une ligne <namespace>/<nom>=<raison de la condition Ready>
Succès toutes les lignes valent SecretSynced, message ok tous les ExternalSecret sont SecretSynced
Échec ExternalSecret(s) non synchronises : <namespace>/<nom>=SecretSyncedError ; aucun ExternalSecret retourne, resultat inexploitable ; kubectl injoignable, l'etat des ExternalSecret n'a pas pu etre verifie : <sortie>

Le contrôle porte sur la raison de la condition, pas sur l'existence de la ressource : un ExternalSecret créé mais jamais synchronisé est un KO, ce qui est bien l'effet recherché.

Renvoi en cas d'échec, du plus probable au moins probable :

  • aucun ExternalSecret retourne : External Secrets Operator n'est pas installé, ses CRD manquent. Étape 6.1, « External Secrets Operator ».
  • un ou deux SecretSyncedError seulement, sur des noms précis : le chemin Vault correspondant est absent ou mal orthographié. kubectl describe externalsecret <nom> -n <namespace> nomme le chemin, vault kv get -mount=secrets prod/<service> confirme s'il existe. Étape 6.4, « Écrire le secret d'amorçage », pour le jeton Cloudflare ; étape 11, « Couche de données », pour les six endpoints de la couche de données et, par la question ouverte de cette même étape, pour la base de Keycloak.
  • tous les ExternalSecret en SecretSyncedError : le ClusterSecretStore n'est pas prêt, ou Vault est rescellé. kubectl get clustersecretstore vault-store doit valoir READY=True. Étape 7, « Fermer la boucle : le ClusterSecretStore », et l'étape 6.2, « Initialiser et desceller Vault », si Vault a redémarré depuis (un Vault rescellé ne se signale nulle part ailleurs dans cette recette).

La matrice des chemins Vault et des Secrets qu'ils produisent est à la page Configuration.

6. Issuer OIDC

Vérifie que le document de découverte OIDC de Keycloak annonce exactement l'issuer attendu, https://auth.${TV_DOMAIN}/realms/${TV_KC_REALM}
Commande curl -sS -m 10 https://auth.${TV_DOMAIN}/realms/${TV_KC_REALM}/.well-known/openid-configuration, puis extraction du champ issuer
Succès le champ lu est identique à l'issuer dérivé de la fiche, message ok issuer=<url>
Échec issuer=<url lue>, attendu <url dérivée> : Keycloak fige son issuer dans KC_HOSTNAME, valeur qui n'a pas ete rendue avec le domaine de la fiche ; champ issuer absent de la reponse, resultat inexploitable ; aucune reponse du document de decouverte, resultat inexploitable ; curl injoignable, l'issuer n'a pas pu etre verifie : <sortie>

Renvoi en cas d'échec :

  • issuer= différent de l'attendu : Keycloak fige son issuer dans KC_HOSTNAME, et cette valeur ne porte pas le domaine dérivé de la fiche. Deux chemins y mènent : la recette tourne avec une fiche dont le TV_DOMAIN n'est pas celui qui a été appliqué aux manifests, ou le déploiement Keycloak a été réappliqué depuis un manifeste non rendu, qui porte encore le domaine d'origine du dépôt. Étape 2 du pas-à-pas, « Appliquer la fiche client aux manifests », puis réappliquer Keycloak comme à l'étape 13, « Applications et exposition ».
  • champ issuer absent : Keycloak répond, mais pas ce document. Le realm ${TV_KC_REALM} n'a pas été créé, ou son nom diffère de la fiche. Étape 14, « Keycloak et son realm ».
  • aucune reponse ou curl injoignable : le host auth ne répond pas du tout, ce qui est le même défaut que le contrôle 7 relèvera sur ce host. Traiter le contrôle 7 d'abord.

Un ok sur ce contrôle ne couvre que le chemin public

Le contrôle appelle https://auth.${TV_DOMAIN} depuis le poste, donc à travers Traefik. Un ok dit que l'issuer annoncé sur ce chemin est celui dérivé de la fiche, rien de plus. Il ne dit pas que le chemin interne, celui qu'empruntent les services .NET pour valider les jetons, annonce la même valeur. Le défaut où les deux chemins annoncent deux issuers différents passe donc ce contrôle au vert, et se voit ailleurs : IDX10205 dans les journaux et 401 sur tous les appels d'API. Le seul test qui conclut compare les deux chemins, il est décrit à l'entrée 4 du Troubleshooting.

7. Hosts publics

Vérifie que les trois hosts publics répondent en HTTPS depuis l'extérieur, avec un certificat valide
Commande curl -sSI -m 10 sur https://hub.${TV_DOMAIN}, https://portal.${TV_DOMAIN} et https://auth.${TV_DOMAIN}, code HTTP et cible de redirection
Succès code 200, ou redirection 301, 302, 307 ou 308 dont la cible reste sous ${TV_DOMAIN} et en https, sur les trois : ok <host> HTTP 200, ou ok <host> HTTP <code>, redirection vers <cible>, sous <domaine>
Échec <host> HTTP <code>, redirection vers <cible>, hors de <domaine> quand la redirection sort du domaine ; <host> HTTP <code>, redirection vers <cible>, hote <hôte> sous <domaine> mais ecrit autrement : la comparaison exige <hôte attendu>, en minuscules et sans point final quand la cible est sous le domaine mais son hôte est écrit dans une autre casse ou avec un point final ; <host> HTTP <code>, redirection vers <cible>, sous <domaine> mais en <schéma> au lieu de https quand la cible reste sous le domaine mais retombe en clair ; <host> HTTP <code> sans cible de redirection, resultat inexploitable ; <host> HTTP <code> pour tout autre code ; <host> injoignable via curl : <message de curl> quand curl lui-même échoue (DNS, TLS ou délai de dix secondes dépassé)

Ce contrôle s'exécute depuis le poste de l'opérateur et emprunte donc le chemin public complet : DNS public, tunnel Cloudflare, Traefik, certificat wildcard, application. Il ne distingue pas ces maillons entre eux.

Renvoi en cas d'échec :

  • injoignable sur les trois hosts à la fois : l'enregistrement DNS public ou le tunnel. Étape 13 du pas-à-pas, « Applications et exposition ». Vérifier d'abord que les pods cloudflared tournent et que l'UUID du tunnel a bien été reporté dans sa configuration.
  • injoignable sur un seul host : l'enregistrement DNS de ce host, ou sa route de tunnel, manque. Même étape.
  • un code HTTP 502 ou 503 : le chemin public fonctionne, c'est l'application derrière qui ne répond pas. kubectl get pods -n web -n internal puis -n api -n auth distingue immédiatement ImagePullBackOff (étape 12, « Images et secrets de registre ») de CrashLoopBackOff (étape 11, « Couche de données », et étape 13, « Applications et exposition » : secrets et endpoints de données).
  • un code 404 sur auth alors que hub et portal répondent : Keycloak tourne mais sa route d'ingress ne le sert pas.
  • une redirection sous le domaine mais en http : la cible vient de l'application ou de sa route, pas du chemin public lui-même. Étape 13 du pas-à-pas, « Applications et exposition ».
  • hote <hôte> sous <domaine> mais ecrit autrement : la cible est bien sous ${TV_DOMAIN}, mais son hôte n'est pas écrit comme la comparaison l'attend, casse ou point final. Le contrôle refuse délibérément de normaliser : cette comparaison exacte est ce qui distingue le domaine de la fiche d'un domaine qui l'imite, et l'élargir affaiblirait le seul garde-fou du contrôle. Faire écrire l'hôte en minuscules et sans point final par l'application ou par sa route. Étape 13 du pas-à-pas, « Applications et exposition ».

Le contrôle tranche lui-même les redirections

L'étape 10, « Le reste de la couche réseau, ingress et DNS interne », accepte 200, 302 ou 307 sur une route servie ; le contrôle accepte de même 200, 301, 302, 307 et 308. Un host qui redirige légitimement, par exemple vers un chemin de connexion, ne produit donc pas de KO, à condition que la cible reste sous ${TV_DOMAIN} et en https. Le script extrait le schéma et l'hôte de l'en-tête Location : sous le domaine et en https, c'est un ok qui nomme la cible ; hors du domaine, c'est un KO de routage ; sous le domaine mais en http, c'est un KO de rétrogradation de schéma, la ligne « Vérifie » promet trois hosts qui répondent en HTTPS et un host public qui renvoie en clair ne tient pas cette promesse. Aucune ligne ok ne demande donc de vérification supplémentaire, et compter les lignes ok reste un raccourci valable.

Le script porte en plus une branche cible sans hote, pour un en-tête Location qui ne porterait pas d'hôte. C'est une garde défensive, pas un cas à attendre : curl résout les cibles relatives en absolu avant de les exposer dans %{redirect_url}, donc cette branche ne se déclenche pas. Inutile de la chercher devant un KO.

La requête est un HEAD (curl -sSI). Un service qui refuse la méthode HEAD répondrait 405 en étant sain ; aucun des trois hosts publics n'est dans ce cas sur l'instance de référence, mais c'est la première chose à écarter devant un 405.

8. Hosts internes non publiés

Vérifie qu'aucun host interne ne se résout depuis un résolveur DNS public. La liste n'est pas écrite dans le script : elle se dérive des manifests livrés, les host: des Ingress moins les hostname: que route le tunnel
Commande dig +short <host>.${TV_DOMAIN} @1.1.1.1, précédé d'une requête témoin sur ce même résolveur
Succès aucune réponse pour aucun host interne, un message ok <host> non resolu publiquement par host dérivé
Échec <host> resout publiquement en <adresse> pour chaque host exposé ; resolveur public injoignable, le cloisonnement public/interne n'a pas pu etre verifie si le témoin ne répond pas, suivi d'une ligne -- <host> non teste (saute) par host dérivé

Les hosts contrôlés ne sont pas énumérés ici, et c'est délibéré : les nommer ferait une seconde liste à tenir, qui se périmerait au premier Ingress ajouté. La liste est celle que le script dérive à chaque exécution : les host: de tous les Ingress livrés, moins les hostname: que route core/cloudflare/cloudflared-config.yaml, chaque premier label étant recomposé sous le TV_DOMAIN de la fiche. Un Ingress ajouté à la livraison entre donc dans le contrôle sans qu'il faille toucher au script ni à cette page. Pour lire la liste d'une livraison donnée, dérouler la sortie du contrôle 8 : elle nomme un host par ligne, ok ou --.

Depuis l'abandon du TLD interne, ce contrôle est le seul garde-fou automatique du cloisonnement entre ce qui est public et ce qui ne l'est pas. Il n'est pas exhaustif pour autant, sa portée est celle de sa dérivation : un host publié autrement que par un Ingress, ou sous un autre domaine que celui de la fiche, n'est vu par aucun contrôle de cette recette. Le reste ne tient qu'à la discipline d'exploitation : ne publier que trois enregistrements DNS et n'ouvrir que les trois routes de tunnel correspondantes.

Renvoi en cas d'échec :

  • un host qui se résout publiquement : un enregistrement DNS de trop a été publié, ou une route de tunnel de trop a été ouverte. Étape 13 du pas-à-pas, encart « Trois hosts publics, pas un de plus ». Retirer l'enregistrement dans la zone Cloudflare et la route dans la configuration du tunnel, puis relancer la recette. Tant que ce KO subsiste, une interface d'exploitation (Vault, Grafana, Longhorn) est atteignable depuis internet.
  • resolveur public injoignable : le poste de l'opérateur ne joint pas 1.1.1.1, souvent parce que le réseau du client filtre le DNS sortant. Ce n'est pas un défaut de l'installation, mais aucun host interne n'est alors testé : le contrôle s'arrête sur le témoin et nomme les hosts un par un en --, pour qu'aucun ne disparaisse de la sortie. Rejouer la recette depuis un réseau qui joint un résolveur public avant de prononcer la conformité, sans quoi le seul garde-fou du cloisonnement n'a pas été exercé. dig absent du PATH produit le même effet, et les mêmes lignes --.

Ce que la recette ne prouve pas

Huit contrôles verts signifient que la plateforme est installée conformément à ce que le guide décrit. Ils ne signifient pas qu'elle est prête à porter la production. Restent hors du périmètre de cette recette :

  • La tenue en charge. Aucun contrôle n'envoie plus d'une requête par host. Les scénarios k6 versionnés dans loadtest/ sont le moyen prévu pour cela, et ils s'exécutent séparément.
  • Les parcours fonctionnels applicatifs. Un HTTP 200 sur hub prouve qu'une page est servie, pas qu'une déclaration se crée, qu'un évènement circule sur le bus ou qu'un média s'affiche. Ces parcours se recettent avec le client, à partir des cas d'usage métier.
  • La restauration des sauvegardes. Ni les sauvegardes ni leur restauration ne sont exercées ici ; elles relèvent de la page Exploitation.
  • La supervision. Aucun contrôle ne regarde la stack monitoring, ni ses tableaux de bord, ni la collecte des journaux : voir Observabilité, en gardant à l'esprit que c'est la stack monitoring qui compte, pas la stack observability héritée.
  • L'état des pods applicatifs pris un par un. Les sept APIs et les portails internes ne sont atteints par aucun contrôle. Un service en CrashLoopBackOff qui n'est pas sur le chemin des trois hosts publics passe la recette sans être vu. Compléter par kubectl get pods sur les quatre zones avant de prononcer la conformité.
  • L'accès interne par VPN. Le contrôle 8 vérifie que les hosts internes ne sont pas publics, jamais qu'ils sont joignables par les personnes qui doivent y accéder. Le tester à la main depuis un poste raccordé au VPN.
  • L'état du sceau de Vault. Un Vault rescellé après l'installation ne se voit ici qu'indirectement, par le contrôle 5, et seulement si un ExternalSecret resynchronise entre temps.

Tenir la page et le script alignés

Cette page décrit scripts/validate-install.sh contrôle par contrôle, avec ses messages exacts. Les deux se modifient ensemble, dans le même commit : une page qui décrit un contrôle que le script n'exécute plus est pire qu'une page absente, parce que l'opérateur la croit. Après toute modification du script, comparer les deux avec :

scripts/validate-install.sh --dry-run --env scripts/install.env