Un ingeniero acostumbrado a los servidores tradicionales llega a Kubernetes y hace lo que siempre ha hecho: exporta HTTP_PROXY en el entorno, reinicia el proceso y espera que todo el tráfico de salida pase por el proxy. A veces funciona. Mucho más frecuentemente rompe la mitad del clúster, y la otra mitad sigue saliendo a internet directamente, sin pasar por el proxy. Y entonces empieza lo interesante: por qué algunos pods tomaron las variables y otros no, por qué los servicios dejaron de verse entre sí y por qué el healthcheck de repente pasó por un servidor proxy que no está en la lista de confianza.

Este artículo es un análisis detallado de cómo funciona realmente el tráfico de salida en un clúster y dónde se integra correctamente el proxy. No vamos a repetir la configuración básica de variables de entorno: eso ya lo sabes. La conversación será sobre las particularidades de Kubernetes, donde los enfoques habituales producen efectos inesperados. Todos los ejemplos se basan en fragmentos reales de manifiestos, y en el rol de infraestructura proxy actúa Proxeon (proxeon.net).

Fundamentos: por qué en el clúster todo es distinto

Empecemos por lo básico. En un servidor clásico el concepto de tráfico de salida es trivial: hay una interfaz de red, hay una tabla de enrutamiento, hay variables de entorno del sistema, y casi todo lo que ejecutas las hereda del shell padre. Exportaste la variable en el profile y la ve todo el proceso del usuario.

En Kubernetes ese punto único no existe. Aquí está el pod, la unidad mínima de despliegue, dentro de la cual vive uno o varios contenedores. El pod tiene su propio namespace de red, su propia dirección IP, su propio conjunto de variables de entorno definido por el manifiesto. El shell del que un proceso podría heredar algo simplemente no existe. Una variable de entorno aparece en el contenedor solo si la declaraste explícitamente en la especificación del pod o en la imagen.

Qué es el tráfico de salida de un pod

Cuando un contenedor se comunica con una dirección externa, el paquete recorre un largo camino. Primero sale del namespace de red del contenedor a través de una interfaz virtual. Luego llega al stack de red del nodo, donde se ocupa de él el plugin CNI, el componente responsable de la red del clúster. Después entran en juego las reglas de iptables o eBPF, el mecanismo SNAT (sustitución de la dirección de origen), tras lo cual el paquete sale por la interfaz de red del nodo hacia el mundo exterior.

Punto clave: desde la perspectiva del servicio externo, la petición no llega desde la dirección del pod, sino desde la dirección del nodo del clúster. El pod queda oculto tras la traducción de direcciones. Esto es lo primero que sorprende a los novatos cuando intentan configurar el acceso por lista blanca de IP: agregan la dirección del pod a la lista, pero el tráfico llega desde una dirección completamente distinta.

Dos tipos de tráfico de salida

Es importante distinguir desde el principio dos flujos radicalmente diferentes:

  • Este-oeste: tráfico entre servicios dentro del clúster. Un pod se comunica con otro a través de un servicio, ClusterIP o un nombre DNS del tipo my-service.namespace.svc.cluster.local.
  • Norte-sur: tráfico hacia afuera, hacia APIs externas, bases de datos, servicios de socios, almacenamiento de objetos.

El proxy casi siempre solo se necesita para el tráfico norte-sur. Y el error más común y doloroso consiste en que una configuración incorrecta del proxy captura accidentalmente también el tráfico este-oeste, rompiendo la comunicación interna. Por eso la lista de exclusiones aquí es más importante que en cualquier otro lugar. Pero de eso hablaremos un poco más adelante.

Inmersión profunda: tres niveles donde vive el proxy

Existen exactamente tres niveles arquitectónicos en los que se puede integrar el proxy en la ruta de salida de un pod. Cada uno resuelve la tarea a su manera, cada uno tiene su precio. Analicemos los tres con honestidad, con pros y contras.

Nivel 1: el propio contenedor

Es cuando la aplicación dentro del contenedor sabe del proxy por sí misma. Lee las variables de entorno HTTP_PROXY y HTTPS_PROXY, o tiene el proxy en su propia configuración y dirige a través de él sus peticiones HTTP. La lógica del proxy está integrada en la librería cliente de la propia aplicación.

Ventajas. Máxima simplicidad de implementación al inicio. No hay que agregar nada al clúster, ningún componente adicional. Control a nivel del pod concreto: sabes exactamente qué aplicación va a dónde.

Desventajas. Cada aplicación debe saber leer estas variables, y no todas pueden. La configuración se dispersa por decenas de manifiestos. Actualizar la dirección del proxy significa recorrer todos los despliegues. Es fácil olvidar un servicio, y ese saldrá directamente. No hay política centralizada.

Nivel 2: contenedor sidecar

Aquí, junto al contenedor principal, en el mismo pod se ejecuta un segundo: el sidecar. Intercepta el tráfico de salida y lo dirige a través del proxy. La aplicación puede no saber siquiera de la existencia del proxy: envía peticiones como siempre, y el sidecar las proxea de forma transparente. Así funcionan los service mesh como Istio y Linkerd, y con este mismo principio se puede instalar un agente proxy local ligero.

Ventajas. Transparencia para la aplicación. Política unificada mediante la plantilla del pod. Posibilidad de agregar, además del proxeado, observabilidad: métricas, trazado, reintentos, tiempos de espera. El sidecar está aislado en el pod y comparte su ciclo de vida.

Desventajas. Sobrecarga: ahora hay dos contenedores por pod, lo que significa más memoria y CPU. Se complica la depuración: aparece un eslabón extra en la cadena. El orden de arranque de los contenedores es importante: si la aplicación arranca antes que el sidecar, las primeras peticiones pueden fallar. En Kubernetes 1.28+ este problema se resuelve con native sidecar containers como contenedores init con política restartPolicy Always.

Nivel 3: gateway de egress del clúster

Es un nodo o pod dedicado a través del cual se dirige forzosamente todo el tráfico de salida del clúster. Los paquetes se enrutan al gateway de egress mediante el CNI, y este ya se comunica con el proxy externo o actúa él mismo como punto de salida con una IP de salida estable.

Ventajas. Punto único de control para todo el clúster. Dirección de salida estable y predecible, cómoda de agregar a las listas blancas de socios externos. La política se cambia en un solo lugar. Las aplicaciones no saben nada.

Desventajas. El gateway de egress se convierte en un punto crítico: si cae, se detiene todo el tráfico norte-sur. Requiere redundancia y monitoreo. La configuración es más compleja y necesita soporte del CNI. La granularidad es menor: es más difícil definir políticas distintas para aplicaciones distintas sin reglas adicionales.

Cómo elegir el nivel

Referencia práctica basada en la experiencia: un proyecto pequeño con un par de servicios que necesitan un proxy externo: nivel de contenedor. Un clúster mediano con decenas de servicios y el requisito de una política unificada: sidecar. Una infraestructura grande donde los socios externos exigen una IP de salida fija y auditoría de todo el tráfico: gateway de egress. A menudo se combinan los niveles: gateway de egress para el control básico más variables de entorno para el ajuste fino de pods concretos a través de Proxeon.

Variables de entorno en el manifiesto y el papel de NO_PROXY

Pasemos al detalle más subestimado. Las variables HTTP_PROXY, HTTPS_PROXY y NO_PROXY en el manifiesto se definen mediante el bloque env de la especificación del contenedor. Los nombres clásicos conviene duplicarlos en minúsculas, porque algunas librerías leen precisamente las variantes en minúsculas.

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"

Por qué sin un NO_PROXY correcto el clúster se desmorona

Aquí está la esencia del problema. En cuanto declaras HTTP_PROXY, el cliente HTTP de la aplicación empieza a dirigir a través del proxy absolutamente todas las peticiones, incluidas las dirigidas a servicios vecinos dentro del clúster. Pero los servicios internos son accesibles solo dentro de la red del clúster: el servidor proxy externo no puede alcanzarlos físicamente. Resultado: una petición a orders.default.svc.cluster.local se va al proxy externo, este intenta resolver ese nombre, no puede y devuelve un error. La comunicación interna se rompe al instante.

NO_PROXY es la lista de exclusiones, direcciones y dominios que deben eludir el proxy y enviarse directamente. En un servidor normal se ponen ahí localhost y, quizás, un par de subredes internas. En Kubernetes esta lista se vuelve críticamente importante, porque debe cubrir toda la topología interna del clúster. Si te olvidas de un sufijo, una parte del tráfico entre servicios se va por el proxy externo.

Qué debe estar obligatoriamente en el NO_PROXY del clúster

  • localhost y 127.0.0.1: peticiones dentro del propio pod.
  • Rango Pod CIDR: la subred de la que se asignan direcciones a los pods, por ejemplo 10.0.0.0/8 o el rango específico de tu clúster.
  • Rango Service CIDR: la subred de ClusterIP de los servicios, a menudo 10.96.0.0/12.
  • Sufijos DNS del clúster: .svc, .svc.cluster.local, .cluster.local. Precisamente ellos cubren todos los nombres DNS internos de los servicios.
  • kubernetes.default: el nombre del API server al que se comunican las aplicaciones y los agentes.
  • Metadatos de la nube: la dirección 169.254.169.254, si estás en un entorno de nube, para que las peticiones de metadatos no pasen por el proxy.

Particularidades de la sintaxis de NO_PROXY

Aquí se esconde toda una capa de sutilezas en las que tropiezan incluso ingenieros experimentados.

Primero, distintas librerías interpretan las entradas de manera diferente. Algunas consideran que .cluster.local con punto inicial significa sufijo y coincidirá con todos los subdominios. Otras exigen el formato cluster.local sin punto. Práctica: indica ambas variantes, con punto y sin punto, para cubrir el máximo de clientes.

Segundo, el soporte de la notación CIDR no es universal. La librería Go entiende 10.0.0.0/8, pero algunas versiones antiguas de clientes en otros lenguajes no, y necesitan direcciones individuales o rangos en otro formato. Verifica el comportamiento de tu stack concreto.

Tercero, los puertos. Si la entrada en NO_PROXY se indica sin puerto, normalmente se aplica a cualquier puerto del host. Pero algunos clientes comparan estrictamente con el puerto. Con puertos no estándar de servicios internos esto es importante verificarlo.

Insight de la práctica: nueve de cada diez incidentes de comunicación interna rota tras implementar un proxy son un NO_PROXY incompleto. Arma una lista de referencia para tu clúster una vez, ponla en un ConfigMap común y reutilízala en todos los despliegues. Esto ahorra decenas de horas de depuración.

Qué NO toma las variables de entorno

La creencia ingenua de que "puse HTTP_PROXY, entonces todo el tráfico pasó por el proxy" en el clúster es doblemente errónea. Hay toda una clase de componentes que simplemente ignoran estas variables. Hay que conocerlos de antemano.

kubelet y componentes del sistema

Las variables de entorno del contenedor solo son visibles para los procesos dentro de ese contenedor. kubelet, el agente del nodo que descarga imágenes, arranca contenedores y se comunica con el API server, vive a nivel del nodo, no del pod. No lee el env del manifiesto. Si necesitas que kubelet descargue imágenes a través del proxy, la configuración se hace a nivel del servicio systemd de kubelet o de la configuración del container runtime, no en la especificación del 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"

Lo mismo aplica al container runtime, containerd o CRI-O. La descarga de imágenes ocurre a través de su propio contexto de red. La configuración del proxy para el registro de imágenes se define en el config del runtime, y es una historia aparte de los pods.

Imágenes con configuración propia

Muchas imágenes populares tienen configuraciones de red integradas que sobrescriben las variables de entorno. Por ejemplo, los gestores de paquetes, servidores web o herramientas proxy dentro de la imagen pueden leer su propio archivo de configuración en lugar del entorno. Si la aplicación dentro del contenedor usa, digamos, un archivo de configuración con parámetros de conexión explícitos, simplemente no verá tus variables de entorno.

Categoría aparte: aplicaciones donde el cliente de red se inicializa antes de leer el entorno o cachea la configuración al arrancar. Cambiar la variable sin reiniciar el proceso aquí no tendrá efecto.

SDK y runtimes de lenguaje concretos

Este es el grupo más traicionero. El respeto a HTTP_PROXY es una convención, no un estándar. Algunos la cumplen, otros no.

  • Go. El http.Client estándar a través de ProxyFromEnvironment respeta las variables. Pero si el código crea un transporte con Proxy nil explícito, las variables se ignoran.
  • Python. La librería requests lee el entorno por defecto. En cambio, los sockets de bajo nivel, algunos clientes gRPC y librerías asíncronas no.
  • Java. La JVM usa sus propias propiedades del sistema http.proxyHost y https.proxyHost, y por defecto no lee las variables de entorno en absoluto. Hay que pasarlas a través de JAVA_TOOL_OPTIONS.
  • Node.js. El módulo http integrado no respeta las variables de entorno. Se necesitan agentes de terceros que las lean.
  • gRPC. Algunas implementaciones leen la variable especial grpc_proxy, en lugar de las estándar.

La conclusión es simple: no puedes asumir que porque la variable está declarada, todo el tráfico pasará por ella. Verifica cada runtime por separado. Para la JVM, por ejemplo, el manifiesto se ve así:

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"

Fíjate: en la JVM el separador de exclusiones es la barra vertical, no la coma, y los patrones usan asterisco. Otra trampa para quienes copian NO_PROXY mecánicamente.

Secretos: credenciales del proxy mediante Secret, no en el manifiesto

Si tu proxy requiere autenticación, surge la cuestión de almacenar el usuario y la contraseña. La tentación es grande: escribirlos directamente en la URL dentro del valor env. Eso no se debe hacer, y he aquí por qué.

Los manifiestos de despliegue casi siempre están en el sistema de control de versiones. Una contraseña en texto plano en el env es una filtración de credenciales al historial de Git, accesible a todos los que tengan acceso al repositorio. Además, los valores del env son visibles para cualquiera que pueda ejecutar kubectl describe pod. Es una violación directa del principio de menor privilegio.

El camino correcto es el objeto Secret. Almacena datos sensibles por separado, con posibilidad de restringir el acceso mediante RBAC y activar el cifrado en el almacenamiento etcd.

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

Luego conectamos los valores del secreto a las variables de entorno del contenedor mediante secretKeyRef, y la propia URL del proxy la armamos de forma que las credenciales no queden expuestas en el propio manifiesto:

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 sustituye los valores de variables declaradas previamente mediante la sintaxis con dólar y paréntesis. Esto permite armar la URL con credenciales en tiempo de ejecución, sin poner la contraseña directamente en el texto del manifiesto. Ten en cuenta: el valor ensamblado de HTTPS_PROXY de todos modos será visible en el env del contenedor en ejecución, por lo que además limita quién puede ejecutar exec y describe para esos pods.

Buenas prácticas para trabajar con secretos

  • Activa el cifrado de etcd at rest; de lo contrario, los secretos se almacenan solo en base64, lo que no es protección.
  • Restringe el acceso a los secretos mediante RBAC: el pod debe ver solo su propio secreto.
  • Rota las credenciales del proxy con regularidad y automatiza el reinicio de los pods tras la rotación.
  • Considera gestores externos de secretos conectados vía driver CSI, para que las credenciales no lleguen a etcd en absoluto.
  • Nunca registres en logs la URL del proxy ensamblada: la contraseña se filtrará al sistema de recolección de logs.

Enfoque sidecar: cuándo se justifica y qué aporta

Ya mencionamos el sidecar como uno de los tres niveles. Ahora analicémoslo como estrategia propia con más detalle, porque es la herramienta más flexible, si estás dispuesto a pagar por ella con recursos.

Cuándo se justifica el sidecar

  • La aplicación no sabe leer HTTP_PROXY y reescribir su código no es posible o es muy costoso.
  • Se necesita una política unificada de proxeado sin modificar cada aplicación.
  • Se requiere intercepción transparente del tráfico, incluidos protocolos no HTTP.
  • Se necesita observabilidad: métricas de conexiones de salida, trazado, auditoría.
  • Se requieren políticas de reintentos, tiempos de espera, corte de circuito a nivel de conexión.

Qué aporta el sidecar además del proxeado

Aquí está el verdadero valor. El sidecar no es solo un redirigidor de paquetes. Bien configurado, se convierte en un punto de control y observación de todo el tráfico de salida del pod.

  • Métricas. Cuántas peticiones salieron, a qué hosts, con qué latencia, cuántos errores. Sin sidecar, estos datos habría que recolectarlos en cada aplicación por separado.
  • Trazado. Trazas distribuidas de las llamadas de salida, vinculadas a las peticiones entrantes.
  • Políticas de fiabilidad. Reintentos automáticos de peticiones idempotentes, tiempos de espera, limitación de conexiones concurrentes.
  • TLS unificado. El sidecar puede terminar y establecer conexiones seguras de forma centralizada.
  • Auditoría y cumplimiento. Registro completo de a dónde fue exactamente la aplicación: insustituible para el compliance.

Ejemplo de pod con sidecar proxy

Abajo hay una plantilla simplificada, donde el contenedor principal dirige las peticiones HTTP de salida a un sidecar local, y este las proxea más allá a través de Proxeon. Para la aplicación, la dirección del proxy es localhost, lo que automáticamente excluye el tráfico entre servicios del proxeado externo, si la aplicación se comunica con los vecinos directamente.

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

Native sidecar y orden de arranque

El problema clásico del sidecar es la carrera en el arranque. Si la aplicación principal arranca y hace su primera petición de salida antes de que el sidecar esté listo para aceptar conexiones, la petición falla. A partir de Kubernetes 1.28 apareció el soporte de contenedores sidecar nativos: se declaran en el bloque initContainers con restartPolicy Always y arrancan garantizadamente y permanecen vivos hasta el contenedor principal. Esto resuelve el problema de la carrera de forma limpia, sin soluciones improvisadas como retrasos y reintentos al inicio.

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

Gateway de egress: dirección de salida estable para el clúster

El tercer nivel merece una conversación aparte, porque precisamente resuelve la tarea por la que la mayoría de las veces se llega al proxy en el clúster: la IP de salida predecible.

Para qué se necesita una IP de salida fija

Los socios externos, las pasarelas de pago, los proveedores de datos no rara vez trabajan con lista blanca de direcciones. Dicen: aceptamos peticiones solo desde estas IP. En un clúster normal, la dirección de salida del pod es la dirección del nodo en el que el pod resultó estar en ese momento. Los nodos son muchos, se escalan, se reemplazan, se agregan con el autoscaling. La dirección es impredecible. El socio no puede incluir en la lista blanca todo el pool de nodos, que además cambia.

El gateway de egress resuelve esto: todo el tráfico de salida se reúne en un punto con una dirección fija. El socio incluye en la lista blanca una o dos IP estables y todo funciona. Al usar Proxeon como punto de salida obtienes una dirección externa estable, que entregas a los socios una sola vez.

Cómo se enruta el tráfico al gateway de egress

El mecanismo depende del CNI. Algunos plugins CNI soportan el objeto de política de egress, donde describes: el tráfico de pods con tales etiquetas, que va hacia afuera, debe salir a través de tal nodo o dirección. El plugin configura las reglas correspondientes de SNAT y enrutamiento automáticamente. Conceptualmente se ve así:

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 sintaxis exacta varía entre CNI, así que consulta la documentación de tu plugin. La idea es invariable: marcar los pods cuyo tráfico se dirige a través del gateway y definir una dirección de salida estable.

Fiabilidad del gateway de egress

Como el gateway es un punto único, también es un punto único de falla. Reglas de supervivencia:

  • Redunda: mínimo dos nodos gateway con conmutación automática.
  • Monitorea la disponibilidad de salida con una petición sintética hacia afuera cada minuto.
  • Vigila el ancho de banda: todo el tráfico norte-sur fluye por el gateway, el cuello de botella afectará todo.
  • Separa políticas: el tráfico crítico y el de fondo conviene distribuirlos por rutas distintas, para que la carga de fondo no estorbe a lo importante.

Diagnóstico del tráfico de salida en el clúster

Cuando algo va mal, y va a ir mal, se necesita un arsenal de verificaciones. Armemos un conjunto práctico de comandos y enfoques.

Paso 1: conocer la IP de salida real desde el pod

Lo primero que verificamos: desde qué dirección el pod es visto por el mundo exterior. Lanzamos un contenedor de depuración efímero o hacemos exec en un pod existente y nos comunicamos con un servicio que devuelve tu dirección pública.

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 primera petición muestra la dirección sin proxy, la segunda, a través del proxy explícitamente. Si ambas son iguales y esperabas que fueran distintas, el proxy no se está aplicando. Si la segunda devuelve la dirección esperada de Proxeon, el proxy funciona y el problema está en la configuración de la aplicación.

Paso 2: verificar si la aplicación ve las variables

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

Si la salida está vacía, las variables no llegaron al contenedor. Revisa el manifiesto y que el pod haya sido recreado tras el cambio. Recuerda: cambiar el env requiere recrear el pod, no se toma en caliente.

Paso 3: verificar la resolución DNS desde dentro

Muchos errores se enmascaran como problema de proxy, cuando en realidad es DNS. Verificamos la resolución de un nombre interno y uno externo:

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

Si el nombre interno no resuelve, el problema está en CoreDNS o en que la petición se fue al proxy externo por falta del sufijo en NO_PROXY. Esto es confirmación directa de que la lista de exclusiones está incompleta.

Paso 4: dónde ver los rechazos

  • Logs de la aplicación. Busca errores de conexión, tiempos de espera, fallo de autenticación del proxy (normalmente código 407).
  • Logs del sidecar. Si lo hay, ahí se ven qué peticiones aceptó y a dónde las dirigió.
  • Métricas del gateway de egress. Aumento de rechazos y latencias en el gateway.
  • Eventos del pod. kubectl describe pod mostrará problemas de arranque, incluida la carrera del sidecar.
  • NetworkPolicy. Verifica si la política de red está bloqueando el tráfico de salida: causa frecuente de rechazos silenciosos.

Firmas típicas de problemas

  • Código 407 del proxy: credenciales incorrectas o ausentes. Revisa el Secret.
  • Tiempo de espera agotado al comunicarse con un servicio interno tras activar el proxy: NO_PROXY incompleto.
  • Distintas IP de salida en peticiones repetidas: el tráfico no pasa por el gateway de egress, funciona el SNAT normal del nodo.
  • Las primeras peticiones tras el arranque fallan, después todo funciona: carrera del sidecar, pasa a native sidecar.
  • La aplicación Java ignora el proxy: olvidaste JAVA_TOOL_OPTIONS y las propiedades del sistema.

Errores típicos que conviene evitar

Reunamos en un solo lugar los tropiezos más frecuentes. Revisa esta lista antes, no después del incidente.

NO_PROXY incompleto

El líder absoluto. Olvidaste el Service CIDR, olvidaste el sufijo .svc, no consideraste la dirección de metadatos de la nube, y obtuviste una cascada de fallos misteriosos. Parte siempre de una lista de referencia completa para tu clúster.

Contraseña del proxy en texto plano en el manifiesto

Filtración al Git y a la salida de describe. Solo Secret, solo acceso restringido.

Asumir que todos los runtimes respetan las variables

La JVM, Node.js y parte de los clientes gRPC ignoran HTTP_PROXY. Verifica cada stack por separado y configúralo de forma nativa.

Ignorar kubelet y el runtime

La descarga de imágenes a través del proxy se configura a nivel del nodo, no del pod. No te sorprendas si las imágenes no se descargan cuando configuraste solo el env del manifiesto.

Carrera en el arranque del sidecar

La aplicación arranca antes que el proxy y tumba las primeras peticiones. Solución: native sidecar containers.

Gateway de egress sin redundancia

Un punto único de falla sin duplicación tumba todo el tráfico de salida. Redunda y monitorea.

Mezcla de formatos CIDR

Indicaste 10.0.0.0/8 para un cliente que no entiende CIDR. Verifica qué formato acepta tu librería y ofrece alternativa.

Falta de recreación de pods tras el cambio de env

Cambiaste la variable en el despliegue, pero los pods viejos siguen trabajando con los valores antiguos hasta el reinicio. Asegura el rollout.

Olvidar el minúsculas de las variables

Algunas librerías leen solo http_proxy en minúsculas. Duplica los nombres en ambos registros.

Herramientas y recursos

Qué tener a mano para trabajar con el tráfico de salida en el clúster.

Diagnóstico

  • kubectl exec y kubectl debug: la base para verificaciones desde dentro del pod.
  • Contenedores de depuración efímeros: permiten conectar un conjunto de herramientas de red a un pod en funcionamiento sin reconstruir la imagen.
  • Imagen con utilidades de red: curl, dig, nslookup, traceroute, reunidos en un contenedor para verificaciones rápidas.
  • Servicio de retorno de IP pública: verificación de la dirección de salida real.

Infraestructura

  • ConfigMap con NO_PROXY de referencia: fuente única de verdad para todos los despliegues.
  • Secret y gestores externos de secretos: para las credenciales del proxy.
  • Service mesh: si necesitas sidecar con observabilidad lista para usar.
  • Políticas de egress del CNI: para el enrutamiento al gateway.
  • Proxeon (proxeon.net): infraestructura proxy para dirección de salida estable y acceso autenticado.

Monitoreo

  • Métricas de conexiones de salida desde el sidecar o el gateway de egress.
  • Verificaciones sintéticas de disponibilidad de direcciones externas desde el clúster.
  • Alertas ante aumento de códigos 407 y tiempos de espera de peticiones de salida.
  • Dashboard con distribución del tráfico de salida por hosts de destino.

Casos y resultados

Analicemos tres escenarios generalizados que muestran cómo la elección del nivel influye en el resultado.

Caso 1: integración de pagos y lista blanca de IP

El equipo integró una pasarela de pago externa que acepta peticiones solo desde direcciones acordadas. Primero probaron variables de entorno a nivel de contenedor y se toparon con que durante el autoscaling los pods se dispersaban por nodos nuevos con direcciones nuevas, y la IP de salida seguía siendo la del nodo, no la del proxy. Algunas peticiones empezaron a ser rechazadas.

Solución: migraron el servicio de pagos a egress a través de Proxeon con dirección de salida fija. El socio incluyó una IP en la lista blanca. Los rechazos por dirección desconocida desaparecieron. Efecto adicional: registro centralizado de todos los accesos a la pasarela de pago para auditoría.

Caso 2: ruptura de la comunicación entre servicios tras implementar el proxy

La empresa agregó HTTP_PROXY a todos los despliegues de golpe mediante una plantilla común. A los pocos minutos empezaron los errores: los servicios dejaron de verse entre sí. Las llamadas internas se iban al proxy externo, que no podía resolverlas.

El diagnóstico tomó tiempo, hasta que verificaron la resolución desde dentro del pod y vieron que el nombre .svc.cluster.local se iba al proxy. Causa: NO_PROXY contenía solo localhost. Armaron una lista de referencia completa con Pod CIDR, Service CIDR y todos los sufijos DNS, la pusieron en un ConfigMap y la conectaron en todos los pods. La comunicación interna se restableció. Desde entonces, el NO_PROXY de referencia es parte obligatoria de la plantilla de despliegue.

Caso 3: observabilidad del tráfico de salida mediante sidecar

Requisito de seguridad: saber exactamente a dónde sale cada aplicación, con registro completo. Las aplicaciones estaban en distintos lenguajes, algunas no sabían leer HTTP_PROXY. Modificar el código de decenas de servicios era demasiado costoso.

Implementaron un sidecar proxy en la plantilla del pod. Las aplicaciones dirigen el tráfico de salida a un agente local, este proxea a través de Proxeon y escribe métricas: host de destino, latencia, código de respuesta. Apareció un dashboard de accesos de salida por cada servicio. Además, configuraron tiempos de espera y reintentos a nivel del sidecar, lo que redujo el número de fallos en cascada ante la indisponibilidad temporal de APIs externas. El costo fueron recursos adicionales para el sidecar, pero la ganancia en observabilidad y fiabilidad lo justificó.

FAQ: preguntas frecuentes de ingenieros

Por qué el tráfico hacia un pod vecino pasa por el proxy externo, si es obviamente una dirección interna

Porque el cliente HTTP no sabe que la dirección es interna. Ve el nombre o la dirección y, si no está en NO_PROXY, dirige la petición al proxy según las reglas. El cliente no distingue entre este-oeste y norte-sur por sí mismo: la lista de exclusiones lo hace por él. Agrega los sufijos y subredes internos a NO_PROXY.

Hay que configurar el proxy también para la descarga de imágenes

Sí, pero no a través del env del pod. La descarga de imágenes la realizan el container runtime y kubelet a nivel del nodo. El proxy para el registro se configura en la configuración del runtime o en la unidad systemd de kubelet. Las variables de entorno del contenedor no influyen en esto en absoluto.

Qué es más importante para una IP de salida estable: sidecar o gateway de egress

El gateway de egress. El sidecar proxea el tráfico de un pod concreto, pero la dirección de salida sigue determinada por a dónde va la conexión después. Para una dirección garantizadamente fija que acepte un socio externo, se necesita un gateway de egress o una salida a través de un punto externo con dirección permanente, por ejemplo a través de Proxeon.

Por qué la aplicación Java ignora HTTP_PROXY

La JVM, por razones históricas, no lee las variables de entorno para el proxy. Usa sus propias propiedades del sistema http.proxyHost, https.proxyHost y las exclusiones http.nonProxyHosts. Pásalas a través de JAVA_TOOL_OPTIONS. Y recuerda: el separador de exclusiones en la JVM es la barra vertical, y los patrones usan asterisco, no CIDR.

Cómo almacenar de forma segura la contraseña del proxy

En un objeto Secret con acceso restringido por RBAC y cifrado de etcd activado. Conecta los valores mediante secretKeyRef. No escribas la contraseña en texto plano en el manifiesto, o se filtrará al Git y a la salida de describe. Para requisitos elevados, usa un gestor externo de secretos vía CSI.

Cambié la variable de entorno y el comportamiento no cambió, por qué

Las variables de entorno se fijan al arrancar el contenedor. Mientras el pod no se recree, trabaja con los valores antiguos. Actualiza el despliegue para que se realice el rollout de los pods. Además, verifica que la aplicación realmente lea el entorno y no cachee la configuración en la inicialización.

Cómo saber qué formato de NO_PROXY necesita exactamente mi librería

Empíricamente: configura el proxy, comunícate con un nombre interno desde dentro del pod y observa si la petición se fue al proxy o directamente. Si se fue al proxy, el formato no fue reconocido. Prueba la variante con punto inicial y sin él, agrega direcciones individuales en lugar de CIDR, verifica el registro del nombre de la variable. La documentación del cliente concreto es la mejor referencia.

Vale la pena usar siempre service mesh por el proxeado

No. Service mesh es una herramienta potente con observabilidad y políticas, pero conlleva sobrecarga notable y complejidad operativa. Si la tarea es solo dirigir el tráfico de salida a través del proxy, un agente sidecar ligero o un gateway de egress saldrán más económicos. Implementa el mesh cuando realmente necesites todas sus capacidades.

Cómo manejar el tráfico que no es HTTP en absoluto

Las variables HTTP_PROXY funcionan solo para clientes HTTP y HTTPS que las respeten. Para conexiones TCP arbitrarias se necesita intercepción transparente a nivel de sidecar o gateway de egress con las reglas de enrutamiento correspondientes. Aquí las variables de entorno son impotentes.

Se puede definir una política de proxy distinta para pods distintos

Sí. A nivel de contenedor, mediante distintos env en distintos despliegues. A nivel de egress, mediante selectores de etiquetas en las políticas de egress, dirigiendo los pods marcados a la salida adecuada. La combinación da flexibilidad: salida básica por el gateway más configuraciones individuales para servicios concretos.

Conclusión: armemos el checklist de despliegue

Hemos recorrido el camino desde por qué la configuración "como en un servidor normal" no funciona en el clúster, hasta los tres niveles de integración del proxy, las sutilezas de NO_PROXY, los secretos, el sidecar, el gateway de egress y el diagnóstico. La conclusión principal: en Kubernetes el tráfico de salida no es una sola variable, sino un sistema por capas, donde lo más importante no es cómo activar el proxy, sino cómo no romper con él la comunicación interna.

Reunamos el checklist final de despliegue, que conviene tener a la vista en cada implementación.

Checklist de despliegue del proxy en el clúster

  • Definimos el nivel. Elegimos contenedor, sidecar o gateway de egress de forma consciente, para la tarea concreta.
  • Armamos el NO_PROXY completo.

    Sobre el autor

    Andrey Kokh

    Andrey Kokh

    Leading Expert and Business Consultant

    Experiencia laboral: 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.
    Formación académica: Higher School of Economics. Faculty of Economics, Master's Program
    Especialización:
    Strategic Consulting Digital Transformation Change Management Business Strategy Innovation Management Organizational Development Lean Management Agile Transformation

    Comparte el artículo: