Trafic sortant dans Kubernetes et proxy : env du pod, sidecar, passerelle egress et rôle de NO_PROXY
Sommaire de l'article
- Introduction : pourquoi une config « comme sur un serveur classique » ne marche pas dans un cluster
- Les bases : comment le trafic sortant d'un pod est physiquement structuré
- Trois niveaux où poser un proxy : conteneur, sidecar, passerelle egress
- Variables d'environnement dans le manifeste : http_proxy, https_proxy, no_proxy et pourquoi le cluster casse sans un no_proxy correct
- Ce qui ne récupère pas ces variables : kubelet, images avec config propre, sdk
- Secrets : les identifiants du proxy via secret, pas dans le manifeste
- L'approche sidecar : quand elle se justifie et ce qu'elle apporte au-delà du proxy
- La passerelle egress du cluster : point de sortie unique
- Diagnostic dans le cluster : vérifier l'ip sortante d'un pod et où chercher les pannes
- Erreurs classiques
- Outils et ressources
- Cas et résultats
- Checklist de déploiement
- Faq
- Conclusion
Introduction : pourquoi une config « comme sur un serveur classique » ne marche pas dans un cluster
Sur une bonne vieille VM, configurer un proxy prend cinq minutes. Tu déclares HTTP_PROXY dans le profil, tu ajoutes deux ou trois adresses en exclusion, tu redémarres le service et tu vas prendre un café. Dans Kubernetes, le même scénario finit tout autrement : la moitié des services se mettent à répondre 502, le health-check fait tomber les pods en boucle, et le service de paiement se plaint de ne plus joindre le pod d'à côté. Et pendant ce temps, les requêtes externes pour lesquelles tout a été mis en place fonctionnent très bien.
Pourquoi ? Parce que dans un cluster, un même processus vit dans deux mondes réseau simultanément. Le premier, c'est l'internet externe : API de partenaires, marketplaces, sources de données pour le scraping, services publics. Le second, c'est le réseau interne du cluster : les autres services via ClusterIP, le serveur d'API Kubernetes, le DNS, les bases de données, les files de messages, les métriques. Sur un serveur classique, ce second monde est presque vide, donc une variable d'environnement proxy globale ne casse rien. Dans un cluster, ce second monde est énorme, et n'importe quel proxy qui reçoit une requête vers une adresse du type payments.default.svc.cluster.local ne saura tout simplement pas la résoudre. Le proxy est à l'extérieur et ne connaît rien de ton DNS interne.
Ajoutons à ça les particularités de Kubernetes lui-même. Les variables d'environnement d'un pod n'affectent ni le kubelet ni le runtime de conteneurs. Les images apportent souvent leur propre configuration, qui ignore l'environnement. Les SDK de chaque langage ont leur propre interprétation de ce qu'est une exclusion de proxy. Et tout ça sur fond d'une réalité : l'IP sortante d'un pod est totalement invisible pour le monde extérieur. Ce qui sort, c'est l'adresse du nœud ou de la passerelle NAT du cloud.
Dans ce guide, on va décortiquer le trafic sortant Kubernetes « par couches » : comment un paquet quitte physiquement un pod, où exactement on peut intercaler un proxy, pourquoi NO_PROXY dans un cluster passe du statut de formalité à celui d'élément architectural critique, comment transmettre les identifiants en sécurité, quand le sidecar se justifie et quand il est superflu, et comment diagnostiquer les pannes sans jouer aux devinettes. On ne va pas refaire le cours sur la syntaxe des variables d'environnement — ici, uniquement les spécificités du cluster. Tous les extraits de manifestes sont fonctionnels, tu peux les prendre comme base.
Les bases : comment le trafic sortant d'un pod est physiquement structuré
Avant de poser un proxy, autant comprendre par quel chemin passe un paquet sans lui. Sinon, le diagnostic se transforme en devinettes.
Du conteneur au nœud
Chaque pod reçoit sa propre IP dans le pod CIDR, par exemple 10.244.3.17. À l'intérieur du pod, tous les conteneurs partagent le même espace de noms réseau : une seule IP, un seul jeu d'interfaces, un localhost commun. C'est un fait clé pour l'approche sidecar, sur laquelle on reviendra. Un paquet sortant du conteneur passe par l'interface virtuelle du pod vers le nœud, où le plugin CNI s'en occupe. Ensuite, deux scénarios.
- Destinataire interne au cluster. Si l'application s'adresse au ClusterIP d'un service (par exemple 10.96.14.2), kube-proxy ou le datapath eBPF du CNI remplace l'adresse de destination par l'IP d'un pod backend précis. Le trafic part vers le bon nœud via l'overlay ou directement en L3 et ne quitte jamais le cluster.
- Destinataire externe. Si l'adresse n'appartient ni au pod CIDR ni au service CIDR, le paquet part par la route par défaut du nœud. À la sortie du nœud, un SNAT s'applique presque toujours : l'adresse source 10.244.3.17 est remplacée par l'IP du nœud. Dans les clouds, une passerelle NAT peut s'ajouter par-dessus, et le monde extérieur voit alors son adresse à elle.
Première conclusion pratique : un service externe ne voit jamais l'IP du pod. Il voit l'IP du nœud ou le NAT du cloud. Si tu as de l'autoscaling de nœuds, les IP sortantes changent avec eux. C'est exactement pour ça que l'autorisation sur un proxy par adresse IP, sans nœud egress dédié, marche mal dans un cluster — on y revient plus bas.
Le DNS à l'intérieur du cluster
Les pods utilisent le DNS du cluster (généralement CoreDNS). Dans le resolv.conf du pod sont déclarés les domaines de recherche : namespace.svc.cluster.local, svc.cluster.local, cluster.local. Grâce à ça, le nom payments se résout en payments.default.svc.cluster.local. Un proxy externe n'a aucune idée de ce schéma. Il reçoit une requête vers l'hôte payments, essaie de le résoudre via le DNS public et renvoie une erreur. C'est la raison fondamentale pour laquelle NO_PROXY dans un cluster est plus important que sur n'importe quel serveur isolé.
Ce que « mettre un proxy » veut vraiment dire dans un cluster
Sous cette formule, les ingénieurs entendent trois choses différentes, et les confondre est dangereux :
- Faire passer une application précise par un serveur proxy vers l'extérieur (par exemple via des adresses dédiées Proxeon), sans toucher aux autres services.
- Garantir une IP sortante stable et prévisible pour tout un cluster ou une partie — c'est un enjeu de réseau d'entreprise, de conformité, d'audit.
- Centraliser la gestion du trafic externe : logs, limites, point de sortie unique.
La première tâche se résout au niveau du pod. Les deux autres, au niveau du cluster. Mélanger les niveaux, c'est la source de la majorité des problèmes dont on va parler.
Trois niveaux où poser un proxy : conteneur, sidecar, passerelle egress
Imagine ton cluster comme un immeuble de bureaux. Tu peux donner un proxy à chaque employé individuellement (conteneur), asseoir un assistant à côté de chaque poste de travail (sidecar), ou installer un seul poste de garde à la sortie de l'immeuble (passerelle egress). Chaque schéma a son coût.
Niveau 1 : le conteneur lui-même
L'application reçoit l'adresse du proxy via des variables d'environnement, un fichier de config ou des flags au démarrage, et route elle-même son trafic par là. C'est la méthode la plus simple et la plus répandue.
Avantages :
- Zéro infra en plus : les changements se font uniquement dans le manifeste du Deployment.
- Granularité : un service a besoin du proxy, son voisin non.
- Le proxy « comprend » le protocole : un proxy HTTP voit l'hôte de destination, peut loguer, appliquer des règles.
Inconvénients :
- Ça ne marche que si le code ou la bibliothèque respecte la config. Java, certains clients Node.js, quelques implémentations gRPC demandent une configuration séparée.
- Dépendance totale à un NO_PROXY correct : une erreur et la communication intra-cluster est cassée.
- Les identifiants du proxy se retrouvent dans l'environnement du processus, accessibles à quiconque peut faire un exec dans le conteneur.
- La rotation du proxy ou un changement d'adresse impose de recréer les pods.
Niveau 2 : le conteneur sidecar
À côté de l'application, dans le même pod, on lance un second conteneur — un proxy de forward local. L'application envoie son trafic vers localhost:3128 (ou il est intercepté par des règles iptables), et le sidecar décide ensuite où et comment l'envoyer : quel upstream, avec quelle authentification, avec quel timeout.
Avantages :
- L'application ne connaît pas les identifiants externes : ils vivent uniquement dans le sidecar.
- Point unique de logique : rotation, retries, choix d'un pool d'adresses, métriques — sans toucher au code de l'application.
- Le mode transparent via iptables fonctionne même avec les bibliothèques qui ignorent HTTP_PROXY.
- Mettre à jour la logique de proxification, c'est mettre à jour une seule image sidecar.
Inconvénients :
- Un conteneur en plus dans chaque pod : mémoire, CPU, un objet de plus à monitorer.
- L'interception transparente exige NET_ADMIN dans un init-conteneur, ce qui n'est pas toujours autorisé par les politiques de sécurité.
- Plus dur à débugger : deux endroits où ça peut casser.
Niveau 3 : la passerelle egress du cluster
Un ensemble dédié de pods (ou de nœuds) devient l'unique point de sortie. Les autres pods passent soit explicitement par un Service interne de la passerelle, soit y sont forcés par les politiques réseau et le routage. La passerelle, elle, sort directement ou via un proxy externe.
Avantages :
- Une IP sortante unique et stable (ou un petit pool) pour tout le cluster. Idéal pour l'autorisation par IP côté fournisseur de proxy et pour les whitelists partenaires.
- Logs, limites et audit centralisés.
- Les applications ne stockent aucun identifiant : elles ne voient qu'une adresse interne de passerelle.
- La rotation des adresses externes se fait à un seul endroit.
Inconvénients :
- Point unique de défaillance : la passerelle doit être redondée (plusieurs réplicas, PodDisruptionBudget, anti-affinity).
- Un saut réseau supplémentaire et un goulot de bande passante potentiel.
- Nécessite des NetworkPolicy bien pensées, sinon il reste une « fuite » en sortie directe à côté de la passerelle.
- Granularité plus compliquée : si des équipes différentes ont besoin d'adresses externes différentes, il faudra plusieurs passerelles.
Comment choisir
Règle pratique qu'on utilise en conception :
- Une ou deux applis, code maîtrisé, clients HTTP standards — niveau conteneur.
- Beaucoup de services aux stacks variés, exigences de rotation et de retries, refus de distribuer des identifiants — sidecar.
- Exigences de conformité, autorisation par IP, audit de tout le trafic externe, des dizaines d'équipes — passerelle egress, souvent combinée à un sidecar pour la logique fine.
Remarque : les niveaux ne s'excluent pas. Un schéma mature typique, c'est une passerelle egress comme frontière obligatoire, plus des variables d'environnement ou un sidecar là où il faut de la spécificité.
Variables d'environnement dans le manifeste : HTTP_PROXY, HTTPS_PROXY, NO_PROXY et pourquoi le cluster casse sans un NO_PROXY correct
La syntaxe des variables, tu la connais. Ce qui nous intéresse, c'est ce qui leur arrive dans un pod et pourquoi une valeur NO_PROXY standard « localhost,127.0.0.1 » est ici catastrophiquement insuffisante.
Anatomie d'une panne
Déroulons la chaîne. Le service orders appelle payments à l'adresse http://payments:8080/charge. HTTP_PROXY est défini dans l'environnement. Le client HTTP regarde l'hôte de destination, ne le trouve pas dans NO_PROXY et, au lieu d'ouvrir une connexion TCP vers payments, ouvre une connexion au proxy et lui envoie la requête GET http://payments:8080/charge. Le proxy essaie de résoudre le nom payments via son propre resolver. Le nom n'existe pas. Le proxy renvoie un 502 ou un 503. Ton service reçoit une erreur du proxy, mais dans les logs, elle apparaît comme une erreur de payments. L'équipe part réparer payments, qui est en parfaite santé.
Il y a une deuxième variante, plus sournoise. Si le service s'adresse directement au ClusterIP sous forme d'adresse du type 10.96.14.2, le proxy reçoit une requête vers une adresse privée. Certains proxys rejettent ces requêtes d'emblée, d'autres essaient de se connecter à 10.96.14.2 dans leur propre réseau. Au pire, un autre hôte y existe avec cette adresse.
Troisième variante, côté Kubernetes cette fois. Les bibliothèques clientes qui parlent au serveur d'API (opérateurs, contrôleurs, applis avec leader election) utilisent l'adresse kubernetes.default.svc ou l'IP de la variable KUBERNETES_SERVICE_HOST. Sans exclusion, elles passent aussi par le proxy et perdent le contact avec le control plane.
Ce qui doit obligatoirement figurer dans NO_PROXY pour un pod
L'ensemble minimal qu'on recommande comme point de départ. Remplace les CIDR par les tiens — tu les trouveras dans la config du cluster ou dans les annotations des services :
- localhost, 127.0.0.1, ::1 — adresses locales, y compris la communication avec le sidecar.
- .svc, .svc.cluster.local, .cluster.local — tous les noms de services. Le point en tête signifie « n'importe quel sous-domaine ».
- kubernetes.default.svc, kubernetes.default.svc.cluster.local — explicitement, pour les clients de l'API.
- Service CIDR, par exemple 10.96.0.0/12 — accès directs au ClusterIP.
- Pod CIDR, par exemple 10.244.0.0/16 — services headless et communications pod-à-pod.
- Node CIDR — si l'application tape sur des ports du nœud (métriques, services hostNetwork).
- 169.254.169.254 — métadonnées cloud, si utilisées.
- Domaines internes de l'entreprise, par exemple .corp.local, s'il existe une route directe depuis le cluster.
- Adresses des bases et files internes, si elles sont hors du cluster mais dans le réseau privé.
Le piège que tout le monde ne connaît pas : tous les clients ne supportent pas le CIDR
C'est là que commence la vraie spécificité. Le format de NO_PROXY n'est pas standardisé. Chaque bibliothèque le parse à sa façon :
- Go (net/http) comprend la notation CIDR, les suffixes avec point en tête et les hôtes individuels. C'est le meilleur cas, et la plupart des outils Kubernetes sont écrits en Go.
- curl ne supporte le CIDR qu'à partir de la version 7.86. Dans les vieux images basées sur des distributions anciennes, l'entrée 10.96.0.0/12 sera simplement ignorée.
- Python (urllib, requests) ne comprend pas le CIDR du tout. La comparaison se fait par suffixe de chaîne. L'entrée 10.96.0.0/12 ne marchera jamais ; et l'entrée 10.96. ne fonctionnera comme correspondance de préfixe que pour les hôtes se terminant par cette chaîne, donc ça n'aidera pas non plus.
- Node.js : le client HTTP intégré ignore complètement les variables d'environnement. Il faut un ProxyAgent dans undici ou un agent tiers, et c'est lui qui décide comment traiter NO_PROXY.
- Java utilise sa propre variable http.nonProxyHosts avec une syntaxe wildcards du type *.svc.cluster.local et un séparateur barre verticale.
Conclusion pratique : si tu as un zoo de langages dans le cluster, le seul moyen fiable d'exclure le trafic interne par CIDR, c'est de ne pas compter sur le NO_PROXY du client, mais de le gérer au niveau du sidecar ou de la passerelle, où le parser est sous ton contrôle. Pour les noms DNS, les suffixes avec point marchent presque partout, donc dans les applis, mieux vaut appeler les services par nom que par ClusterIP.
La casse des variables
Encore un point qui coûte régulièrement des heures de debug. curl ne lit http_proxy qu'en minuscules : HTTP_PROXY en majuscules est ignoré délibérément, pour une raison historique de vulnérabilité CGI. En revanche, HTTPS_PROXY et NO_PROXY, curl les accepte dans les deux casses. Go, Python et .NET lisent les deux. Donc dans le manifeste, on déclare toujours les deux formes — ça coûte rien et ça élimine toute une classe de problèmes.
Extrait fonctionnel : ConfigMap avec NO_PROXY et Deployment
Sors NO_PROXY dans un ConfigMap au niveau du namespace. Comme ça, tu modifies la liste à un endroit au lieu de vingt Deployments :
apiVersion: v1
kind: ConfigMap
metadata:
name: egress-proxy-common
namespace: crawlers
data:
NO_PROXY: "localhost,127.0.0.1,::1,.svc,.svc.cluster.local,.cluster.local,kubernetes.default.svc,10.96.0.0/12,10.244.0.0/16,192.168.10.0/24,169.254.169.254,.corp.local"
no_proxy: "localhost,127.0.0.1,::1,.svc,.svc.cluster.local,.cluster.local,kubernetes.default.svc,10.96.0.0/12,10.244.0.0/16,192.168.10.0/24,169.254.169.254,.corp.local"Et le Deployment qui l'utilise. L'adresse du proxy ici est encore sans identifiants — on verra comment les stocker dans une section dédiée :
apiVersion: apps/v1
kind: Deployment
metadata:
name: price-collector
namespace: crawlers
spec:
replicas: 3
selector:
matchLabels:
app: price-collector
template:
metadata:
labels:
app: price-collector
spec:
containers:
- name: app
image: registry.internal/price-collector:2.7.0
envFrom:
- configMapRef:
name: egress-proxy-common
env:
- name: HTTP_PROXY
value: "http://proxy-host.example:8080"
- name: http_proxy
value: "http://proxy-host.example:8080"
- name: HTTPS_PROXY
value: "http://proxy-host.example:8080"
- name: https_proxy
value: "http://proxy-host.example:8080"
readinessProbe:
httpGet:
path: /healthz
port: 8080Fais attention au schéma dans HTTPS_PROXY : la plupart du temps, c'est http://, parce que le client se connecte au proxy lui-même en TCP classique, puis établit un tunnel CONNECT à l'intérieur pour les requêtes HTTPS. La valeur https:// signifie ici TLS jusqu'au proxy, ce que très peu de clients et de serveurs proxy supportent. Vérifie dans le panneau Proxeon quel type de connexion offre ton forfait.
Vérifie que la probe ne passe pas par le proxy
Les probes liveness et readiness sont exécutées par le kubelet, pas par l'application, donc elles ne voient pas les variables d'environnement et le proxy ne les affecte pas. Mais si ton application, à l'intérieur du handler /healthz, appelle elle-même des dépendances internes, ces appels tomberont sous HTTP_PROXY. C'est exactement le symptôme classique « après avoir ajouté le proxy, les pods ne passent plus la readiness ».
Ce qui ne récupère PAS ces variables : kubelet, images avec config propre, SDK
Les variables d'environnement d'un pod vivent à l'intérieur du processus applicatif. Tout ce qui est en dehors ou qui ignore délibérément l'environnement demande une configuration séparée. Voyons trois groupes.
Kubelet et runtime de conteneurs
La question la plus fréquente lors de la migration d'un cluster derrière un proxy : « pourquoi les images ne se tirent pas, on a pourtant mis HTTP_PROXY dans le Deployment ». Réponse : ce n'est pas le pod qui télécharge les images, mais le runtime de conteneurs (containerd ou CRI-O) sur le nœud, sur ordre du kubelet. Ce sont des services système lancés via systemd, et ils ne lisent que leur propre environnement.
La config se fait via un fichier drop-in pour l'unité systemd, sur chaque nœud. Pour containerd, ça ressemble à ça (fichier /etc/systemd/system/containerd.service.d/http-proxy.conf) :
[Service]
Environment="HTTP_PROXY=http://proxy-host.example:8080"
Environment="HTTPS_PROXY=http://proxy-host.example:8080"
Environment="NO_PROXY=localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16,192.168.10.0/24,.svc,.cluster.local,registry.internal"Ensuite — systemctl daemon-reload puis systemctl restart containerd. Dans NO_PROXY, il faut absolument que le registry interne figure, sinon le runtime ira chercher tes propres images via le proxy externe. Pour kubelet, un drop-in séparé est plus rare — uniquement s'il accède lui-même à des API cloud ou à des webhooks externes.
Dans les clusters managés, l'accès aux nœuds peut être absent, et la config du runtime passe alors par les mécanismes du fournisseur cloud ou par un DaemonSet avec un conteneur privilégié qui modifie la config sur l'hôte. La seconde option est un compromis à valider avec l'équipe sécurité.
Images avec leur propre configuration
Beaucoup d'images prêtes à l'emploi embarquent un fichier de config qui écrase ou ignore l'environnement :
- Applications JVM. Java ne lit pas HTTP_PROXY. Il faut des propriétés système. Pratique à passer via JAVA_TOOL_OPTIONS, que la JVM récupère sans toucher aux scripts de démarrage :
env:
- name: JAVA_TOOL_OPTIONS
value: "-Dhttp.proxyHost=proxy-host.example -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy-host.example -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.svc|*.svc.cluster.local|*.cluster.local|10.*|192.168.*"- Images avec leur propre config de client HTTP (certains agents de monitoring, connecteurs, outils ETL) lisent le proxy dans leur propre YAML ou TOML. Elles voient l'environnement, mais ne l'utilisent pas. Lis la doc de l'image et cherche des clés du type proxy_url.
- Images avec un resolver DNS codé en dur dans l'application, ce qui casse toute l'idée d'un NO_PROXY par nom : l'application résout elle-même le nom, obtient une IP, et compare ensuite cette IP à la liste, où tu n'as que des suffixes de domaine.
- Scripts entrypoint qui écrasent les variables. C'est plus rare, mais ça vaut le coup de vérifier : kubectl exec pod -- cat /proc/1/environ | tr l'octet nul en retour ligne te montrera l'environnement réel du processus principal.
Certains SDK et protocoles
La liste est longue, on cite le plus fréquent :
- gRPC. Beaucoup d'implémentations ne supportent HTTP_PROXY qu'en CONNECT et avec des limites ; certaines ignorent NO_PROXY. Pour du trafic gRPC interne au cluster, c'est doublement dangereux.
- Node.js. Le fetch intégré et les modules http/https ne lisent pas l'environnement. Il faut un agent explicite. La bibliothèque axios lit HTTP_PROXY via sa dépendance, mais le fetch natif non. Dans une même appli, deux clients peuvent se comporter différemment.
- SDK cloud. La plupart respectent HTTP_PROXY, mais l'accès à l'endpoint de métadonnées (169.254.169.254) doit être dans les exclusions, sinon la récupération d'identifiants temporaires casse.
- WebSocket et TCP brut. Les clients de bases de données, brokers de messages, Redis fonctionnent sur leurs propres protocoles au-dessus de TCP et ne passent pas du tout par un proxy HTTP. Si ce trafic doit passer par un proxy, il faut un mode SOCKS ou une interception transparente au niveau du sidecar.
- Clients DNS maison dans les applis (résolution via une bibliothèque séparée) n'interagissent pas avec le proxy et passent directement en UDP 53. C'est normal, mais il faut s'en souvenir quand on écrit des NetworkPolicy.
Conclusion de la section : avant de déployer un proxy sur un service, fais une cartographie de ses connexions sortantes par protocole et par bibliothèque. Une demi-heure d'inventaire économise une journée de debug.
Secrets : les identifiants du proxy via Secret, pas dans le manifeste
Une adresse proxy avec login et mot de passe dans le corps du Deployment, c'est une fuite qui attend son heure. Les manifestes vivent dans git, finissent dans les logs CI, s'affichent dans kubectl get deploy -o yaml à quiconque a le droit de lire le namespace. Faisons les choses bien.
Option 1 : URL complète du proxy dans un Secret
Le schéma le plus simple : le Secret contient la chaîne toute prête avec les identifiants, l'application la reçoit comme variable d'environnement. On crée le secret hors de git (via variables CI, gestionnaire externe de secrets, ou à la main) :
kubectl -n crawlers create secret generic egress-proxy-creds --from-literal=HTTP_PROXY="http://user123:s3cretPass@proxy-host.example:8080" --from-literal=HTTPS_PROXY="http://user123:s3cretPass@proxy-host.example:8080" --from-literal=http_proxy="http://user123:s3cretPass@proxy-host.example:8080" --from-literal=https_proxy="http://user123:s3cretPass@proxy-host.example:8080"Dans le Deployment, au lieu d'env explicites — une référence au Secret et au ConfigMap avec NO_PROXY :
containers:
- name: app
image: registry.internal/price-collector:2.7.0
envFrom:
- configMapRef:
name: egress-proxy-common
- secretRef:
name: egress-proxy-credsDétail important : si le login ou le mot de passe contient des caractères spéciaux (@, :, /, %), il faut les encoder en URL. Un mot de passe avec un @ non encodé casse le parsing de l'URL, et le client tentera de se connecter au mauvais hôte. Dans le panneau Proxeon, au moment de générer les accès, c'est pratique de choisir directement un mot de passe sans caractères spéciaux ou de copier l'URL prête à l'emploi.
Option 2 : champs séparés et assemblage de l'URL dans le conteneur
Quand login et mot de passe sont stockés séparément (par exemple synchronisés depuis un coffre externe), l'URL se construit dans l'entrypoint ou dans l'appli elle-même :
env:
- name: PROXY_USER
valueFrom:
secretKeyRef:
name: egress-proxy-creds
key: username
- name: PROXY_PASS
valueFrom:
secretKeyRef:
name: egress-proxy-creds
key: password
- name: PROXY_HOST
value: "proxy-host.example:8080"Cette variante est plus pratique pour la rotation du mot de passe : on change une clé du Secret au lieu de quatre chaînes prêtes à l'emploi.
Option 3 : un fichier plutôt qu'une variable
Les variables d'environnement d'un processus se lisent dans /proc et finissent souvent dans les crash-dumps et les rapports d'erreur. Si la politique de sécurité est stricte, monte le Secret comme fichier en droits 0400 et lis-le dans l'appli ou dans le sidecar :
volumes:
- name: proxy-creds
secret:
secretName: egress-proxy-creds
defaultMode: 0400
containers:
- name: app
volumeMounts:
- name: proxy-creds
mountPath: /var/run/secrets/proxy
readOnly: trueOption 4 : se débarrasser complètement des identifiants
Si tu as une passerelle egress avec une IP sortante stable, tu peux basculer l'autorisation sur le proxy du login/mot de passe vers une whitelist d'IP. Dans ce cas, le cluster ne stocke plus rien de secret : les pods passent par un Service interne de la passerelle, la passerelle sort depuis une adresse fixe, le fournisseur laisse passer le trafic selon l'adresse. Proxeon supporte les deux modes d'authentification, et pour un cluster avec un egress dédié, on recommande généralement le mode IP, plus sûr et plus simple à exploiter.
Hygiène autour du Secret
- Active le chiffrement des Secrets at rest dans etcd — il est désactivé par défaut dans la plupart des installations autonomes.
- Limite le RBAC : le droit de lire les secrets d'un namespace ne doit être accordé qu'aux service accounts qui en ont vraiment besoin. Une appli qui reçoit le secret via envFrom ne doit pas avoir à lire l'objet Secret via l'API.
- Ne logue pas l'environnement. Vérifie que le framework n'affiche pas les env au démarrage en mode debug.
- Conviens d'une politique de rotation. Changer le mot de passe du proxy doit être une opération de routine : on met à jour le Secret, on fait un rollout restart, on désactive l'ancien mot de passe.
- Garde en tête qu'avec envFrom, mettre à jour le Secret ne change pas l'environnement des pods déjà démarrés. Il faut un rollout restart. Avec un fichier monté, le kubelet met à jour le contenu en une ou deux minutes, mais l'appli doit savoir relire le fichier.
L'approche sidecar : quand elle se justifie et ce qu'elle apporte au-delà du proxy
Un sidecar dans le contexte egress, c'est un conteneur proxy local dans le pod, qui reçoit le trafic de l'application et l'envoie plus loin via un proxy externe. Il transforme la question « est-ce que la bibliothèque respecte HTTP_PROXY » en « est-ce que le sidecar sait intercepter le trafic », et à la seconde question, il est plus simple de répondre.
Quand le sidecar se justifie
- L'application est écrite sur un stack où la gestion du proxy est pénible (JVM avec une dizaine de clients différents, Node.js avec un mélange de fetch et d'axios, du legacy en C++).
- Il faut de la rotation d'adresses selon un planning ou sur erreur, sans toucher au code : le sidecar tient un pool de plusieurs proxies Proxeon et bascule tout seul.
- Tu veux sortir les identifiants de l'application : le sidecar est le seul à connaître le mot de passe, et l'appli tape sur localhost sans authentification.
- Il faut une politique unifiée de retries, de timeouts et de limites de requêtes sortantes pour le service.
- Il faut du trafic qui n'est pas que du HTTP : mode SOCKS ou interception TCP transparente.
Quand le sidecar est superflu
- Un service unique en Go ou Python avec un seul client HTTP. Les variables d'environnement suffiront.
- Limites de ressources strictes : des milliers de petits pods, où les 30-50 Mo en plus du sidecar se multiplient en tonnes de mémoire.
- Tu as déjà une passerelle egress avec la logique voulue, et la dupliquer dans chaque pod n'a pas de sens.
Les sidecars natifs
Depuis Kubernetes 1.29 est apparu, et à partir de 1.33 est stabilisé, le mécanisme de sidecar natif : un init-conteneur avec restartPolicy: Always. Un tel conteneur démarre avant le principal, vit toute la durée de vie du pod et se termine proprement après le principal. Ça résout une vieille douleur : avant, l'application pouvait démarrer avant le proxy et recevoir connection refused sur les premières requêtes, et un Job avec sidecar ne se terminait jamais, parce que le sidecar continuait de tourner.
Extrait fonctionnel : pod avec sidecar proxy
Ici, le sidecar est un petit proxy de forward (image fictive egress-sidecar), qui écoute sur 127.0.0.1:3128, lit les identifiants upstream dans un Secret et sait quelles adresses passer directement :
apiVersion: apps/v1
kind: Deployment
metadata:
name: feed-importer
namespace: integrations
spec:
replicas: 2
selector:
matchLabels:
app: feed-importer
template:
metadata:
labels:
app: feed-importer
spec:
initContainers:
- name: egress-sidecar
image: registry.internal/egress-sidecar:1.3.0
restartPolicy: Always
env:
- name: LISTEN_ADDR
value: "127.0.0.1:3128"
- name: DIRECT_CIDRS
value: "10.96.0.0/12,10.244.0.0/16,192.168.10.0/24"
- name: DIRECT_SUFFIXES
value: ".svc,.cluster.local,.corp.local"
envFrom:
- secretRef:
name: egress-upstream-creds
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
memory: 96Mi
readinessProbe:
tcpSocket:
port: 3128
containers:
- name: app
image: registry.internal/feed-importer:4.1.0
envFrom:
- configMapRef:
name: egress-proxy-common
env:
- name: HTTP_PROXY
value: "http://127.0.0.1:3128"
- name: HTTPS_PROXY
value: "http://127.0.0.1:3128"
- name: http_proxy
value: "http://127.0.0.1:3128"
- name: https_proxy
value: "http://127.0.0.1:3128"Attention : le NO_PROXY de l'application reste nécessaire. Le sidecar sait passer le trafic interne directement, mais pourquoi le faire transiter par un saut supplémentaire sur localhost ? De plus, le parsing des CIDR dans le sidecar est sous ton contrôle, alors que dans l'application, il ne l'est pas. La double protection est ici pertinente.
Mode transparent via iptables
Si l'application ignore complètement l'environnement, on ajoute un init-conteneur qui redirige tout le trafic TCP sortant du pod, sauf les réseaux internes et le trafic du sidecar lui-même, vers le port du sidecar. Le sidecar doit alors fonctionner en mode proxy transparent et restaurer l'adresse de destination originale. L'init-conteneur a besoin de la capability NET_ADMIN, et le sidecar doit tourner sous un UID séparé pour que son propre trafic ne boucle pas :
initContainers:
- name: egress-redirect
image: registry.internal/egress-init:1.0.0
securityContext:
capabilities:
add: ["NET_ADMIN"]
command: ["sh", "-c"]
args:
- |
iptables -t nat -N EGRESS_REDIRECT
iptables -t nat -A EGRESS_REDIRECT -d 10.96.0.0/12 -j RETURN
iptables -t nat -A EGRESS_REDIRECT -d 10.244.0.0/16 -j RETURN
iptables -t nat -A EGRESS_REDIRECT -d 127.0.0.0/8 -j RETURN
iptables -t nat -A EGRESS_REDIRECT -m owner --uid-owner 1337 -j RETURN
iptables -t nat -A EGRESS_REDIRECT -p tcp -j REDIRECT --to-ports 15001
iptables -t nat -A OUTPUT -p tcp -j EGRESS_REDIRECTIci, 1337 est l'UID sous lequel tourne le sidecar, et 15001 est son port transparent. C'est exactement le principe que les service mesh utilisent pour intercepter le trafic. Si un mesh est déjà déployé dans le cluster, ses capacités egress suffiront probablement et un sidecar séparé devient inutile.
Ce que le sidecar apporte au-delà du proxy
- Métriques du trafic sortant par hôte de destination, code de réponse, temps : l'application ne les connaît pas, le sidecar voit tout.
- Circuit breaker sur les API externes : en cas de série d'erreurs, le sidecar cesse de marteler le partenaire et renvoie un refus rapide.
- Pool d'adresses et rotation : liste de proxies Proxeon dans la config, bascule en round-robin ou sur erreur 407/403.
- Cache DNS local pour les noms externes, réduisant la charge sur CoreDNS.
- Limitation de débit par destination, pour respecter les limites des API partenaires et ne pas se faire throttler.
La passerelle egress du cluster : point de sortie unique
Une passerelle egress répond à une autre échelle : pas « comment ce service passe par un proxy », mais « par quelles adresses le cluster sort-il dans le monde ». Voyons une réalisation pratique sans dépendance à un CNI précis.
Architecture
- On définit un groupe de nœuds avec des IP externes fixes (dans le cloud, avec une adresse publique réservée ou derrière une passerelle NAT dédiée). Les nœuds reçoivent le label node-role: egress et un taint, pour que des pods étrangers ne s'y installent pas.
- Sur ces nœuds, on déploie un Deployment de proxy de forward avec plusieurs réplicas et de l'anti-affinity. Devant, un Service de type ClusterIP.
- Les pods applicatifs reçoivent HTTP_PROXY pointant vers ce Service. Leur NO_PROXY contient les réseaux internes.
- Une NetworkPolicy interdit aux pods applicatifs tout egress hors cluster, sauf le DNS et le trafic vers le Service de la passerelle.
- La passerelle sort elle-même soit directement (son IP est alors l'adresse sortante du cluster), soit via un proxy externe Proxeon avec autorisation par IP de la passerelle.
Extrait fonctionnel : Deployment de la passerelle et NetworkPolicy
apiVersion: apps/v1
kind: Deployment
metadata:
name: egress-gateway
namespace: egress
spec:
replicas: 3
selector:
matchLabels:
app: egress-gateway
template:
metadata:
labels:
app: egress-gateway
spec:
nodeSelector:
node-role: egress
tolerations:
- key: dedicated
value: egress
effect: NoSchedule
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: egress-gateway
topologyKey: kubernetes.io/hostname
containers:
- name: proxy
image: registry.internal/egress-gateway:2.0.1
ports:
- containerPort: 3128
envFrom:
- secretRef:
name: egress-upstream-creds
---
apiVersion: v1
kind: Service
metadata:
name: egress-gateway
namespace: egress
spec:
selector:
app: egress-gateway
ports:
- port: 3128
targetPort: 3128La politique pour le namespace applicatif, qui ferme la sortie directe vers internet :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-only-via-gateway
namespace: crawlers
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: egress
podSelector:
matchLabels:
app: egress-gateway
ports:
- protocol: TCP
port: 3128
- to:
- ipBlock:
cidr: 10.244.0.0/16
- ipBlock:
cidr: 10.96.0.0/12Une telle politique transforme la passerelle d'une recommandation en obligation : un pod qui a oublié son HTTP_PROXY ne sortira pas en douce à côté, il prendra simplement un timeout. Ça se détecte en quelques minutes sur un environnement de test, pas en quelques semaines en prod.
Haute disponibilité et capacité
- Au minimum trois réplicas sur des nœuds distincts et un PodDisruptionBudget avec minAvailable: 2.
- Monitoring des connexions : nombre d'ouvertes, temps d'établissement du CONNECT, part de 5xx de l'upstream.
- Limite du nombre de connexions simultanées par pod client, pour qu'un service défaillant ne sature pas la passerelle.
- Surveillance du conntrack sur les nœuds de la passerelle : à des dizaines de milliers de connexions par seconde, la table sature et tu obtiens des pertes de paquets mystérieuses. Augmente nf_conntrack_max à l'avance.
Diagnostic dans le cluster : vérifier l'IP sortante d'un pod et où chercher les pannes
Les problèmes de proxy dans un cluster sont sournois parce que le symptôme apparaît loin de la cause. Voici une méthode qui permet de localiser une panne en quelques étapes.
Étape 1 : s'assurer que l'environnement est bien appliqué
kubectl -n crawlers exec deploy/price-collector -- env | grep -i proxyCherche les deux casses, l'exhaustivité de NO_PROXY, l'absence d'espaces parasites. Si l'appli démarre via un script entrypoint, vérifie en plus l'environnement du processus principal via /proc/1/environ — il peut différer de ce que montre un exec qui lance un nouveau shell.
Étape 2 : vérifier l'IP sortante
Déploie dans le cluster, ou sur un hôte externe de l'entreprise, un simple service d'écho qui renvoie l'adresse du client et les en-têtes. Puis, depuis le pod :
kubectl -n crawlers exec deploy/price-collector -- curl -s https://echo.yourcompany.example/ip
kubectl -n crawlers exec deploy/price-collector -- curl -s --noproxy '*' https://echo.yourcompany.example/ipLa première commande passe par les règles de l'environnement, la seconde force le contournement du proxy. Si les adresses sont identiques, le proxy n'est pas utilisé. Si la première renvoie l'IP du fournisseur de proxy et la seconde l'IP du nœud ou du NAT cloud, le schéma fonctionne. C'est aussi le seul moyen fiable de vérifier que tu vois bien l'adresse donnée par le panneau Proxeon, et non celle du nœud.
Étape 3 : regarder le dialogue avec le proxy du point de vue du client
kubectl -n crawlers exec deploy/price-collector -- curl -sv https://api.partner.example/status 2>&1 | head -30Dans la sortie, cherche la ligne avec CONNECT et le code de réponse du proxy à cette ligne. Signaux typiques :
- 407 Proxy Authentication Required — les identifiants ne passent pas ou sont faux. Vérifie l'encodage URL du mot de passe et que le proxy attend bien une autorisation Basic.
- 403 du proxy sur CONNECT — l'hôte ou le port cible est interdit par la politique du proxy, ou ton IP n'est pas dans la whitelist en mode autorisation par IP.
- 502/503 sur CONNECT — le proxy n'a pas pu joindre la cible. Si la cible est interne, le diagnostic est « trou dans le NO_PROXY ».
- Connection refused vers le proxy lui-même — mauvaise adresse, mauvais port, ou une NetworkPolicy qui bloque le trafic vers la passerelle.
- Timeout sans réponse — le plus souvent une politique réseau ou une route manquante. Vérifie d'abord que le DNS résout bien.
Étape 4 : vérifier les appels intra-cluster
kubectl -n crawlers exec deploy/price-collector -- curl -sv http://payments.default.svc.cluster.local:8080/healthz 2>&1 | grep -iE "connected|proxy|HTTP/"Si la sortie montre un passage par le proxy au lieu d'une connexion directe vers le ClusterIP, c'est que NO_PROXY n'a pas couvert cette adresse. Refais le test pour le nom court payments et pour l'IP du service — les trois doivent passer en direct.
Étape 5 : conteneur éphémère pour les images sans outils
Dans les images minimales, il n'y a ni curl ni nslookup. Utilise kubectl debug, qui attache un conteneur de debug dans l'espace réseau du pod :
kubectl -n crawlers debug -it pod/price-collector-7d9f4-x2k8q --image=registry.internal/netdebug:latest --target=app -- shÀ l'intérieur, tu retrouves les mêmes IP, le même resolv.conf et le même sidecar sur localhost. Seul l'environnement de l'application n'est pas hérité ici : vérifie les variables via /proc/PID/environ du processus principal.
Étape 6 : logs et événements
- Logs du sidecar ou de la passerelle : codes de réponse upstream, temps de CONNECT, hôtes contactés. C'est là qu'on voit une tentative d'envoyer une adresse interne vers l'extérieur.
- Événements du pod : kubectl describe pod montrera ImagePullBackOff — signe que le runtime de conteneurs n'est pas configuré pour le proxy, ou que le registry n'est pas dans son NO_PROXY.
- Logs CoreDNS en mode debug : un pic de NXDOMAIN sur des noms externes indique que l'appli résout les hôtes externes elle-même, alors qu'elle devrait les confier au proxy.
- Métriques conntrack sur les nœuds egress : une croissance de nf_conntrack_count vers la limite est le signe avant-coureur de pertes.
Étape 7 : distinguer problème de proxy et problème de cible
Un même 403 peut venir du proxy ou du site cible. L'en-tête Via ou des en-têtes spécifiques du fournisseur dans la réponse aident à trancher, tout comme la comparaison avec une requête via --noproxy. Si via le proxy c'est 403 et en direct 200 — question à la config du proxy, ou au fait que la ressource cible limite le trafic depuis les adresses de ce pool. Si c'est 403 dans les deux cas — le problème est dans la requête elle-même.
Erreurs classiques
Rassemblons les pièges sur lesquels on tombe le plus souvent. Beaucoup ont déjà été traités, ici — un récap pour vérification rapide.
- NO_PROXY copié depuis un serveur. La valeur localhost,127.0.0.1 dans un cluster casse à coup sûr la communication entre services. Il faut .svc, .cluster.local, le service et le pod CIDR.
- Parier sur le CIDR dans NO_PROXY chez Python ou un vieux curl. La bibliothèque ignore silencieusement l'entrée, le trafic vers le ClusterIP passe par le proxy. Appelle par noms DNS et utilise les suffixes.
- Seulement des variables en majuscules. curl ne lit pas HTTP_PROXY en majuscules. Déclare les deux formes.
- Identifiants dans le manifeste. Mot de passe dans git et dans la sortie de kubectl get -o yaml. Uniquement en Secret, mieux : autorisation par IP via la passerelle.
- Mot de passe avec caractères spéciaux sans encodage URL. Le client parse l'URL de travers, tu obtiens connection refused vers un hôte inexistant.
- Attendre que kubelet et containerd récupèrent l'env du pod. Les images ne se tirent pas, tout le monde va chercher dans le Deployment, alors qu'il faut un systemd drop-in sur les nœuds.
- Registry oublié dans le NO_PROXY du runtime. Le registry interne devient inaccessible, parce que containerd est allé le chercher via le proxy externe.
- Java avec HTTP_PROXY. La JVM ne lit pas ces variables. Il faut des propriétés système via JAVA_TOOL_OPTIONS.
- Sidecar en conteneur classique au lieu d'un init natif avec restartPolicy: Always. L'appli démarre avant le proxy et tombe sur les premières requêtes, le Job ne se termine jamais.
- Passerelle egress sans NetworkPolicy. La passerelle existe, mais la moitié des pods passe à côté, et tu l'apprends quand le partenaire t'envoie la liste des IP inconnues.
- Secret mis à jour sans rollout restart. Les vieux pods continuent avec l'ancien mot de passe, que tu as déjà désactivé dans le panneau.
- Métadonnées cloud ignorées. 169.254.169.254 absent de NO_PROXY — le SDK cloud ne peut pas obtenir ses clés temporaires, et l'appli plante sur des erreurs d'auth mystérieuses.
- Saturation d'une adresse proxy par toutes les réplicas. Trente réplicas tapent sur une seule adresse de sortie, l'API partenaire répond par des limites. Répartis la charge sur un pool d'adresses via le sidecar ou la passerelle.
Outils et ressources
L'ensemble à garder sous la main quand on travaille sur le trafic sortant dans un cluster.
Outils Kubernetes natifs
- kubectl exec et kubectl debug — vérification de l'environnement et des connexions réseau dans un pod, y compris les conteneurs éphémères pour les images minimales.
- kubectl describe pod et événements — ImagePullBackOff, échecs de probes, erreurs de montage de secrets.
- NetworkPolicy — forcer la sortie via la passerelle et tester les hypothèses « qui passe à côté ».
- Sidecars natifs (restartPolicy: Always dans initContainers) — cycle de vie correct d'un conteneur assistant proxy.
Diagnostic réseau
- Une image de debug avec curl, dig, tcpdump, ss, iptables — construis la tienne à partir d'une distro minimale et garde-la dans le registry interne.
- Un service d'écho maison qui renvoie l'IP du client et les en-têtes — la façon la plus honnête de voir l'adresse sortante.
- tcpdump sur le nœud avec un filtre sur l'IP du pod — quand il faut voir précisément vers quelle adresse la connexion s'ouvre.
- Logs CoreDNS en mode debug — pour comprendre qui résout quoi.
Gestion des secrets et de la configuration
- Un gestionnaire externe de secrets avec synchronisation vers un Secret du cluster — pour que les identifiants proxy ne vivent pas dans git et tournent de manière centralisée.
- Kustomize ou Helm avec un ConfigMap commun pour NO_PROXY — une source unique de vérité pour tous les Deployments d'un namespace.
- Des politiques d'admission (admission controllers) qui vérifient la présence de NO_PROXY dans chaque pod avec HTTP_PROXY — protection automatique contre le copier-coller « depuis le serveur ».
Fournisseur de proxy
Côté proxy, trois choses comptent : la possibilité d'autorisation par IP (pour le schéma avec passerelle), des adresses stables avec une durée de réservation claire, et une API pour gérer les accès, afin d'automatiser la rotation des mots de passe depuis la CI. Proxeon propose ces mécanismes dans le panneau et via l'API, ce qui permet d'intégrer le fournisseur dans un process GitOps au lieu de copier les chaînes de connexion à la main.
Cas et résultats
Cas 1 : plateforme ETL et services internes « cassés »
L'équipe data a ajouté HTTP_PROXY dans vingt Deployments, pour que les collecteurs de catalogues de marketplaces passent par des adresses dédiées. Le NO_PROXY a été copié depuis la doc serveur : localhost et 127.0.0.1. En une heure, la part de 5xx entre services internes est passée de 0,1% à 34%, les readiness probes de trois services ont commencé à flapper. Après ajout de .svc, .cluster.local et des deux CIDR dans un ConfigMap commun puis rollout restart, les erreurs ont disparu en cinq minutes. Effet bonus : le déplacement de NO_PROXY dans un ConfigMap a réduit le temps du changement suivant de la demi-journée (vingt manifestes à éditer) à un seul commit.
Cas 2 : intégrations JVM et sidecar
Un intégrateur financier en Java avec sept clients HTTP différents dans une même application (certains via des bibliothèques qui ne lisent pas les propriétés système du proxy). Une tentative de tout configurer via JAVA_TOOL_OPTIONS a couvert cinq clients sur sept. Solution — un sidecar natif avec interception transparente via iptables. Les sept clients sont passés par le proxy sans modification du code, les identifiants ont disparu de l'application, et les métriques de trafic sortant sont apparues pour la première fois de l'histoire du service. Coût : 40 Mo de mémoire par pod, ce qui, avec douze réplicas, s'est avéré négligeable.
Cas 3 : passerelle egress et autorisation par IP
Un cluster de soixante nœuds avec autoscaling appelait plusieurs API partenaires exigeant une whitelist d'IP. Chaque scaling de nœuds modifiait l'ensemble des adresses sortantes, les partenaires bloquaient le trafic. On a déployé une passerelle egress sur trois nœuds dédiés avec des adresses réservées, introduit des NetworkPolicy dans tous les namespaces applicatifs, basculé l'autorisation sur le proxy Proxeon en mode IP. Résultat : le nombre d'incidents « le partenaire nous a encore bloqués » est passé de trois-quatre par mois à zéro sur le trimestre, et tous les Secrets avec mots de passe proxy ont disparu du cluster. Effet de bord : les logs centralisés de la passerelle ont révélé deux services qui appelaient des API externes dont personne ne savait rien.
Cas 4 : containerd et un ImagePullBackOff mystérieux
Après avoir déplacé le cluster dans un réseau avec proxy obligatoire, les pods avec HTTP_PROXY fonctionnaient, mais les nouveaux nœuds ne pouvaient tirer aucune image. Cause — absence de drop-in pour containerd. Après ajout du fichier avec le proxy et le registry interne dans NO_PROXY, problème résolu. La leçon a été formalisée comme check dans le script de bootstrap des nœuds : avant de rejoindre le cluster, un nœud vérifie que containerd peut tirer une image de test depuis le registry interne et externe.
Checklist de déploiement
Passe en revue la liste avant de déployer un proxy sur un service de production. Chaque point correspond à l'incident nocturne de quelqu'un.
Préparation
- Une cartographie des connexions sortantes du service est établie : hôtes externes, services internes, bases, files, endpoints cloud, protocoles.
- Les service CIDR, pod CIDR, node CIDR du cluster et les domaines internes de l'entreprise sont connus.
- On a vérifié quels clients HTTP utilise l'application et comment chacun traite NO_PROXY (CIDR, suffixes, casse).
- Le niveau est choisi : conteneur, sidecar ou passerelle. La décision est documentée.
Configuration
- NO_PROXY est sorti dans un ConfigMap du namespace et contient : localhost, 127.0.0.1, .svc, .svc.cluster.local, .cluster.local, kubernetes.default.svc, service CIDR, pod CIDR, 169.254.169.254, domaines internes.
- Les variables sont déclarées dans les deux casses.
- Les identifiants sont uniquement en Secret ; le mot de passe est encodé en URL ; le RBAC sur les secrets est restreint.
- Pour la JVM, JAVA_TOOL_OPTIONS est défini avec nonProxyHosts.
- Si sidecar — il est natif (initContainers avec restartPolicy: Always), avec readinessProbe et limites de ressources.
- Si passerelle — au moins trois réplicas, anti-affinity, PDB, NetworkPolicy dans les namespaces applicatifs.
- Le runtime de conteneurs sur les nœuds est configuré séparément ; le registry interne est dans son NO_PROXY.
Vérification sur l'environnement de test
- env dans le pod montre les valeurs attendues.
- Une requête vers le service d'écho renvoie l'adresse du proxy ; avec --noproxy — celle du nœud.
- Les requêtes vers les services internes par nom court, nom complet et ClusterIP passent en direct (visible dans curl -v).
- Readiness et liveness passent de manière stable pendant au moins dix minutes.
- Le client de l'API Kubernetes (s'il existe) voit le control plane.
- Les logs du sidecar ou de la passerelle ne contiennent pas d'accès à des adresses internes.
Déploiement et après
- Rollout progressif avec maxUnavailable au plus 25% ; monitoring des 5xx entre services et des erreurs 407/403 du proxy.
- La procédure de rotation du mot de passe est documentée : mise à jour du Secret, rollout restart, désactivation de l'ancien accès.
- Des alertes sont configurées sur la hausse de la part d'erreurs venant du proxy et sur l'approche de la limite conntrack sur les nœuds egress.
- Une semaine plus tard — revue des logs de la passerelle : pas d'apparition de destinataires externes inattendus.
FAQ
Peut-on définir HTTP_PROXY globalement pour tout le cluster, plutôt que dans chaque Deployment ?
Il n'y a pas de mécanisme direct : les variables d'environnement font partie de la spec du pod. Les options pratiques — un ConfigMap commun avec envFrom dans tous les manifestes via Helm ou Kustomize, ou un mutating admission controller qui ajoute automatiquement les variables aux pods portant une certaine annotation. La seconde méthode est plus puissante, mais demande de la rigueur : elle doit aussi ajouter NO_PROXY, sinon l'automatisation cassera massivement les services.
Pourquoi curl dans le pod passe par le proxy, mais l'application Python non, alors que l'environnement est le même ?
C'est probablement une question de casse ou de parsing de NO_PROXY. curl ne lit http_proxy qu'en minuscules, Python — dans les deux. D'un autre côté, Python ne comprend pas le CIDR dans NO_PROXY, alors qu'un curl récent le comprend. Vérifie les deux hypothèses : définis les variables dans les deux casses et utilise des suffixes de domaine plutôt que du CIDR.
Faut-il un NO_PROXY si tout le trafic interne passe de toute façon par un sidecar avec des règles direct ?
Techniquement, on peut s'en passer, mais on recommande de le garder. D'abord, un saut supplémentaire via localhost pour des milliers de requêtes internes, c'est des latences non nulles et de la charge sur le sidecar. Ensuite, si le sidecar tombe ou redémarre, l'application avec un NO_PROXY correct continuera de parler aux services internes, alors que sans lui, elle perdra absolument tout.
Comment savoir qu'une requête vers un partenaire est bien partie depuis l'adresse du proxy, et non depuis celle du nœud ?
Le seul moyen fiable — une requête de contrôle vers un service d'écho qui renvoie l'IP du client, depuis le même pod et avec le même environnement. Compare la réponse avec l'adresse affichée dans le panneau Proxeon pour ton accès et avec la réponse sous --noproxy. Les logs du partenaire peuvent aider aussi, mais ils sont rarement sous la main.
Faut-il chiffrer la connexion jusqu'au proxy (schéma https:// dans HTTPS_PROXY) ?
Pour le trafic HTTPS vers les sites cibles, le contenu est déjà chiffré de bout en bout à l'intérieur du tunnel CONNECT, le proxy ne voit que l'hôte de destination. Du TLS jusqu'au proxy protège les identifiants d'authentification et les noms d'hôtes sur le segment du pod au proxy. Si ce segment traverse un réseau non fiable, du TLS jusqu'au proxy est justifié, mais assure-toi que le client et le proxy le supportent : certaines bibliothèques ignorent le schéma https:// dans HTTPS_PROXY. À l'intérieur du cluster, entre le pod et la passerelle egress, des politiques réseau suffisent généralement.
Que faire du trafic des bases de données et des brokers qui doit sortir du cluster ?
Un proxy HTTP ne le laissera pas passer — ce n'est pas du HTTP. Options : un mode SOCKS5 si le fournisseur et la bibliothèque cliente le supportent ; une interception transparente au niveau du sidecar avec tunnelisation TCP ; ou une passerelle egress qui travaille au niveau du routage, pas du protocole. Le choix dépend de si tu as besoin d'une IP externe précise ou si une sortie centralisée suffit.
Peut-on utiliser le même proxy pour containerd et pour les applications ?
Techniquement oui, mais en pratique ce sont des tâches différentes. Le runtime tire des images depuis des registry publics, et il a besoin du proxy du périmètre d'entreprise. Les applications parlent à des API partenaires, et elles ont besoin d'adresses dédiées avec des propriétés précises. Mélanger conduit à ce que le téléchargement d'images consomme les quotas de trafic destinés aux usages applicatifs. Sépare.
Comment changer un mot de passe proxy sans downtime ?
Crée un second accès chez le fournisseur, mets à jour le Secret avec les nouveaux identifiants, fais un rollout restart avec mise à jour progressive des réplicas, vérifie dans les logs l'absence de 407, et seulement ensuite désactive l'ancien accès. Si tu utilises l'autorisation par IP via la passerelle, la rotation de mots de passe disparaît complètement de ta liste de tâches.
Comment restreindre l'usage du proxy à certains domaines externes précis ?
NO_PROXY fonctionne à l'inverse — il décrit où ne pas passer par le proxy, et on ne peut pas construire de whitelist à partir de lui. Cette logique se met en place dans le sidecar ou la passerelle : des règles du type « domaines de la liste A — via le pool d'adresses Proxeon, tout le reste — en direct ou interdit ». Plus une NetworkPolicy, pour que la sortie directe vers internet n'existe physiquement pas.
Conclusion
Le trafic sortant dans Kubernetes diffère d'un serveur non pas par les détails, mais par la structure elle-même : chaque pod a deux mondes — interne et externe — et le proxy ne doit voir que le second. De là découle tout le reste. NO_PROXY passe du statut de formalité à celui d'élément architectural, qui doit décrire tout le réseau interne du cluster et tenir compte du fait que les bibliothèques l'interprètent différemment. Les variables d'environnement ne fonctionnent qu'à l'intérieur du processus — kubelet, le runtime de conteneurs et les SDK bornés demandent une attention séparée. Les identifiants ne doivent pas se retrouver dans les manifestes et, idéalement, disparaître complètement du cluster grâce à une passerelle avec autorisation par IP.
Les trois niveaux d'intégration d'un proxy — conteneur, sidecar, passerelle egress — ne sont pas concurrents, mais des outils pour des problèmes différents. Le niveau conteneur est rapide et granulaire. Le sidecar enlève la dépendance au comportement des bibliothèques et ajoute métriques, rotation, retries. La passerelle donne au cluster un visage prévisible dans le monde extérieur et fait de la sortie via proxy non pas une recommandation, mais une règle, appuyée par les politiques réseau.
Que faire ensuite ? Commence par l'inventaire : établis la cartographie des connexions sortantes de tes services et regarde combien d'entre elles sont internes. Ensuite, sors NO_PROXY dans un ConfigMap commun et vérifie-le sur le banc de test selon la méthode de la section diagnostic. Si le cluster compte plus d'une stack ou s'il y a des exigences d'adresses stables — conçois une passerelle egress et connecte-la à des adresses dédiées Proxeon avec autorisation par IP. Et garde la checklist de déploiement à portée de main : elle s'amortit dès le premier incident qui n'a pas eu lieu.