Stratégie¶
La page précédente fixe les six couches et leurs dépendances. Celle-ci explique pourquoi cet ordre ne se permute pas, ce qu'une commande unique applique et ce qui s'applique à la main, la place d'ArgoCD, deux paires de mécanismes faciles à confondre (sondes de vivacité et de disponibilité, HPA et metrics-server), et comment revenir en arrière après une mise à jour.
Pourquoi l'ordre des six couches ne se permute pas¶
Quatre dépendances concrètes, chacune déjà observée ou déjà vérifiée dans ce dépôt, justifient l'ordre fixé à la page précédente.
- Les secrets avant les certificats, alors que rien ne le laisse deviner. Le
ClusterIssuerdu challenge DNS-01 résout son jeton Cloudflare via l'ExternalSecretcloudflare-dns-token, alimenté par Vault à travers leClusterSecretStorevault-store. Émettrecerts/avant que ce chemin ne fonctionne laisse leClusterIssuersans jeton : le challenge DNS-01 ne se résout jamais, le certificat wildcard resteFalseindéfiniment, et rien ne dit à l'opérateur que la cause est un maillon situé une couche plus loin. L'ordre ne boucle pas pour autant : Vault (secrets/vault.yaml, namespacevault) est couvert parkubectl kustomize ., donc disponible dès la première commande, et son Servicevault.vault.svc:8200se résout par le DNS interne standard de Kubernetes, sans dépendre du CoreDNS personnalisé ni d'aucun certificat. C'est cette voie, pas un Ingress, qui sert à désceller Vault et à y écrire le jeton Cloudflare. - Le certificat avant les Ingress. Le TLSStore de Traefik référence le certificat wildcard
émis par cert-manager. Un Ingress appliqué avant que ce certificat ne soit
Readydémarre quand même, mais Traefik ne peut lui servir qu'un certificat par défaut, pas celui du domaine du client. - Les secrets avant les applications. External Secrets Operator injecte les identifiants de connexion (base applicative, SMTP, MinIO) dans les Secrets Kubernetes que consomment les pods .NET et les portails. Une application démarrée avant que ces Secrets n'existent redémarre en boucle sans message explicite côté Kubernetes, le même symptôme qu'un endpoint de données externe injoignable.
- CoreDNS avant les pods .NET. La résolution de
*.${TV_DOMAIN}par CoreDNS conditionne la résolution du nom pour les services .NET. Le piège déjà rencontré sur ce dépôt, une réponse en CNAME que glibc ne suit pas, se rejoue si un pod .NET démarre avant que CoreDNS ne soit en place. La confiance TLS, elle, ne dépend plus de rien : depuis le retrait du CA interne le 2026-09-10,*.${TV_DOMAIN}est signé par Let's Encrypt, déjà présent dans le magasin par défaut de chaque image.
Poser le jeton Cloudflare à la main ne dispense pas de peupler Vault
Il est possible de créer le Secret cloudflare-dns-token à la main, sans passer par Vault
ni ESO, pour émettre le wildcard sans attendre. Ce raccourci ne tient pas dans la durée :
dès qu'ESO démarre, l'ExternalSecret du même nom reprend la main sur ce Secret. Si le
chemin Vault correspondant n'a pas été peuplé au préalable, le jeton posé à la main
disparaît à ce moment-là, sans qu'aucun message ne le signale. L'échec ne se manifeste
alors qu'au renouvellement suivant du certificat, des semaines plus tard, loin de sa cause
réelle. C'est pourquoi l'ordre recommandé reste Vault et ESO d'abord, jamais l'inverse.
Ce que couvre une commande unique, ce qui s'applique par dossier¶
Le dépôt n'est pas entièrement kustomize, pour une raison historique : les modules apps,
ingress, externalsecrets, keycloak et mailpit ont été ajoutés après la couche de base, sans
réintégration dans le kustomize racine. kubectl kustomize . ne couvre donc que cette couche de
base : namespace, réseau, cert-manager, couche de données interne au cluster, une pile
d'observabilité, Vault, Traefik, ArgoCD. Tout le reste s'applique par dossier. L'inventaire
ci-dessous est rangé par couche, ce n'est pas un ordre d'exécution et ce n'est pas la
procédure d'installation : il nomme ce que la seule commande de base laisse de côté. Le jouer
de haut en bas échouerait, ne serait-ce que parce que kube-vip et le jeton Cloudflare doivent
précéder certs/. Les commandes exactes, leur ordre et leurs préalables vivent au
Pas-à-pas.
| Couche | Ce que le kustomize racine ne pose pas | Effet de l'omission |
|---|---|---|
| 2, secrets | secrets/eso/vault-store.yaml |
aucun ExternalSecret ne se synchronise |
| 3, certificats | core/cert-manager/cloudflare-dns-token.yaml, puis certs/ |
le ClusterIssuer n'a pas de jeton, le wildcard n'est jamais émis |
| 4, réseau et ingress | core/kube-vip/ |
aucun service LoadBalancer n'obtient d'adresse |
| 4 | core/traefik/traefik-rbac.yaml, core/traefik/traefik-ingressclass.yaml |
Traefik tourne et ne lit aucun Ingress, donc ne sert aucune route, sans erreur visible |
| 4 | core/traefik/tlsstore-default.yaml |
Traefik sert son certificat auto-signé sur tous les hosts |
| 4 | core/coredns/coredns-configmap.yaml |
*.${TV_DOMAIN} ne résout pas depuis les pods |
| 4 | les deux Ingress nommés de core/platform-access/, core/lan-dns/ |
pas d'accès aux interfaces d'exploitation, pas de résolution LAN ni VPN |
| 4 | core/metrics-server/metrics-server.yaml |
kubectl top muet, dix HPA inertes en <unknown> |
| 4 | core/monitoring/node-exporter.yaml, que l'étape 10 du pas-à-pas ajoute hors de son bloc principal |
aucune métrique d'hôte (CPU, mémoire, disque des noeuds) ne remonte à Prometheus ; le tableau de bord tradeval-ressources reste vide sur ces séries |
| 6, applications | apps/api/, apps/keycloak/, apps/web/ |
ni Keycloak, ni microservices, ni portails |
| 6 | les quatre fichiers de core/cloudflare/, dans l'ordre |
aucune exposition publique |
La ligne node-exporter est la seule du tableau que la couche de base ne posait pas et qu'aucun
manifeste hérité ne réclamait : c'est l'étape 10 du
Pas-à-pas qui l'ajoute,
avec sa commande et sa vérification, hors de son bloc principal pour que ce statut reste visible.
Ne pas le confondre avec metrics-server : les deux servent des métriques, mais seul
metrics-server alimente kubectl top et les HPA, et seul node-exporter alimente Prometheus.
core/vpn/ ne figure pas dans ce tableau, et ce n'est pas un oubli : ce que ce répertoire
contient n'est pas appliqué par la procédure. L'accès VPN passe par une VM passerelle hors
cluster, l'option retenue par le dépôt ; wireguard-incluster.yaml est l'option B, son en-tête
porte Non appliqué, et il monte un Secret qu'aucun manifeste appliqué ne crée. Étape 13 du
Pas-à-pas.
Un opérateur qui ne connaît pas ce partage risque une omission silencieuse : appliquer
uniquement kubectl kustomize . laisse le cluster sans VIP propre au client, sans wildcard,
sans CoreDNS personnalisé, sans confiance CA, sans ClusterSecretStore donc sans aucun secret
synchronisé, sans résolution LAN ni VPN, sans HPA actifs, sans Keycloak, sans microservices ni
portails, et sans exposition publique, sans qu'aucune erreur ne le signale au moment de
l'apply.
Deux piles d'observabilité, une seule réellement branchée
Le kustomize racine déploie une pile Prometheus, Grafana, Kibana dans le namespace
observability. Elle tourne, mais rien ne s'y connecte : la pile réellement utilisée pour
exploiter l'instance (Prometheus, Grafana, Loki, Alloy) est installée séparément par Helm,
dans le namespace monitoring, hors du périmètre de kubectl kustomize .. Un opérateur qui
applique le kustomize racine obtient la première pile et n'y voit passer aucune métrique
utile, sans qu'aucun message n'explique pourquoi. La page Observabilité détaille laquelle
des deux installer et comment.
La place d'ArgoCD¶
L'installation reste pilotée par les commandes kubectl apply de cette page et de la
précédente, exécutées à la main. ArgoCD intervient après elle : installé par Helm depuis
platform/argocd/, il suit ensuite la branche main de ce dépôt pour les répertoires
applicatifs (apps/), et les mises à jour des applications passent par git. Les couches de
base (Vault, External Secrets, réseau) restent appliquées à la main. Voir platform/README.md.
Sondes de vivacité et de disponibilité, pourquoi elles sont distinctes¶
Une sonde de disponibilité (readinessProbe) retire un pod du Service tant qu'il ne répond pas,
sans le redémarrer. Une sonde de vivacité (livenessProbe) redémarre le pod si elle échoue trop
longtemps. Confondre les deux, ou pointer l'une des deux vers un chemin qui ne convient pas,
produit des symptômes très différents : un pod qui redémarre en boucle sans raison applicative
dans un cas, un pod Running mais injoignable pendant des heures dans l'autre, sans qu'aucun
tableau de bord classique ne le signale puisqu'il compte les pods Running.
C'est exactement ce second symptôme qui s'est produit sur l'instance de référence : un portail
est resté non disponible pendant plusieurs heures, sans redémarrer et sans qu'aucune erreur
n'apparaisse côté Kubernetes, parce que sa sonde de disponibilité suivait une redirection vers
une page d'authentification dont le temps de rendu dépassait le délai accordé à la sonde. La
leçon à retenir : un chemin de sonde ne doit jamais rediriger, ni s'authentifier, ni dépendre
d'un service externe. Sur les dix déploiements de ce dépôt qui exposent une route HTTP
applicative, les sondes de disponibilité et de vivacité sont déjà séparées, chacune sur un
chemin direct (/health et /health/ready côté .NET, /api/health côté portails Next.js) : un
opérateur qui ajoute un service suivra ce même schéma plutôt que de pointer une sonde vers une
page qui peut rediriger.
La seconde moitié de la règle, le délai accordé à la sonde, n'est en revanche pas tenue partout
dans le dépôt livré : la sonde de disponibilité de hub-portail ne fixe pas timeoutSeconds et
prend donc le défaut d'une seconde, la valeur même que l'incident incrimine. À corriger avant la
mise en service, détail à Troubleshooting, entrée 11.
HPA et metrics-server¶
Les dix HPA de ce dépôt, appliqués avec le reste d'apps/api/ et d'apps/web/, ne réagissent à rien tant que
metrics-server n'est pas installé : ils restent inertes, sans le signaler autrement que par
une valeur <unknown> dans kubectl get hpa. Le manifeste est
core/metrics-server/metrics-server.yaml, appliqué à la couche 4 dans le bloc de commandes
ci-dessus : il ne vit pas dans kubectl kustomize ., et un opérateur qui ne l'applique pas se
retrouve avec dix autoscalers inertes sans qu'aucun message ne l'indique. Installer
metrics-server réveille d'un coup tous les HPA existants, avec
leur configuration telle qu'elle est écrite dans le dépôt, sans période de rodage. C'est ce qui
s'est produit lors de l'installation de référence : les HPA sont montés à leur maximum en
quelques minutes, saturant le CPU du cluster et bloquant tout déploiement en cours. Le mécanisme, les
valeurs livrées et la façon de vérifier qu'elles n'ont pas dérivé sont à
Troubleshooting, entrée 10 ;
la leçon à retenir est que les valeurs de requests CPU et mémoire, et les bornes
maxReplicas, doivent être confrontées à un usage réel avant l'installation de
metrics-server, pas après.
Mise à jour d'un service et retour arrière¶
Le retour arrière d'un déploiement applicatif utilise kubectl rollout undo, comme pour tout
Deployment Kubernetes standard.
Ce mécanisme suffit pour les microservices .NET, dont la configuration est lue au démarrage du
conteneur. Il ne suffit pas pour les portails Next.js : leurs variables NEXT_PUBLIC_* sont
figées au moment du build de l'image, pas au démarrage. Si le retour arrière fait suite à un
changement de domaine, revenir à l'image précédente restaure du code qui appelle encore
l'ancien ${TV_DOMAIN}, même après le rollback. Dans ce cas précis, un retour arrière complet
exige de reconstruire l'image du portail avec les bonnes variables, pas seulement de changer la
version déployée.