Aller au contenu

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 ClusterIssuer du challenge DNS-01 résout son jeton Cloudflare via l'ExternalSecret cloudflare-dns-token, alimenté par Vault à travers le ClusterSecretStore vault-store. Émettre certs/ avant que ce chemin ne fonctionne laisse le ClusterIssuer sans jeton : le challenge DNS-01 ne se résout jamais, le certificat wildcard reste False indé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, namespace vault) est couvert par kubectl kustomize ., donc disponible dès la première commande, et son Service vault.vault.svc:8200 se 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 Ready dé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.

kubectl rollout undo deployment/<nom> -n <namespace>

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.