Plan — Exposition des UI par hostPort/NodePort L4
État : Actif (2026-06-23) · Fonde : ADR 0092 (Accepted). Étapes 1-4 livrées (codées + validées sans cluster) ; 5-6 = preuve banc/prod.
Applique le passage Gateway-L7 → L4 (http://<IP-nœud>:<port>, zéro DNS).
Supersede le mécanisme
d’ADR 0071.
ADR fondateurs
Section intitulée « ADR fondateurs »- 0092 — la décision (NodePort par défaut, hostPort pour briques maison, zéro DNS/LB-IPAM).
- 0071 (superseded) — le
Gateway L7 qu’on remplace ;
0043 (contrat,
ui_nodeport) ; 0003 (réseau privé).
Invariants
Section intitulée « Invariants »- NodePort AUTO, port OBSERVÉ (décision user) : on ne fige PAS le
nodePortau contrat — k8s l’attribue dans30000-32767, et le portail lit le port réel (service.spec.ports[].nodePort) via l’API à chaque chargement. Les liens sont donc toujours justes même si le port change (recréation du Service). Conséquence : pas de matrice de ports à gérer, pas de collision à valider ; le contrat ne déclare PLUSui_hostnameniui_nodeport(l’accès =exposed: true). - kubeProxyReplacement (déjà posé) sert NodePort + hostPort en eBPF — pas de LB-IPAM, pas de Gateway dans le chemin d’exposition.
- Vendored non modifiés : un Service NodePort SÉPARÉ (mêmes labels), jamais éditer un chart/bundle (CLAUDE.md).
- Banc d’abord : prouvé sur Lima avant dirqual.
1. Contrat : exposed: true (NodePort auto, port non figé)
Section intitulée « 1. Contrat : exposed: true (NodePort auto, port non figé) »- ÉDITER
contract/endpoints.example.yaml: remplacerui_hostnamepar un booléenexposed: truesur les UI à exposer en NodePort. Le port n’est PAS déclaré (k8s l’attribue, le portail l’observe). - ÉDITER
scripts/check_contract.py: valider que chaqueexposed: truea un Service NodePort correspondant (étape 3) et inversement. - Preuve SANS cluster :
check_contract+ tests.
2. Portail : Service NodePort + liens http://<IP-nœud>:<nodePort observé>
Section intitulée « 2. Portail : Service NodePort + liens http://<IP-nœud>:<nodePort observé> »- ÉDITER
platform/portal/portal.yaml: Servicetype: NodePort. Retirerplatform/portal/gateway.yaml. - ÉDITER
nestor/portal_server.py(observe_cluster) : lire lenodePortréel des Services exposés (service.spec.ports[].node_port) + l’IP d’un nœud Ready (list_node). Plus de lecture des HTTPRoutes/hostnames. - ÉDITER
nestor/portal.py:Observedgagnenode_port/node_ip; l’URL devienthttp://<node_ip>:<node_port>(au lieu dehttps://<hostname>). Le verdict MATCH/DRIFT se fonde sur présence + readiness + NodePort observé. Tests adaptés (plus deui_hostname). - Preuve banc : portail joignable, liens
http://<IP-nœud>:<nodePort>justes.
3. UI vendored : Services NodePort séparés
Section intitulée « 3. UI vendored : Services NodePort séparés »- CRÉER
platform/<brique>/nodeport.yaml(ou unplatform/exposition/) : un Service NodePort par UI (grafana, argocd, dagster, mlflow, gitea, marquez-web, k8s-dashboard), sélectionnant les labels du Service ClusterIP existant. - ÉDITER le déploiement de chaque brique pour appliquer son NodePort.
- Preuve banc : chaque UI répond sur
http://<IP-nœud>:<nodePort>.
4. Drift state.sh : allowlist NodePort/hostPort
Section intitulée « 4. Drift state.sh : allowlist NodePort/hostPort »- ÉDITER
bootstrap/state.sh: allowlister les NodePort/hostPort déclarés au contrat (au lieu de les marquer drift). Le contrat est la source de l’allowlist. - Preuve :
state.shne signale plus les NodePort du contrat comme drift.
5. Bascule Cilium : retirer LB-IPAM + Gateway
Section intitulée « 5. Bascule Cilium : retirer LB-IPAM + Gateway »- ÉDITER
bootstrap/cni.sh: ne plus poser leCiliumLoadBalancerIPPoolni le Gateway/HTTPRoute par défaut (L4 pur).GatewayClassretiré si plus aucun Gateway. - Retirer les
gateway.yaml/HTTPRoute des briques (argocd…) → remplacés par NodePort (étape 3). - Preuve banc : aucun Gateway/pool LB-IPAM ; toutes les UI en NodePort.
6. Bascule prod dirqual + preuve e2e
Section intitulée « 6. Bascule prod dirqual + preuve e2e »- dirqual (mutation opérateur) : retirer
default-pool(CiliumLoadBalancerIPPool), helm upgrade Cilium sans LB-IPAM, appliquer les NodePort. argocd passe de10.67.3.240→http://10.67.2.11:<nodePort>. - Scénario : étendre
28-ui-reachable.sh/32-portal.shpour sonderhttp://<IP-nœud>:<nodePort>(au lieu du Gateway/SNI). - Preuve : depuis le poste opérateur (qui atteint
10.67.2.x), les UI + le portail s’ouvrent directement, zéro DNS.
- Étape 1 — contrat
exposed: true(9 entrées ; port non figé) - Étape 2 — portail observe le NodePort + liens
http://<IP-nœud>:<port> - Étape 3 — UI vendored en NodePort (7 Services séparés + câblage rôles)
- Étape 4 — drift
state.shallowlist contrat +check_contractancrage NodePort - Étape 5 — bascule Cilium L4 pur (cni.sh sans Gateway/LB-IPAM + retrait CR
résiduels ; 7 gateway.yaml + cilium-expo supprimés ; access.sh/scénarios
28-32/docs refondus). PROUVÉE au banc (banc.yaml from-scratch,
2026-06-23) : cni L4 pur (0 GatewayClass/Gateway/HTTPRoute/pool LB-IPAM,
gatewayAPIoff,kubeProxyReplacement=true) ; 3 NodePort auto posés (argocd 31358, gitea 31296, grafana 31088) ; data path L4 OK (curlNodeIP:nodePort→ 200/200/302 en eBPF). dataops/mlflow/portail non montés = limite RAM banc (8 → 12 Go, fixVM_MEMORY_DEFAULT), PAS un bug L4. - Étape 6 — PROD dirqual : Phase 1 NodePort additive seule (cohabitation LB-IPAM, ne PAS rejouer cni.sh) ; rebuild from-scratch ≈ sept. 2026.