Un ingénieur habitué aux serveurs classiques arrive dans Kubernetes et fait ce qu'il a toujours fait : il exporte HTTP_PROXY dans l'environnement, redémarre le processus et attend que tout le trafic sortant passe par le proxy. Parfois ça marche. Beaucoup plus souvent, ça casse la moitié du cluster et l'autre moitié continue d'aller sur Internet directement, en contournant le proxy. Et là commence le plus intéressant : pourquoi certains pods ont récupéré les variables et d'autres non, pourquoi les services ne se voient plus et pourquoi le healthcheck est soudain passé par un serveur proxy qui n'est pas dans la liste de confiance.

Cet article est une analyse détaillée de la façon dont le trafic sortant fonctionne réellement dans un cluster et où intégrer correctement un proxy. Nous n'allons pas reprendre la configuration de base des variables d'environnement : vous la connaissez déjà. Le sujet, c'est la spécificité de Kubernetes, où les approches habituelles produisent des effets inattendus. Tous les exemples sont tirés de vrais extraits de manifestes, et c'est Proxeon (proxeon.net) qui joue le rôle de l'infrastructure proxy.

Les bases : pourquoi tout est différent dans un cluster

Commençons par le fondamental. Sur un serveur classique, la notion de trafic sortant est triviale : une seule interface réseau, une table de routage, des variables d'environnement système, et presque tout ce que vous lancez en hérite depuis le shell parent. On exporte la variable dans le profile, et tout le processus utilisateur la voit.

Dans Kubernetes, ce point unique n'existe pas. Il y a le pod - l'unité minimale de déploiement, à l'intérieur de laquelle vit un ou plusieurs conteneurs. Le pod a son propre namespace réseau, sa propre adresse IP, son propre ensemble de variables d'environnement défini par le manifeste. Le shell dont le processus pourrait hériter quoi que ce soit n'existe tout simplement pas. Une variable d'environnement n'apparaît dans le conteneur que si vous l'avez explicitement déclarée dans la spécification du pod ou dans l'image.

Qu'est-ce que le trafic sortant d'un pod

Quand un conteneur contacte une adresse externe, le paquet parcourt un long chemin. D'abord il sort du namespace réseau du conteneur via une interface virtuelle. Ensuite il entre dans la pile réseau du nœud, où le plugin CNI - le composant responsable du réseau du cluster - s'en occupe. Puis entrent en jeu les règles iptables ou eBPF, le mécanisme SNAT (substitution d'adresse source), après quoi le paquet part via l'interface réseau du nœud vers le monde extérieur.

Point clé : du point de vue du service externe, la requête n'arrive pas depuis l'adresse du pod, mais depuis l'adresse du nœud du cluster. Le pod est masqué derrière la traduction d'adresses. C'est la première chose qui surprend les débutants quand ils essaient de configurer un accès par liste blanche IP : ils ajoutent l'adresse du pod à la liste, et le trafic arrive d'une adresse complètement différente.

Deux types de trafic sortant

Il est important de distinguer dès le départ deux flux fondamentalement différents :

  • Est-ouest - le trafic entre services à l'intérieur du cluster. Un pod contacte un autre via un service, un ClusterIP ou un nom DNS du type my-service.namespace.svc.cluster.local.
  • Nord-sud - le trafic vers l'extérieur, vers des API externes, des bases de données, des services partenaires, des stockages objet.

Le proxy n'est presque toujours nécessaire que pour le trafic nord-sud. Et l'erreur la plus fréquente et la plus douloureuse, c'est qu'une mauvaise configuration du proxy capture accidentellement aussi le trafic est-ouest, en brisant la communication interne. C'est précisément pour cela que la liste d'exclusions est ici plus importante qu'ailleurs. Mais nous y reviendrons un peu plus tard.

Plongée en profondeur : les trois niveaux où vit le proxy

Il existe exactement trois niveaux architecturaux où l'on peut intégrer un proxy dans le chemin sortant d'un pod. Chacun résout le problème à sa manière, chacun a son coût. Examinons les trois honnêtement, avec les avantages et les inconvénients.

Niveau 1 : le conteneur lui-même

C'est quand l'application à l'intérieur du conteneur connaît elle-même le proxy. Elle lit les variables d'environnement HTTP_PROXY et HTTPS_PROXY ou a le proxy dans sa propre configuration et dirige ses requêtes HTTP à travers lui. La logique proxy est intégrée dans la bibliothèque cliente de l'application elle-même.

Avantages. Simplicité maximale de mise en place au démarrage. Rien à ajouter au cluster, aucun composant supplémentaire. Contrôle au niveau du pod concerné : vous savez exactement quelle application va où.

Inconvénients. Chaque application doit savoir lire ces variables, et elles sont loin de toutes le pouvoir. La configuration s'éparpille sur des dizaines de manifestes. Mettre à jour l'adresse du proxy, c'est repasser sur tous les déploiements. On oublie facilement un service, et il part directement. Pas de politique centralisée.

Niveau 2 : le conteneur sidecar

Ici, à côté du conteneur principal dans le même pod, on lance un second conteneur - le sidecar. Il intercepte le trafic sortant et le dirige à travers le proxy. L'application peut totalement ignorer l'existence du proxy : elle envoie ses requêtes comme d'habitude, et le sidecar les proxifie discrètement. C'est ainsi que fonctionnent les service mesh comme Istio et Linkerd, et on peut suivre le même principe pour installer un léger agent proxy local.

Avantages. Transparence pour l'application. Politique unifiée via le template de pod. Possibilité d'ajouter au proxying de l'observabilité : métriques, tracing, tentatives, timeouts. Le sidecar est isolé dans le pod et partage son cycle de vie.

Inconvénients. Surcoût : chaque pod compte désormais deux conteneurs, donc plus de mémoire et de CPU. Le débogage se complique - un maillon supplémentaire s'ajoute à la chaîne. L'ordre de démarrage des conteneurs est important : si l'application démarre avant le sidecar, les premières requêtes peuvent échouer. Dans Kubernetes 1.28+, ce problème se résout avec les native sidecar containers en tant qu'init-containers avec la politique restartPolicy Always.

Niveau 3 : la passerelle egress du cluster

C'est un nœud ou un pod dédié par lequel on force tout le trafic sortant du cluster. Les paquets sont routés vers la passerelle egress par les moyens du CNI, et c'est elle qui communique avec le proxy externe ou qui sert elle-même de point de sortie avec une IP sortante stable.

Avantages. Point de contrôle unique pour tout le cluster. Une adresse sortante stable et prévisible, pratique à ajouter aux listes blanches des partenaires externes. La politique se change à un seul endroit. Les applications n'en savent rien.

Inconvénients. La passerelle egress devient un point critique : si elle tombe, tout le nord-sud s'arrête. Il faut de la redondance et de la supervision. La configuration est plus complexe et nécessite le support du CNI. La granularité est plus faible : il est plus difficile de définir une politique différente selon les applications sans règles supplémentaires.

Comment choisir le niveau

Repère pratique issu de l'expérience : petit projet avec deux ou trois services qui ont besoin d'un proxy externe - niveau conteneur. Cluster moyen avec des dizaines de services et l'exigence d'une politique unifiée - sidecar. Grande infrastructure où des partenaires externes exigent une IP sortante fixe et un audit de tout le trafic - passerelle egress. Souvent on combine les niveaux : passerelle egress pour le contrôle de base plus des variables d'environnement pour affiner des pods individuels via Proxeon.

Variables d'environnement dans le manifeste et rôle de NO_PROXY

Passons au détail le plus sous-estimé. Les variables HTTP_PROXY, HTTPS_PROXY et NO_PROXY se définissent dans le manifeste via le bloc env de la spécification du conteneur. Les noms classiques sont généralement dupliqués en minuscules, car une partie des bibliothèques ne lit que ces variantes.

apiVersion: apps/v1
kind: Deployment
metadata:
 name: worker
spec:
 replicas: 2
 selector:
matchLabels:
 app: worker
 template:
metadata:
 labels:
app: worker
spec:
 containers:
- name: app
 image: registry.example.com/worker:1.4.0
 env:
- name: HTTP_PROXY
 value: "http://gate.proxeon.net:8080"
- name: HTTPS_PROXY
 value: "http://gate.proxeon.net:8080"
- name: NO_PROXY
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"
- name: http_proxy
 value: "http://gate.proxeon.net:8080"
- name: https_proxy
 value: "http://gate.proxeon.net:8080"
- name: no_proxy
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"

Pourquoi sans un NO_PROXY correct le cluster s'effondre

Voilà le cœur du problème. Dès que vous avez déclaré HTTP_PROXY, le client HTTP de l'application commence à diriger à travers le proxy absolument toutes les requêtes, y compris les appels aux services voisins à l'intérieur du cluster. Or les services internes ne sont accessibles qu'à l'intérieur du réseau du cluster - un serveur proxy externe ne pourra physiquement pas les atteindre. Résultat : une requête vers orders.default.svc.cluster.local part vers le proxy externe, celui-ci essaie de résoudre ce nom, n'y arrive pas, et renvoie une erreur. La communication interne se casse instantanément.

NO_PROXY est une liste d'exclusions, des adresses et des domaines à contourner pour envoyer directement. Sur un serveur classique on y met localhost et éventuellement quelques sous-réseaux internes. Dans Kubernetes, cette liste devient critique, car elle doit couvrir toute la topologie interne du cluster. On oublie un suffixe, et une partie du trafic interservices part via le proxy externe.

Ce qui doit obligatoirement figurer dans le NO_PROXY du cluster

  • localhost et 127.0.0.1 - les appels à l'intérieur du pod lui-même.
  • La plage Pod CIDR - le sous-réseau d'où sont attribuées les adresses des pods, par exemple 10.0.0.0/8 ou une plage affinée pour votre cluster.
  • La plage Service CIDR - le sous-réseau des ClusterIP des services, souvent 10.96.0.0/12.
  • Les suffixes DNS du cluster - .svc, .svc.cluster.local, .cluster.local. Ce sont eux qui couvrent tous les noms DNS internes des services.
  • kubernetes.default - le nom du serveur API, que contactent les applications et les agents.
  • Les métadonnées cloud - l'adresse 169.254.169.254, si vous êtes dans un environnement cloud, pour que les appels de métadonnées ne passent pas par le proxy.

Subtilités de syntaxe de NO_PROXY

Ici se cache toute une couche d'évidences contre-intuitives sur lesquelles même des ingénieurs expérimentés trébuchent.

Premièrement, les bibliothèques interprètent les entrées différemment. Certaines considèrent que .cluster.local avec un point initial signifie un suffixe et correspondra à tous les sous-domaines. D'autres exigent le format cluster.local sans point. En pratique : indiquez les deux variantes, avec et sans point, pour couvrir un maximum de clients.

Deuxièmement, le support de la notation CIDR n'est pas universel. La bibliothèque Go comprend 10.0.0.0/8, mais les anciennes versions de certains clients dans d'autres langages non, ils ont besoin d'adresses individuelles ou de plages dans un autre format. Vérifiez le comportement de votre propre stack.

Troisièmement, les ports. Si une entrée NO_PROXY est indiquée sans port, elle s'applique généralement à n'importe quel port de l'hôte. Mais certains clients font une correspondance stricte avec le port. Avec des ports non standard pour les services internes, c'est important à vérifier.

Insight de terrain : neuf incidents sur dix de communication interne cassée après l'introduction d'un proxy, c'est un NO_PROXY incomplet. Composez une liste de référence pour votre cluster une bonne fois pour toutes, mettez-la dans un ConfigMap commun et réutilisez-la dans tous les déploiements. Cela économise des dizaines d'heures de débogage.

Ce qui NE récupère PAS les variables d'environnement

La conviction naïve « j'ai mis HTTP_PROXY, donc tout le trafic passe par le proxy » est doublement erronée dans un cluster. Il existe toute une classe de composants qui ignorent purement et simplement ces variables. Il faut les connaître à l'avance.

kubelet et les composants système

Les variables d'environnement d'un conteneur ne sont visibles que par les processus à l'intérieur de ce conteneur. kubelet - l'agent du nœud, qui télécharge les images, lance les conteneurs et communique avec le serveur API - vit au niveau du nœud, pas du pod. Il ne lit pas les env du manifeste. Si vous voulez que kubelet tire les images via un proxy, la configuration se fait au niveau du service systemd kubelet ou de la configuration du container runtime, pas dans la spécification du pod.

# /etc/systemd/system/kubelet.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://gate.proxeon.net:8080"
Environment="HTTPS_PROXY=http://gate.proxeon.net:8080"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"

Il en va de même du container runtime - containerd ou CRI-O. Le téléchargement des images passe par leur propre contexte réseau. La configuration du proxy pour le registre d'images se fait dans la config du runtime, et c'est une histoire distincte de celle des pods.

Images avec leur propre configuration

Beaucoup d'images populaires ont des paramètres réseau intégrés qui écrasent les variables d'environnement. Par exemple, les gestionnaires de paquets, les serveurs web ou les outils proxy à l'intérieur de l'image peuvent lire leur propre fichier de configuration plutôt que l'environnement. Si l'application dans le conteneur utilise, disons, un fichier de paramètres avec des paramètres de connexion explicitement définis, elle ne verra tout simplement pas vos variables env.

Catégorie à part - les applications où le client réseau s'initialise avant la lecture de l'environnement ou met en cache les paramètres au démarrage. Changer la variable sans redémarrer le processus n'aura alors aucun effet.

Certains SDK et runtimes de langage

C'est le groupe le plus sournois. Le respect de HTTP_PROXY est une convention, pas un standard. Certains la respectent, d'autres non.

  • Go. Le http.Client standard via ProxyFromEnvironment respecte les variables. Mais si le code crée un transport avec Proxy nil explicitement, les variables sont ignorées.
  • Python. La bibliothèque requests lit l'environnement par défaut. Mais les sockets bas niveau, certains clients gRPC et les bibliothèques asynchrones non.
  • Java. La JVM utilise ses propres propriétés système http.proxyHost et https.proxyHost, et ne lit pas du tout les variables d'environnement par défaut. Il faut les passer via JAVA_TOOL_OPTIONS.
  • Node.js. Le module http intégré ne respecte pas les variables d'environnement. Il faut des agents tiers qui les lisent.
  • gRPC. Certaines implémentations lisent une variable spéciale grpc_proxy, pas les standards.

La conclusion est simple : on ne peut pas supposer que parce que la variable est déclarée, tout le trafic passera par elle. Vérifiez chaque runtime séparément. Pour la JVM par exemple, le manifeste ressemble à ceci :

env:
 - name: JAVA_TOOL_OPTIONS
value: "-Dhttp.proxyHost=gate.proxeon.net -Dhttp.proxyPort=8080 -Dhttps.proxyHost=gate.proxeon.net -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.svc|*.cluster.local"

Notez bien : dans la JVM, le séparateur d'exclusions est la barre verticale, pas la virgule, et les motifs utilisent l'astérisque. Encore un piège pour ceux qui copient mécaniquement NO_PROXY.

Secrets : les identifiants proxy via Secret, pas dans le manifeste

Si votre proxy exige une authentification, se pose la question du stockage du login et du mot de passe. La tentation est grande de les inscrire directement dans l'URL au sein de la valeur env. C'est à ne pas faire, et voici pourquoi.

Les manifestes de déploiement sont presque toujours dans un système de contrôle de version. Un mot de passe en clair dans env, c'est une fuite d'identifiants dans l'historique Git, accessible à tous ceux qui ont accès au dépôt. De plus, les valeurs env sont visibles par quiconque peut exécuter kubectl describe pod. C'est une violation directe du principe du moindre privilège.

La bonne voie, c'est l'objet Secret. Il stocke les données sensibles séparément, avec la possibilité de restreindre l'accès via RBAC et d'activer le chiffrement dans le stockage etcd.

apiVersion: v1
kind: Secret
metadata:
 name: proxeon-credentials
type: Opaque
stringData:
 proxy-user: my_account
 proxy-pass: s3cr3t_token_value

Ensuite on connecte les valeurs du secret aux variables d'environnement du conteneur via secretKeyRef, et on assemble l'URL du proxy de façon à ce que les identifiants ne s'affichent pas dans le manifeste lui-même :

env:
 - name: PROXY_USER
valueFrom:
 secretKeyRef:
name: proxeon-credentials
key: proxy-user
 - name: PROXY_PASS
valueFrom:
 secretKeyRef:
name: proxeon-credentials
key: proxy-pass
 - name: HTTPS_PROXY
value: "http://$(PROXY_USER):$(PROXY_PASS)@gate.proxeon.net:8080"
 - name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"

Kubernetes substitue les valeurs des variables précédemment déclarées via la syntaxe avec le dollar et les parenthèses. Cela permet d'assembler l'URL avec les identifiants au runtime, sans mettre le mot de passe directement dans le texte du manifeste. Notez bien : la valeur HTTPS_PROXY assemblée restera visible dans l'env du conteneur en cours d'exécution, donc limitez en plus qui peut exécuter exec et describe sur ces pods.

Bonnes pratiques avec les secrets

  • Activez le chiffrement etcd at rest - sinon les secrets sont stockés en simple base64, ce qui n'est pas une protection.
  • Limitez l'accès aux secrets via RBAC : un pod ne doit voir que son propre secret.
  • Faites tourner les identifiants proxy régulièrement et automatisez le redémarrage des pods après rotation.
  • Envisagez des gestionnaires de secrets externes connectés via un driver CSI, pour que les identifiants n'arrivent jamais dans etcd.
  • Ne loguez jamais l'URL proxy assemblée - le mot de passe fuirait dans le système de collecte de logs.

Approche sidecar : quand elle est justifiée et ce qu'elle apporte

Nous avons déjà cité le sidecar comme l'un des trois niveaux. Maintenant examinons-le plus en détail comme stratégie à part entière, car c'est l'outil le plus flexible si vous êtes prêt à y consacrer des ressources.

Quand le sidecar est justifié

  • L'application ne sait pas lire HTTP_PROXY et la réécrire est impossible ou coûteux.
  • Il faut une politique de proxying unifiée sans modifier chaque application.
  • Il faut une interception transparente du trafic, y compris les protocoles non HTTP.
  • Il faut de l'observabilité : métriques des connexions sortantes, tracing, audit.
  • Il faut des politiques de tentatives, de timeouts, de coupure de circuit au niveau des connexions.

Ce que le sidecar apporte en plus du proxying

C'est là que se cache la vraie valeur. Un sidecar n'est pas juste un redirecteur de paquets. Bien configuré, il devient un point de contrôle et d'observation de tout le trafic sortant du pod.

  • Métriques. Combien de requêtes sont sorties, vers quels hôtes, avec quelle latence, combien d'erreurs. Sans sidecar, ces données devraient être collectées dans chaque application séparément.
  • Tracing. Des traces distribuées des appels sortants, liées aux requêtes entrantes.
  • Politiques de fiabilité. Tentatives automatiques pour les requêtes idempotentes, timeouts, limitation des connexions concurrentes.
  • TLS unifié. Le sidecar peut terminer et établir des connexions sécurisées de manière centralisée.
  • Audit et conformité. Un journal complet de l'endroit exact où allait l'application - irremplaçable pour la conformité.

Exemple de pod avec sidecar proxy

Ci-dessous un template simplifié où le conteneur principal dirige les requêtes HTTP sortantes vers un sidecar local, qui les proxifie ensuite via Proxeon. Pour l'application, l'adresse du proxy est localhost, ce qui exclut automatiquement le trafic interservices du proxying externe si l'application contacte ses voisins directement.

apiVersion: v1
kind: Pod
metadata:
 name: app-with-proxy-sidecar
 labels:
app: billing
spec:
 containers:
- name: app
 image: registry.example.com/billing:2.1.0
 env:
- name: HTTP_PROXY
 value: "http://127.0.0.1:3128"
- name: HTTPS_PROXY
 value: "http://127.0.0.1:3128"
- name: NO_PROXY
 value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"
- name: egress-proxy
 image: registry.example.com/egress-agent:1.0.0
 ports:
- containerPort: 3128
 env:
- name: UPSTREAM_PROXY
 value: "http://gate.proxeon.net:8080"
 resources:
requests:
 cpu: 50m
 memory: 64Mi
limits:
 cpu: 200m
 memory: 128Mi

Sidecar natif et ordre de démarrage

Le problème classique du sidecar, c'est la course au démarrage. Si l'application principale démarre et fait sa première requête sortante avant que le sidecar soit prêt à accepter des connexions, la requête échoue. Depuis Kubernetes 1.28, le support des conteneurs sidecar natifs est apparu : ils se déclarent dans le bloc initContainers avec restartPolicy Always et démarrent de manière garantie tout en restant vivants jusqu'au conteneur principal. Cela résout la course proprement, sans béquilles comme des délais ou des tentatives au démarrage.

spec:
 initContainers:
- name: egress-proxy
 image: registry.example.com/egress-agent:1.0.0
 restartPolicy: Always
 ports:
- containerPort: 3128

Passerelle egress : une adresse sortante stable pour le cluster

Le troisième niveau mérite une discussion à part, car c'est précisément lui qui résout la tâche pour laquelle on vient le plus souvent au proxy dans un cluster : une IP sortante prévisible.

Pourquoi une IP sortante fixe est nécessaire

Les partenaires externes, les passerelles de paiement, les fournisseurs de données travaillent souvent par liste blanche d'adresses. Ils disent : nous acceptons les requêtes uniquement depuis ces IP. Dans un cluster ordinaire, l'adresse sortante d'un pod est l'adresse du nœud sur lequel le pod se trouve à ce moment-là. Les nœuds sont nombreux, ils s'adaptent, se remplacent, s'ajoutent lors de l'autoscaling. L'adresse est imprévisible. Le partenaire ne peut pas mettre en liste blanche tout le pool de nœuds, qui change en plus.

La passerelle egress résout cela : tout le trafic sortant est rassemblé en un point avec une adresse fixe. Le partenaire met en liste blanche une ou deux IP stables - et tout fonctionne. En utilisant Proxeon comme point de sortie, vous obtenez une adresse externe stable que vous donnez au partenaire une seule fois.

Comment le trafic est routé vers la passerelle egress

Le mécanisme dépend du CNI. Certains plugins CNI supportent un objet de politique egress, où vous décrivez : le trafic des pods avec telles étiquettes, allant vers l'extérieur, doit sortir par tel nœud ou telle adresse. Le plugin configure automatiquement les règles SNAT et de routage correspondantes. Conceptuellement, cela ressemble à ceci :

apiVersion: policy.example.io/v1
kind: EgressPolicy
metadata:
 name: billing-egress
spec:
 selector:
matchLabels:
 egress: proxeon
 egressIP: 203.0.113.10
 destinationCIDRs:
- 0.0.0.0/0

La syntaxe exacte diffère selon le CNI, donc référez-vous à la documentation de votre plugin. L'idée reste la même : marquer les pods dont le trafic est dirigé via la passerelle, et définir une adresse de sortie stable.

Fiabilité de la passerelle egress

Puisque la passerelle est un point unique, elle est aussi un point de défaillance unique. Règles de survie :

  • Prévoyez de la redondance : au minimum deux nœuds-passerelles avec bascule automatique.
  • Surveillez la disponibilité de la sortie avec une requête synthétique vers l'extérieur chaque minute.
  • Surveillez la bande passante : tout le nord-sud passe par la passerelle, un goulot d'étranglement se répercutera sur tout.
  • Séparez les politiques : le trafic critique et le trafic de fond gagnent à être séparés sur des chemins différents, pour que la charge de fond ne gêne pas l'important.

Diagnostic du trafic sortant dans le cluster

Quand quelque chose ne va pas - et ça arrivera - il faut un arsenal de vérifications. Rassemblons un ensemble pratique de commandes et d'approches.

Étape 1 : connaître la vraie IP sortante depuis un pod

La première chose à vérifier : depuis quelle adresse le pod est vu par le monde extérieur. On lance un conteneur de débogage éphémère ou on fait un exec dans un pod existant et on contacte un service qui renvoie votre adresse publique.

kubectl run nettest --rm -it --image=registry.example.com/nettools:1.0 --restart=Never -- sh
# внутри контейнера
curl -s https://api.ipify.org
curl -s -x http://gate.proxeon.net:8080 https://api.ipify.org

La première requête montre l'adresse sans proxy, la seconde - via le proxy explicitement. Si les deux sont identiques alors que vous attendiez des résultats différents, c'est que le proxy ne s'applique pas. Si la seconde renvoie l'adresse attendue de Proxeon, le proxy fonctionne et le problème est dans la configuration de l'application.

Étape 2 : vérifier si l'application voit les variables

kubectl exec deploy/worker -c app -- env | grep -i proxy

Si la sortie est vide - les variables ne sont pas arrivées au conteneur. Vérifiez le manifeste et le fait que le pod a été recréé après modification. Rappel : changer env nécessite de recréer le pod, ce n'est pas pris en compte à chaud.

Étape 3 : vérifier la résolution DNS de l'intérieur

Beaucoup d'erreurs se masquent en problème de proxy alors qu'en réalité c'est du DNS. On vérifie la résolution d'un nom interne et d'un nom externe :

kubectl exec deploy/worker -c app -- nslookup orders.default.svc.cluster.local
kubectl exec deploy/worker -c app -- nslookup api.external-partner.com

Si le nom interne ne se résout pas, le problème est dans CoreDNS ou dans le fait que la requête est partie vers le proxy externe à cause d'un suffixe manquant dans NO_PROXY. C'est la confirmation directe que la liste d'exclusions est incomplète.

Étape 4 : où regarder les refus

  • Logs de l'application. Cherchez les erreurs de connexion, les timeouts, le refus d'authentification proxy (généralement le code 407).
  • Logs du sidecar. S'il existe, on y voit quelles requêtes il a acceptées et où il les a dirigées.
  • Métriques de la passerelle egress. Croissance des refus et des latences sur la passerelle.
  • Événements du pod. kubectl describe pod montrera les problèmes de démarrage, y compris la course du sidecar.
  • NetworkPolicy. Vérifiez si la politique réseau ne bloque pas le trafic sortant - cause fréquente de refus silencieux.

Signatures typiques des problèmes

  • Code 407 du proxy - identifiants erronés ou manquants. Vérifiez le Secret.
  • Timeout lors de l'appel à un service interne après activation du proxy - NO_PROXY incomplet.
  • Des IP sortantes différentes lors de requêtes répétées - le trafic ne passe pas par la passerelle egress, c'est le SNAT ordinaire du nœud qui fonctionne.
  • Les premières requêtes après le démarrage échouent, puis tout fonctionne - course du sidecar, passez au sidecar natif.
  • Une application Java ignore le proxy - vous avez oublié JAVA_TOOL_OPTIONS et les propriétés système.

Erreurs courantes à éviter

Rassemblons en un seul endroit les pièges sur lesquels on tombe le plus souvent. Vérifiez-vous par rapport à cette liste avant, pas après l'incident.

NO_PROXY incomplet

Le champion absolu. On a oublié le Service CIDR, on a oublié le suffixe .svc, on n'a pas tenu compte de l'adresse des métadonnées cloud - et on a récolté une cascade de refus mystérieux. Partez toujours de la liste de référence complète pour votre cluster.

Mot de passe proxy en clair dans le manifeste

Fuite dans Git et dans la sortie describe. Uniquement Secret, accès restreint.

Croire que tous les runtimes respectent les variables

La JVM, Node.js, une partie des clients gRPC ignorent HTTP_PROXY. Vérifiez chaque stack séparément et configurez de manière native.

Ignorer kubelet et le runtime

Le téléchargement des images via proxy se configure au niveau du nœud, pas du pod. Ne soyez pas surpris que les images ne se tirent pas si vous n'avez configuré que les env du manifeste.

Course au démarrage du sidecar

L'application démarre avant le proxy et fait échouer les premières requêtes. La solution - les native sidecar containers.

Passerelle egress sans redondance

Un point de défaillance unique sans duplication fait tomber tout le trafic sortant. Prévoyez de la redondance et surveillez.

Mélange des formats CIDR

Vous avez indiqué 10.0.0.0/8 pour un client qui ne comprend pas le CIDR. Vérifiez quel format mange votre bibliothèque et donnez une alternative.

Absence de recréation des pods après changement d'env

Vous avez changé la variable dans le déploiement, mais les anciens pods continuent de tourner avec les anciennes valeurs jusqu'au redémarrage. Assurez le rollout.

Oubli des majuscules/minuscules des variables

Une partie des bibliothèques ne lit que http_proxy en minuscules. Dupliquez les noms dans les deux casses.

Outils et ressources

Ce qu'il faut garder sous la main pour travailler avec le trafic sortant dans un cluster.

Diagnostic

  • kubectl exec et kubectl debug - la base pour les vérifications depuis l'intérieur du pod.
  • Conteneurs de débogage éphémères - permettent de connecter un ensemble d'outils réseau à un pod en fonctionnement sans reconstruire l'image.
  • Image avec utilitaires réseau - curl, dig, nslookup, traceroute, rassemblés dans un conteneur pour des vérifications rapides.
  • Service de retour d'IP publique - vérification de la vraie adresse sortante.

Infrastructure

  • ConfigMap avec le NO_PROXY de référence - source unique de vérité pour tous les déploiements.
  • Secret et gestionnaires de secrets externes - pour les identifiants proxy.
  • Service mesh - si vous avez besoin d'un sidecar avec observabilité clé en main.
  • Politiques egress du CNI - pour le routage vers la passerelle.
  • Proxeon (proxeon.net) - infrastructure proxy pour une adresse sortante stable et un accès authentifié.

Supervision

  • Métriques des connexions sortantes via sidecar ou passerelle egress.
  • Vérifications synthétiques de la disponibilité des adresses externes depuis le cluster.
  • Alertes sur la croissance des codes 407 et des timeouts des requêtes sortantes.
  • Dashboard avec la répartition du trafic sortant par hôte de destination.

Cas et résultats

Examinons trois scénarios génériques montrant comment le choix du niveau influence le résultat.

Cas 1 : intégration de paiement et liste blanche IP

L'équipe a intégré une passerelle de paiement externe qui n'accepte les requêtes que depuis des adresses convenues. Ils ont d'abord essayé les variables d'environnement au niveau du conteneur - et se sont heurtés au fait qu'avec l'autoscaling, les pods se répartissaient sur de nouveaux nœuds avec de nouvelles adresses, et l'IP sortante restait de toute façon l'adresse du nœud, pas celle du proxy. Une partie des requêtes a commencé à être rejetée.

Solution : ils ont basculé le service de paiement sur egress via Proxeon avec une adresse sortante fixe. Le partenaire a ajouté une seule IP à la liste blanche. Les refus pour cause d'adresse inconnue ont disparu. Effet supplémentaire - un journal centralisé de tous les appels à la passerelle de paiement pour l'audit.

Cas 2 : rupture de la communication interservices après introduction du proxy

L'entreprise a ajouté HTTP_PROXY à tous les déploiements d'un coup via un template commun. Quelques minutes plus tard, les erreurs ont plu : les services ne se voyaient plus. Les appels internes partaient vers le proxy externe, qui ne pouvait pas les résoudre.

Le diagnostic a pris du temps, jusqu'à ce qu'on vérifie la résolution depuis l'intérieur du pod et qu'on voie que le nom .svc.cluster.local partait vers le proxy. Cause - NO_PROXY ne contenait que localhost. Ils ont composé la liste de référence complète avec Pod CIDR, Service CIDR et tous les suffixes DNS, l'ont mise dans un ConfigMap, connectée à tous les pods. La communication interne a été rétablie. Depuis, le NO_PROXY de référence est une partie obligatoire du template de déploiement.

Cas 3 : observabilité du trafic sortant via sidecar

Exigence de sécurité : savoir exactement où va chaque application à l'extérieur, avec un journal complet. Les applications étaient dans différents langages, une partie ne savait pas lire HTTP_PROXY. Modifier le code de dizaines de services - trop coûteux.

Ils ont introduit un sidecar proxy dans le template de pod. Les applications dirigent le trafic sortant vers un agent local, qui proxifie via Proxeon et écrit des métriques : hôte de destination, latence, code de réponse. Un dashboard des appels sortants par service est apparu. En plus, ils ont configuré des timeouts et des tentatives au niveau du sidecar, ce qui a réduit le nombre de défaillances en cascade lors d'indisponibilités brèves d'API externes. Le coût a été des ressources supplémentaires pour le sidecar, mais le gain en observabilité et en fiabilité l'a justifié.

FAQ : questions fréquentes des ingénieurs

Pourquoi le trafic vers un pod voisin passe-t-il par le proxy externe, alors que c'est visiblement une adresse interne ?

Parce que le client HTTP ne sait pas que l'adresse est interne. Il voit un nom ou une adresse et, si celle-ci n'est pas dans NO_PROXY, il dirige la requête vers le proxy selon les règles. Le client ne distingue pas lui-même est-ouest et nord-sud - c'est la liste d'exclusions qui le fait pour lui. Ajoutez les suffixes internes et les sous-réseaux dans NO_PROXY.

Faut-il aussi configurer un proxy pour télécharger les images ?

Oui, mais pas via les env du pod. Le téléchargement des images est effectué par le container runtime et kubelet au niveau du nœud. Le proxy pour le registre se configure dans la configuration du runtime ou dans l'unité systemd de kubelet. Les variables d'environnement du conteneur n'y influent pas du tout.

Qu'est-ce qui est le plus important pour une IP sortante stable - le sidecar ou la passerelle egress ?

La passerelle egress. Le sidecar proxifie le trafic d'un pod particulier, mais l'adresse sortante est de toute façon déterminée par l'endroit où la connexion va ensuite. Pour une adresse garantie fixe qu'acceptera un partenaire externe, il faut soit une passerelle egress, soit une sortie via un point externe à adresse constante, par exemple via Proxeon.

Pourquoi une application Java ignore-t-elle HTTP_PROXY ?

La JVM, pour des raisons historiques, ne lit pas les variables d'environnement pour le proxy. Elle utilise ses propres propriétés système http.proxyHost, https.proxyHost et les exclusions http.nonProxyHosts. Passez-les via JAVA_TOOL_OPTIONS. Et rappelez-vous : dans la JVM, le séparateur d'exclusions est la barre verticale, et les motifs utilisent l'astérisque, pas le CIDR.

Comment stocker en sécurité le mot de passe proxy ?

Dans un objet Secret avec accès restreint par RBAC et chiffrement etcd activé. Connectez les valeurs via secretKeyRef. N'écrivez pas le mot de passe en clair dans le manifeste, sinon il fuira dans Git et dans la sortie describe. Pour des exigences élevées, utilisez un gestionnaire de secrets externe via CSI.

J'ai changé une variable d'environnement, mais le comportement n'a pas changé - pourquoi ?

Les variables d'environnement sont figées au démarrage du conteneur. Tant que le pod n'est pas recréé, il fonctionne avec les anciennes valeurs. Mettez à jour le déploiement pour que le rollout des pods se fasse. Vérifiez aussi que l'application lit bien l'environnement et ne met pas en cache les paramètres à l'initialisation.

Comment savoir quel format NO_PROXY est nécessaire à ma bibliothèque ?

Empiriquement : configurez le proxy, appelez un nom interne depuis l'intérieur du pod et regardez si la requête est partie vers le proxy ou directement. Si elle est partie vers le proxy - le format n'est pas reconnu. Essayez la variante avec point initial et sans, ajoutez des adresses individuelles au lieu de CIDR, vérifiez la casse du nom de la variable. La documentation du client concerné est le meilleur repère.

Faut-il toujours utiliser un service mesh pour le proxying ?

Non. Un service mesh est un outil puissant avec observabilité et politiques, mais il entraîne des surcoûts notables et de la complexité opérationnelle. Si la tâche est seulement de diriger le trafic sortant via un proxy, un léger agent sidecar ou une passerelle egress coûteront moins cher. Introduisez le mesh quand vous avez vraiment besoin de toutes ses capacités.

Comment gérer le trafic qui n'est pas du tout HTTP ?

Les variables HTTP_PROXY ne fonctionnent que pour les clients HTTP et HTTPS qui les respectent. Pour des connexions TCP arbitraires, il faut une interception transparente au niveau du sidecar ou de la passerelle egress avec les règles de routage correspondantes. Ici, les variables d'environnement sont impuissantes.

Peut-on définir une politique proxy différente pour différents pods ?

Oui. Au niveau du conteneur - via des env différents dans différents déploiements. Au niveau egress - via des sélecteurs d'étiquettes dans les politiques egress, en dirigeant les pods marqués vers la sortie voulue. La combinaison donne de la flexibilité : une sortie de base via la passerelle plus des réglages individuels pour certains services.

Conclusion : dressons la checklist de déploiement

Nous avons parcouru le chemin depuis pourquoi la configuration « comme sur un serveur classique » ne marche pas dans un cluster, jusqu'aux trois niveaux d'intégration du proxy, aux subtilités de NO_PROXY, aux secrets, au sidecar, à la passerelle egress et au diagnostic. La conclusion principale : dans Kubernetes, le trafic sortant n'est pas une simple variable, mais un système en couches, où le plus important n'est pas comment activer le proxy, mais comment ne pas casser avec lui la communication interne.

Rassemblons la checklist finale de déploiement, à garder sous les yeux à chaque introduction.

Checklist de déploiement du proxy dans un cluster

  • Niveau défini. On a choisi conteneur, sidecar ou passerelle egress en connaissance de cause, pour une tâche précise.
  • NO_PROXY complet composé.

    À propos de l'auteur

    Andrey Kokh

    Andrey Kokh

    Leading Expert and Business Consultant

    Expérience professionnelle : Leading expert with 12 years of experience. Consults Forbes-listed companies, author of 3 books. Teaches at HSE and SKOLKOVO. His methodologies are used by hundreds of companies across Russia. RBC and Forbes expert on strategic development and digital transformation.
    Formation : Higher School of Economics. Faculty of Economics, Master's Program
    Expertise :
    Strategic Consulting Digital Transformation Change Management Business Strategy Innovation Management Organizational Development Lean Management Agile Transformation

    Partagez cet article :