Tráfico de salida en Kubernetes y proxies: env del pod, sidecar, gateway de egreso y el rol de NO_PROXY
Contenido del artículo
- Introducción: por qué configurar «como en un servidor normal» no funciona en un clúster
- Fundamentos: cómo está físicamente estructurado el tráfico de salida de un pod
- Tres niveles donde se puede colocar un proxy: contenedor, sidecar, gateway de egreso
- Variables de entorno en el manifiesto: http_proxy, https_proxy, no_proxy y por qué sin un no_proxy correcto se rompe el clúster
- Qué no recoge estas variables: kubelet, imágenes con su propia configuración, sdk
- Secretos: credenciales del proxy vía secret, no en el manifiesto
- Enfoque de sidecar: cuándo está justificado y qué aporta además del proxy
- Gateway de egreso del clúster: punto único de salida
- Diagnóstico en el clúster: cómo verificar la ip de salida desde un pod y dónde mirar las fallas
- Errores típicos
- Herramientas y recursos
- Casos y resultados
- Checklist de despliegue
- Preguntas frecuentes
- Conclusión
Introducción: por qué configurar «como en un servidor normal» no funciona en un clúster
En una máquina virtual común, configurar un proxy toma cinco minutos. Escribes HTTP_PROXY en el perfil, agregas un par de direcciones a las excepciones, reinicias el servicio y te vas a tomar un café. En Kubernetes, ese mismo escenario termina de otra manera: la mitad de los servicios de repente responde 502, los health-checks empiezan a tumbar pods, y el servicio de pagos se queja de que no puede alcanzar al pod de al lado. Y aun así, las peticiones externas por las que hiciste todo esto, funcionan.
¿Por qué pasa esto? Porque en un clúster, un mismo proceso vive simultáneamente en dos mundos de comunicación de red. El primero es el internet externo: APIs de socios, marketplaces, fuentes de datos para scraping, servicios públicos. El segundo es la red interna del clúster: otros servicios vía ClusterIP, el API server de Kubernetes, DNS, bases de datos, colas, métricas. En un servidor común, el segundo mundo está casi vacío, así que una variable global de proxy no rompe nada. En un clúster, el segundo mundo es enorme, y cualquier proxy que reciba una petición a una dirección como payments.default.svc.cluster.local simplemente no podrá resolverla. El proxy está afuera y no sabe nada de tu DNS interno.
Sumemos las particularidades del propio Kubernetes. Las variables de entorno del pod no afectan al kubelet ni al container runtime. Las imágenes suelen traer su propia configuración que ignora el entorno. Los SDK de distintos lenguajes entienden de forma diferente qué es una excepción de proxy. Y todo esto mientras que la IP de salida del pod no es visible para el mundo externo: hacia afuera va la dirección de la nodo o del gateway NAT del cloud.
En esta guía vamos a desglosar el tráfico de salida de Kubernetes «por capas»: cómo el paquete sale físicamente del pod, dónde exactamente se puede insertar un proxy, por qué NO_PROXY en un clúster pasa de ser una formalidad a un elemento arquitectónico crítico, cómo pasar credenciales de forma segura, cuándo el sidecar está justificado y cuándo es redundante, y cómo depurar fallas sin adivinar. No vamos a repetir la sintaxis básica de las variables de entorno: aquí solo la especificidad del clúster. Todos los fragmentos de manifiestos son funcionales, puedes tomarlos como base.
Fundamentos: cómo está físicamente estructurado el tráfico de salida de un pod
Antes de instalar un proxy, conviene entender por qué ruta viaja el paquete sin él. De lo contrario, el diagnóstico se convierte en adivinanza.
Del contenedor a la nodo
Cada pod recibe su propia IP del pod CIDR, por ejemplo 10.244.3.17. Dentro del pod, todos los contenedores comparten el mismo espacio de nombres de red: una IP, un conjunto de interfaces, localhost común. Este es el hecho clave para el enfoque de sidecar, al que volveremos. El paquete del contenedor sale por la interfaz virtual del pod hacia la nodo, donde lo maneja el plugin CNI. Después hay dos escenarios.
- Destino dentro del clúster. Si la aplicación se comunica con el ClusterIP de un servicio (por ejemplo 10.96.14.2), kube-proxy o el dataplane eBPF del CNI sustituye la dirección de destino por la IP del pod backend concreto. El tráfico va a la nodo correspondiente por el overlay o directamente por L3 y nunca abandona el clúster.
- Destino externo. Si la dirección no pertenece ni al pod CIDR ni al service CIDR, el paquete sale por la ruta por defecto de la nodo. A la salida de la nodo casi siempre actúa SNAT: la dirección de origen 10.244.3.17 se reemplaza por la IP de la nodo. En clouds, por encima de esto puede funcionar un gateway NAT, y entonces el mundo externo ya ve su dirección.
De aquí la primera conclusión práctica: un servicio externo nunca ve la IP del pod. Ve la IP de la nodo o el NAT del cloud. Si tienes autoescalado de nodos, las IP de salida cambian junto con ellas. Precisamente por eso la autorización en el proxy por dirección IP sin una nodo de egreso dedicada funciona mal en un clúster, de esto hablamos más abajo.
DNS dentro del clúster
Los pods usan el DNS del clúster (normalmente CoreDNS). En el archivo resolv.conf del pod están definidos los dominios de búsqueda: namespace.svc.cluster.local, svc.cluster.local, cluster.local. Gracias a esto, el nombre payments se resuelve a payments.default.svc.cluster.local. Un proxy externo no tiene ni idea de este esquema. Recibe una petición al host payments, intenta resolverla por el DNS público y devuelve un error. Esta es la razón fundamental por la que NO_PROXY en un clúster es más importante que en cualquier servidor aislado.
Qué significa realmente «poner un proxy» en un clúster
Bajo esta frase los ingenieros entienden tres cosas distintas, y confundirlas es peligroso:
- Hacer que una aplicación concreta salga a través de un servidor proxy (por ejemplo, mediante direcciones dedicadas de Proxeon), sin tocar los demás servicios.
- Garantizar una IP de salida estable y predecible para todo el clúster o para parte de él: tarea de la red corporativa, cumplimiento normativo, auditoría.
- Centralizar la gestión del tráfico externo: logging, límites, punto único de salida.
La primera tarea se resuelve a nivel del pod. La segunda y la tercera, a nivel del clúster. Mezclar niveles genera la mayor parte de los problemas de los que vamos a hablar.
Tres niveles donde se puede colocar un proxy: contenedor, sidecar, gateway de egreso
Imagina el clúster como un edificio de oficinas. Puedes darle un proxy a cada empleado en persona (contenedor), poner un asistente junto a cada puesto de trabajo (sidecar) o instalar un único control de acceso a la salida del edificio (gateway de egreso). Cada esquema tiene su costo.
Nivel 1: el propio contenedor
La aplicación recibe la dirección del proxy mediante variables de entorno, un archivo de configuración o flags de arranque, y ella misma dirige el tráfico a través de él. Esta es la forma más simple y más común.
Ventajas:
- Cero infraestructura adicional: cambios solo en el manifiesto del Deployment.
- Granularidad: un servicio necesita el proxy, el de al lado no.
- El proxy «entiende» el protocolo: un proxy HTTP ve el host de destino, puede registrar, aplicar reglas.
Desventajas:
- Funciona solo si el código o la librería respetan la configuración. Java, parte de los clientes Node.js, algunas implementaciones de gRPC requieren configuración aparte.
- Dependencia total de un NO_PROXY correcto: un error y la comunicación intraclúster está rota.
- Las credenciales del proxy terminan en el entorno del proceso y quedan accesibles a cualquiera que pueda hacer exec en el contenedor.
- Rotar el proxy o cambiar la dirección requiere recrear pods.
Nivel 2: contenedor sidecar
Junto a la aplicación, en el mismo pod, se ejecuta un segundo contenedor: un proxy de reenvío local. La aplicación envía el tráfico a localhost:3128 (o este se intercepta con reglas de iptables), y el sidecar decide hacia dónde y cómo enviarlo: por cuál de los proxies upstream, con qué autorización, con qué timeout.
Ventajas:
- La aplicación no conoce las credenciales externas: solo viven en el sidecar.
- Punto único de lógica: rotación, reintentos, elección del pool de direcciones, métricas, sin tocar el código de la aplicación.
- El modo transparente vía iptables funciona incluso con librerías que ignoran HTTP_PROXY.
- Actualizar la lógica de proxy es actualizar una sola imagen de sidecar.
Desventajas:
- Un contenedor adicional en cada pod: memoria, CPU, otro objeto que monitorear.
- La intercepción transparente requiere NET_ADMIN en el contenedor init, lo que no siempre es admisible por las políticas de seguridad.
- Más difícil de depurar: dos lugares donde puede romperse.
Nivel 3: gateway de egreso del clúster
Un conjunto dedicado de pods (o nodos) se convierte en el único punto de salida. Los demás pods o bien van explícitamente a través del Service interno del gateway, o bien son forzados a hacerlo por políticas de red y enrutamiento. El gateway hacia afuera va directo o a través de un proxy externo.
Ventajas:
- Una única IP de salida estable (o un conjunto pequeño) para todo el clúster. Ideal para autorización por IP del lado del proveedor de proxy y para listas blancas de socios.
- Logs, límites y auditoría centralizados.
- Las aplicaciones no guardan ninguna credencial: solo ven la dirección interna del gateway.
- La rotación de direcciones externas ocurre en un solo lugar.
Desventajas:
- Punto único de falla: el gateway debe ser tolerante a fallos (varias réplicas, PodDisruptionBudget, anti-affinity).
- Un salto de red adicional y un posible cuello de botella en el ancho de banda.
- Requiere NetworkPolicy bien pensadas, de lo contrario queda un «agujero» de salida directa que evita el gateway.
- Granularidad más compleja: si distintos equipos necesitan distintas direcciones externas, habrá que levantar varios gateways.
Cómo elegir
Regla práctica que usamos al diseñar:
- Una o dos aplicaciones, código bajo control, clientes HTTP estándar: nivel de contenedor.
- Muchos servicios con stacks distintos, requisitos de rotación y reintentos, reticencia a repartir credenciales: sidecar.
- Requisitos de cumplimiento, autorización por IP, auditoría de todo el tráfico externo, decenas de equipos: gateway de egreso, a menudo combinado con sidecar para lógica fina.
Ojo: los niveles no se excluyen entre sí. El esquema maduro típico es un gateway de egreso como frontera obligatoria más variables de entorno o sidecar donde se necesita especificidad.
Variables de entorno en el manifiesto: HTTP_PROXY, HTTPS_PROXY, NO_PROXY y por qué sin un NO_PROXY correcto se rompe el clúster
La sintaxis de las variables ya la conoces. Nos interesa qué pasa con ellas precisamente en el pod y por qué un valor estándar de NO_PROXY como «localhost,127.0.0.1» es catastróficamente insuficiente aquí.
Anatomía de la falla
Desglosemos la cadena. El servicio orders llama a payments en la dirección http://payments:8080/charge. En el entorno está definido HTTP_PROXY. El cliente HTTP mira el host de destino, no lo encuentra en NO_PROXY y en lugar de abrir una conexión TCP con payments, abre una conexión con el proxy y le envía la petición GET http://payments:8080/charge. El proxy intenta resolver el nombre payments con su propio resolver. El nombre no existe. El proxy devuelve 502 o 503. Tu servicio recibe un error del proxy, pero en los logs parece un error de payments. El equipo va a arreglar payments, que está perfectamente sano.
Hay una segunda variante, más astuta. Si el servicio se comunica directamente con el ClusterIP en una dirección como 10.96.14.2, el proxy recibirá una petición a una dirección privada. Algunos proxies descartan esas peticiones de inmediato, otros intentan conectarse a 10.96.14.2 en su propia red. En el peor caso, allí hay otro host con esa dirección.
La tercera variante atañe al propio Kubernetes. Las librerías cliente que hablan con el API server (operadores, controladores, aplicaciones con leader election) usan la dirección kubernetes.default.svc o la IP de la variable KUBERNETES_SERVICE_HOST. Sin exclusión, también irán por el proxy y perderán la conexión con el control plane.
Qué debe estar en NO_PROXY para un pod
Conjunto mínimo que recomendamos como punto de partida. Sustituye tus CIDR: puedes obtenerlos de la configuración del clúster o de las anotaciones de los servicios.
- localhost, 127.0.0.1, ::1: direcciones locales, incluida la comunicación con el sidecar.
- .svc, .svc.cluster.local, .cluster.local: todos los nombres de servicio. El punto inicial significa «cualquier subdominio».
- kubernetes.default.svc, kubernetes.default.svc.cluster.local: explícitamente, para los clientes del API.
- Service CIDR, por ejemplo 10.96.0.0/12: llamadas directas al ClusterIP.
- Pod CIDR, por ejemplo 10.244.0.0/16: servicios headless y comunicación pod-to-pod.
- Node CIDR: si la aplicación va a puertos de la nodo (métricas, servicios hostNetwork).
- 169.254.169.254: metadatos del cloud, si se usan.
- Dominios internos de la empresa, por ejemplo .corp.local, si hay ruta directa a ellos desde el clúster.
- Direcciones de bases y colas internas, si están fuera del clúster pero en la red privada.
Trampa que no todos conocen: no todos los clientes soportan CIDR
Aquí empieza la verdadera especificidad. El formato de NO_PROXY no está estandarizado. Cada librería lo parsea a su manera:
- Go (net/http) entiende notación CIDR, sufijos con punto inicial y hosts individuales. Es el mejor caso, y la mayoría de las herramientas de Kubernetes están escritas en Go.
- curl soporta CIDR solo desde la versión 7.86. En imágenes antiguas basadas en distribuciones desactualizadas, la entrada 10.96.0.0/12 simplemente se ignora.
- Python (urllib, requests) no entiende CIDR en absoluto. La comparación es por sufijo de cadena. La entrada 10.96.0.0/12 nunca funcionará; además la entrada 10.96. solo funcionará como coincidencia tipo prefijo para hosts que terminen en esa cadena, es decir, tampoco ayuda.
- Node.js ignora por completo las variables de entorno del cliente HTTP nativo. Se necesita un ProxyAgent en undici o un agente de terceros, y ya es él quien decide cómo interpretar NO_PROXY.
- Java usa su propia variable http.nonProxyHosts con sintaxis de wildcards tipo *.svc.cluster.local y separador de barra vertical.
Conclusión práctica: si en el clúster hay un zoológico de lenguajes, la única forma fiable de excluir el tráfico interno por CIDR es no depender de que el cliente respete NO_PROXY, sino resolverlo a nivel del sidecar o del gateway, donde el parser está bajo tu control. Para nombres DNS, los sufijos con punto funcionan en casi todas partes, por eso en las aplicaciones conviene ir a los servicios por nombres, no por ClusterIP.
Mayúsculas y minúsculas de las variables
Otro punto que suele costar horas de depuración. curl lee http_proxy solo en minúsculas: HTTP_PROXY en mayúsculas la ignora deliberadamente, por una vulnerabilidad histórica con CGI. En cambio HTTPS_PROXY y NO_PROXY curl las acepta en ambos formatos. Go, Python y .NET leen ambos. Por eso en el manifiesto siempre definimos ambas formas: es barato y elimina toda una clase de problemas.
Fragmento funcional: ConfigMap con NO_PROXY y Deployment
Saca NO_PROXY a un ConfigMap a nivel de namespace. Así cambias la lista en un solo lugar, no en veinte 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"Y el Deployment que lo usa. La dirección del proxy aquí va todavía sin credenciales: cómo guardarlas lo veremos en una sección aparte:
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: 8080Fíjate en el esquema en HTTPS_PROXY: casi siempre es http://, porque el cliente se conecta al propio proxy por TCP normal y ya dentro establece el túnel CONNECT para las peticiones HTTPS. El valor https:// aquí significa TLS hasta el propio proxy, algo que no soportan ni todos los clientes ni todos los servidores proxy. Consulta en el panel de Proxeon qué tipo de conexión ofrece tu plan.
Verifica que la probe no pase por el proxy
Las probes de liveness y readiness las ejecuta el kubelet, no la aplicación, así que no ven las variables de entorno y el proxy no las afecta. Pero si tu aplicación dentro del handler /healthz llama por sí misma a dependencias por direcciones internas, esas llamadas sí caerán bajo HTTP_PROXY. Así se ve el síntoma clásico de «después de agregar el proxy, los pods dejaron de pasar readiness».
Qué NO recoge estas variables: kubelet, imágenes con su propia configuración, SDK
Las variables de entorno del pod viven dentro del proceso de la aplicación. Todo lo que está fuera de él o ignora deliberadamente el entorno requiere configuración aparte. Veamos tres grupos.
Kubelet y container runtime
La pregunta más frecuente al migrar un clúster detrás de un proxy: «¿por qué no se descargan las imágenes, si pusimos HTTP_PROXY en el Deployment?». Respuesta: las imágenes las descarga no el pod, sino el container runtime (containerd o CRI-O) en la nodo, por orden del kubelet. Son servicios de sistema lanzados vía systemd, y leen solo su propio entorno.
La configuración se hace con un archivo drop-in para la unidad systemd en cada nodo. Para containerd se ve así (archivo /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"Después de esto: systemctl daemon-reload y systemctl restart containerd. En NO_PROXY debe estar obligatoriamente el registry interno, de lo contrario el runtime irá por tus propias imágenes a través del proxy externo. Para el kubelet se necesita un drop-in aparte con menos frecuencia: solo si él mismo llama a APIs del cloud o a webhooks externos.
En clústeres gestionados puede no haber acceso a las nodos, y entonces la configuración del runtime se hace mediante mecanismos del proveedor de cloud o mediante un DaemonSet con un contenedor privilegiado que edita la configuración en el host. La segunda opción es un compromiso que conviene acordar con el equipo de seguridad.
Imágenes con su propia configuración
Muchas imágenes listas traen un archivo de configuración que sobrescribe o ignora el entorno:
- Aplicaciones JVM. Java no lee HTTP_PROXY. Se necesitan propiedades del sistema. Es cómodo pasarlas por JAVA_TOOL_OPTIONS, que la JVM recoge sin tocar los scripts de arranque:
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.*"- Imágenes con su propio config del cliente HTTP (algunos agentes de monitoreo, conectores, herramientas ETL) leen el proxy de su YAML o TOML. Ven el entorno, pero no lo usan. Lee la documentación de la imagen y busca claves tipo proxy_url.
- Imágenes con un resolver DNS fijado dentro de la aplicación rompen la propia idea de NO_PROXY por nombres: la aplicación resuelve el nombre sola, obtiene la IP y ya compara la IP contra la lista, donde tú tienes solo sufijos de dominio.
- Scripts entrypoint que sobrescriben variables. Ocurre menos, pero vale la pena revisar: kubectl exec pod -- cat /proc/1/environ (separando por byte nulo) mostrará el entorno real del proceso principal.
SDK y protocolos concretos
La lista es larga, veamos lo más frecuente:
- gRPC. Muchas implementaciones soportan HTTP_PROXY solo vía CONNECT y con limitaciones; parte ignora NO_PROXY. Para el tráfico gRPC dentro del clúster esto es doblemente peligroso.
- Node.js. El fetch nativo y el módulo http/https no leen el entorno. Se necesita un agente explícito. La librería axios lee HTTP_PROXY mediante su dependencia, pero el fetch nativo no. Dentro de una misma aplicación, dos clientes pueden comportarse distinto.
- SDK de cloud. La mayoría respeta HTTP_PROXY, pero la llamada al endpoint de metadatos (169.254.169.254) debe estar en las exclusiones, de lo contrario fallará la obtención de credenciales temporales.
- WebSocket y TCP crudo. Los clientes de bases de datos, brokers de mensajes, Redis, funcionan con sus propios protocolos sobre TCP y no pasan en absoluto por un proxy HTTP. Si ese tráfico debe ir por el proxy, se necesita modo SOCKS o intercepción transparente a nivel del sidecar.
- Clientes DNS propios dentro de las aplicaciones (resolución mediante una librería aparte) no interactúan con el proxy y van directo por UDP 53. Es normal, pero hay que recordarlo al escribir NetworkPolicy.
Conclusión de la sección: antes de desplegar el proxy en un servicio, haz un mapa de sus conexiones de salida por protocolos y librerías. Media hora de inventario ahorra un día de depuración.
Secretos: credenciales del proxy vía Secret, no en el manifiesto
Una dirección de proxy con login y contraseña en el cuerpo del Deployment es una fuga esperando su momento. Los manifiestos viven en git, caen en logs de CI, se muestran en kubectl get deploy -o yaml a cualquiera con permisos de lectura en el namespace. Hagámoslo bien.
Opción 1: URL completa del proxy en un Secret
El esquema más simple: en el Secret se guarda la cadena lista con credenciales, la aplicación la recibe como variable de entorno. El secreto se crea fuera de git (por variables de CI, un gestor externo de secretos o manualmente):
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"En el Deployment, en lugar de env explícitas, va la referencia al Secret y al ConfigMap con NO_PROXY:
containers:
- name: app
image: registry.internal/price-collector:2.7.0
envFrom:
- configMapRef:
name: egress-proxy-common
- secretRef:
name: egress-proxy-credsDetalle importante: si el login o la contraseña contienen caracteres especiales (@, :, /, %), hay que codificarlos en URL. Una contraseña con el símbolo @ sin codificar rompe el parseo de la URL y el cliente intenta conectarse al host equivocado. En el panel de Proxeon, al generar accesos, conviene elegir directamente una contraseña sin caracteres especiales o copiar la URL ya lista.
Opción 2: campos separados y armado de la URL en el contenedor
Cuando login y contraseña se guardan por separado (por ejemplo, sincronizados desde un almacén externo de secretos), la URL se arma en el entrypoint o en la propia aplicación:
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"Esta opción es más cómoda para rotar la contraseña: se cambia una sola clave del Secret, no cuatro cadenas completas.
Opción 3: archivo en lugar de variable
Las variables de entorno del proceso se leen desde /proc y a menudo caen en crash-dumps y reportes de error. Si la política de seguridad es estricta, monta el Secret como archivo con permisos 0400 y léelo en la aplicación o en el 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: trueOpción 4: eliminar las credenciales por completo
Si tienes un gateway de egreso con una IP de salida estable, la autorización en el proxy puede pasar de login y contraseña a lista blanca de IP. En ese caso, en el clúster no se guarda nada secreto: los pods van al Service interno del gateway, el gateway sale con una dirección fija y el proveedor deja pasar el tráfico por dirección. Proxeon soporta ambos modos de autorización, y para un clúster con egreso dedicado solemos recomendar justamente el modo por IP, por ser más seguro y más simple de operar.
Higiene en torno al Secret
- Activa el cifrado de Secrets en reposo en etcd: por defecto está desactivado en la mayoría de instalaciones autogestionadas.
- Limita el RBAC: el derecho a leer los secretos del namespace debe estar solo en los service accounts que realmente lo necesitan. La aplicación que recibe el secreto vía envFrom no debe leer el objeto Secret por API.
- No registres el entorno. Verifica que el framework no imprima el env al arrancar en modo debug.
- Acuerda la rotación. Cambiar la contraseña del proxy debe ser una operación de rutina: actualizaste el Secret, hiciste rollout restart, desactivaste la contraseña vieja.
- Ten en cuenta que al usar envFrom, actualizar el Secret no cambia el entorno de los pods ya en ejecución. Se necesita rollout restart. Si se monta como archivo, el kubelet actualizará el contenido en uno o dos minutos, pero la aplicación debe saber releer el archivo.
Enfoque de sidecar: cuándo está justificado y qué aporta además del proxy
El sidecar en el contexto de egreso es un contenedor proxy local en el pod que recibe el tráfico de la aplicación y lo envía hacia afuera a través del proxy externo. Convierte la pregunta «¿respeta la librería HTTP_PROXY?» en la pregunta «¿sabe el sidecar interceptar el tráfico?», y la segunda es más fácil de responder.
Cuándo el sidecar está justificado
- La aplicación está escrita en un stack donde gestionar el proxy es incómodo (JVM con una decena de clientes distintos, Node.js con mezcla de fetch y axios, legacy en C++).
- Necesitas rotar direcciones por horario o por errores sin tocar el código: el sidecar mantiene un pool de varios proxies de Proxeon y se cambia solo.
- Quieres sacar las credenciales de la aplicación: el sidecar es el único que conoce la contraseña, y la aplicación va a localhost sin autorización.
- Necesitas una política unificada de reintentos, timeouts y límites de peticiones al mundo externo para el servicio.
- Se requiere tráfico que no sea solo HTTP: modo SOCKS o intercepción TCP transparente.
Cuándo el sidecar es redundante
- Un servicio en Go o Python con un solo cliente HTTP. Las variables de entorno bastan.
- Límites estrictos de recursos: miles de pods pequeños, donde los 30-50 MB extra del sidecar se multiplican en toneladas de memoria.
- Ya hay un gateway de egreso con la lógica necesaria, y duplicarla en cada pod no tiene sentido.
Contenedores sidecar nativos
Desde Kubernetes 1.29 apareció y hacia 1.33 se estabilizó el mecanismo de sidecar nativo: un contenedor init con restartPolicy: Always. Ese contenedor arranca antes del principal, vive toda la vida del pod y termina correctamente después del principal. Esto resuelve un viejo dolor: antes la aplicación podía arrancar antes que el proxy y recibir connection refused en las primeras peticiones, y un Job con sidecar nunca terminaba porque el sidecar seguía vivo.
Fragmento funcional: pod con sidecar proxy
Aquí el sidecar es un pequeño proxy de reenvío (imagen tipo egress-sidecar) que escucha en 127.0.0.1:3128, lee las credenciales upstream del Secret y sabe qué direcciones dejar pasar directo:
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"Ojo: NO_PROXY en la aplicación sigue siendo necesario. El sidecar sabe dejar pasar el tráfico interno directo, pero ¿para qué hacerlo pasar por un salto extra a localhost? Además, el parseo de CIDR en el sidecar está bajo tu control, y en la aplicación no. La doble protección aquí es pertinente.
Modo transparente vía iptables
Si la aplicación ignora el entorno por completo, se añade un contenedor init que redirige todo el tráfico TCP de salida del pod, salvo las redes internas y el tráfico del propio sidecar, al puerto del sidecar. El sidecar debe trabajar en modo proxy transparente y reconstruir la dirección de destino original. El contenedor init necesita la capability NET_ADMIN, y el sidecar debe correr bajo un UID separado para que su propio tráfico no se loopee:
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_REDIRECTAquí 1337 es el UID bajo el que corre el sidecar, y 15001 su puerto transparente. Es el mismo principio que usan los service mesh para interceptar tráfico. Si en el clúster ya hay un mesh, lo más probable es que sus capacidades de egreso sean suficientes y no haga falta un sidecar aparte.
Qué aporta el sidecar además del proxy
- Métricas de tráfico de salida por host de destino, código de respuesta, tiempo: la aplicación no las conoce, pero el sidecar lo ve todo.
- Circuit breaker hacia APIs externas: ante una serie de errores, el sidecar deja de machacar al socio y devuelve un fallo rápido.
- Pool de direcciones y rotación: la lista de proxies de Proxeon en la configuración, con conmutación por estrategia round-robin o por error 407/403.
- Caché DNS local para nombres externos, reduciendo la carga sobre CoreDNS.
- Limitación de velocidad por destino, para respetar los límites de las APIs de socios y no caer en sus restricciones.
Gateway de egreso del clúster: punto único de salida
El gateway de egreso resuelve una tarea de otra escala: no «cómo este servicio va por el proxy», sino «por qué direcciones sale el clúster al mundo». Veamos una implementación práctica sin atarnos a un CNI concreto.
Arquitectura
- Se dedica un grupo de nodos con IP externas fijas (en el cloud, con dirección pública fijada o detrás de un gateway NAT aparte). Los nodos reciben la etiqueta node-role: egress y un taint para que no se alojen pods ajenos.
- En esos nodos se despliega un Deployment de proxy de reenvío con varias réplicas y anti-affinity. Delante va un Service tipo ClusterIP.
- Los pods de aplicación reciben HTTP_PROXY apuntando a ese Service. Su NO_PROXY contiene las redes internas.
- Una NetworkPolicy prohíbe a los pods de aplicación cualquier egreso fuera del clúster, salvo DNS y tráfico hacia el Service del gateway.
- El gateway hacia afuera va directo (entonces su IP es la dirección de salida del clúster) o a través de un proxy externo de Proxeon con autorización por IP del gateway.
Fragmento funcional: Deployment del gateway y 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 política para el namespace de aplicación, que cierra la salida directa a 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/12Una política así convierte el gateway de recomendación en obligación: un pod con HTTP_PROXY olvidado no saldrá al exterior evitándolo, sino que recibirá un timeout. Esto se detecta en el entorno de pruebas en minutos, no en producción en semanas.
Tolerancia a fallos y capacidad
- Mínimo tres réplicas en nodos distintos y un PodDisruptionBudget con minAvailable: 2.
- Monitoreo de conexiones: cantidad abiertas, tiempo de establecimiento del CONNECT, proporción de 5xx del upstream.
- Límite de conexiones simultáneas por pod cliente, para que un servicio defectuoso no sature el gateway.
- Considera el conntrack en los nodos del gateway: con decenas de miles de conexiones por segundo la tabla se desborda y tendrás pérdidas de paquetes misteriosas. Aumenta nf_conntrack_max de antemano.
Diagnóstico en el clúster: cómo verificar la IP de salida desde un pod y dónde mirar las fallas
Los problemas de proxy en un clúster son astutos porque el síntoma aparece lejos de la causa. Abajo, una metodología que permite localizar la falla en pocos pasos.
Paso 1: asegúrate de que el entorno se aplicó realmente
kubectl -n crawlers exec deploy/price-collector -- env | grep -i proxyBusca ambas formas de mayúsculas/minúsculas, la completitud de NO_PROXY, la ausencia de espacios extra. Si la aplicación arranca mediante un script entrypoint, verifica además el entorno del proceso principal vía /proc/1/environ: puede diferir de lo que muestra exec, que lanza una shell nueva.
Paso 2: verifica la IP de salida
Despliega en el clúster o en un host externo de la empresa un servicio echo simple que devuelva la dirección del cliente y los encabezados. Luego desde el 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/ipEl primer comando va según las reglas del entorno, el segundo fuerza salir sin proxy. Si las direcciones coinciden, el proxy no se está usando. Si el primero devuelve la IP del proveedor de proxy y el segundo la IP de la nodo o del NAT del cloud, el esquema funciona. Es también la única forma fiable de comprobar que ves exactamente la dirección que dio el panel de Proxeon, y no la de la nodo.
Paso 3: mira el diálogo con el proxy desde los ojos del cliente
kubectl -n crawlers exec deploy/price-collector -- curl -sv https://api.partner.example/status 2>&1 | head -30En la salida busca la línea con CONNECT y el código de respuesta del proxy a ella. Señales típicas:
- 407 Proxy Authentication Required: las credenciales no llegaron o son incorrectas. Verifica la codificación URL de la contraseña y que el proxy espere autenticación Basic.
- 403 del proxy al CONNECT: el host o puerto destino está prohibido por la política del proxy, o tu IP no está en la lista blanca con autorización por IP.
- 502/503 al CONNECT: el proxy no pudo llegar al destino. Si el destino es interno, este es el diagnóstico de «agujero en NO_PROXY».
- Connection refused hacia el propio proxy: dirección o puerto incorrectos, o una NetworkPolicy no deja pasar el tráfico al gateway.
- Timeout sin respuesta: casi siempre política de red o ruta ausente. Verifica que el DNS resuelva en absoluto.
Paso 4: verifica las llamadas intraclúster
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 en la salida hay una llamada al proxy en lugar de una conexión directa con el ClusterIP, es que NO_PROXY no cubrió esa dirección. Repite lo mismo para el nombre corto payments y para la IP del servicio: las tres deben ir directas.
Paso 5: contenedor efímero para imágenes sin herramientas
En imágenes mínimas no hay ni curl ni nslookup. Usa kubectl debug, que conecta un contenedor de depuración en el espacio de red del pod:
kubectl -n crawlers debug -it pod/price-collector-7d9f4-x2k8q --image=registry.internal/netdebug:latest --target=app -- shDentro están disponibles las mismas IP, el mismo resolv.conf y el mismo sidecar en localhost. Solo que el entorno de la aplicación no se hereda aquí: verifica las variables mediante /proc/PID/environ del proceso principal.
Paso 6: logs y eventos
- Logs del sidecar o del gateway: códigos de respuesta del upstream, tiempo de CONNECT, a qué hosts se llamó. Justo aquí se ve el intento de enviar una dirección interna hacia afuera.
- Eventos del pod: kubectl describe pod mostrará ImagePullBackOff, señal de que el container runtime no está configurado para el proxy o el registry no está en su NO_PROXY.
- Logs de CoreDNS en modo debug: un pico de NXDOMAIN en nombres externos indica que la aplicación resuelve los hosts externos por sí misma, cuando debería entregárselos al proxy.
- Métricas de conntrack en los nodos de egreso: el crecimiento de nf_conntrack_count hacia el límite es presagio de pérdidas.
Paso 7: separa el problema del proxy del problema del destino
Un mismo 403 puede venir del proxy y del sitio destino. Distinguirlos ayuda el encabezado Via o encabezados específicos del proveedor en la respuesta, así como comparar con una petición mediante --noproxy. Si con proxy da 403 y directo da 200, el asunto es la configuración del proxy o que el recurso destino limita el tráfico desde las direcciones de ese pool. Si da 403 en ambos casos, el problema está en la propia petición.
Errores típicos
Reunamos los tropiezos más frecuentes. Muchos ya los analizamos, aquí va un resumen para revisión rápida.
- NO_PROXY copiado de un servidor. El valor localhost,127.0.0.1 en un clúster rompe garantizadamente la comunicación entre servicios. Se necesitan .svc, .cluster.local, service y pod CIDR.
- Confiar en CIDR en NO_PROXY con Python o curl viejo. La librería ignora la entrada en silencio, el tráfico al ClusterIP va por el proxy. Ve por nombres DNS y usa sufijos.
- Solo variables en mayúsculas. curl no lee HTTP_PROXY en mayúsculas. Define ambas formas.
- Credenciales en el manifiesto. Contraseña en git y en la salida de kubectl get -o yaml. Solo Secret, mejor autorización por IP vía gateway.
- Contraseña con caracteres especiales sin codificación URL. El cliente parsea mal la URL y obtienes connection refused hacia un host inexistente.
- Esperar que kubelet y containerd tomen el env del pod. Las imágenes no se descargan, todos van a buscar el problema en el Deployment, y se necesita un drop-in de systemd en los nodos.
- Registry olvidado en el NO_PROXY del runtime. El registry interno se vuelve inaccesible porque containerd fue por él a través del proxy externo.
- Java con HTTP_PROXY. La JVM no lee estas variables. Se necesitan propiedades del sistema vía JAVA_TOOL_OPTIONS.
- Sidecar como contenedor normal, no como init nativo con restartPolicy: Always. La aplicación arranca antes que el proxy y falla en las primeras peticiones, el Job nunca termina.
- Gateway de egreso sin NetworkPolicy. El gateway existe, pero la mitad de los pods van por fuera de él, y te enteras cuando el socio manda una lista de IP desconocidas.
- Actualizaron el Secret y no hicieron rollout restart. Los pods viejos siguen con la contraseña antigua, que ya desactivaste en el panel.
- Ignorar los metadatos del cloud. 169.254.169.254 no está en NO_PROXY: el SDK del cloud no puede obtener claves temporales y la aplicación falla con errores de autorización misteriosos.
- Sobrecargar una sola dirección de proxy con todas las réplicas. Treinta réplicas machacan una única dirección de salida, la API del socio responde con límites. Distribuye la carga por un pool de direcciones mediante sidecar o gateway.
Herramientas y recursos
Conjunto que conviene tener a mano al trabajar con tráfico de salida en un clúster.
Herramientas integradas de Kubernetes
- kubectl exec y kubectl debug: verificación del entorno y de las conexiones de red dentro del pod, incluidos contenedores efímeros para imágenes mínimas.
- kubectl describe pod y eventos: ImagePullBackOff, fallos de probes, errores de montaje de secretos.
- NetworkPolicy: forzar la salida por el gateway y probar hipótesis de «quién va por fuera».
- Contenedores sidecar nativos (restartPolicy: Always en initContainers): ciclo de vida correcto del asistente proxy.
Diagnóstico de red
- Imagen de depuración con curl, dig, tcpdump, ss, iptables: arma la tuya sobre una distro mínima y guárdala en el registry interno.
- Servicio echo propio que devuelva la IP del cliente y los encabezados: la forma más honesta de ver la dirección de salida.
- tcpdump en la nodo con filtro por IP del pod: cuando necesitas ver exactamente a qué dirección se abre la conexión.
- Logs de CoreDNS en modo debug: para entender quién resuelve qué.
Gestión de secretos y configuración
- Gestor externo de secretos con sincronización al Secret del clúster: para que las credenciales del proxy no vivan en git y se roten de forma centralizada.
- Kustomize o Helm con un ConfigMap común para NO_PROXY: una única verdad para todos los Deployments del namespace.
- Políticas de admisión (admission controllers) que verifiquen la presencia de NO_PROXY en cada pod con HTTP_PROXY: protección automática contra el copy-paste «de servidor».
Proveedor de proxy
Del lado del proxy importan tres cosas: la posibilidad de autorización por IP (para el esquema con gateway), direcciones estables con un plazo de fijación claro y una API para gestionar accesos, de modo que la rotación de contraseñas pueda automatizarse desde CI. En Proxeon estos mecanismos están en el panel y vía API, lo que permite integrar al proveedor en un proceso GitOps en lugar de copiar a mano cadenas de conexión.
Casos y resultados
Caso 1: plataforma ETL y servicios internos «rotos»
El equipo de datos añadió HTTP_PROXY en veinte Deployments para que los recolectores de catálogos de marketplaces fueran por direcciones dedicadas. Copiaron NO_PROXY de la documentación de servidor: localhost y 127.0.0.1. En una hora, la proporción de 5xx entre servicios internos subió del 0,1% al 34%, y las probes de readiness de tres servicios empezaron a fallar de forma intermitente. Tras añadir .svc, .cluster.local y ambos CIDR al ConfigMap común y hacer rollout restart, los errores desaparecieron en cinco minutos. Efecto adicional: pasar NO_PROXY al ConfigMap redujo el tiempo del siguiente cambio de lista de media jornada (editar veinte manifiestos) a un solo commit.
Caso 2: integraciones JVM y sidecar
Un integrador financiero en Java con siete clientes HTTP distintos dentro de una misma aplicación (algunos con librerías que no leen propiedades de sistema del proxy). El intento de configurarlo todo con JAVA_TOOL_OPTIONS cubrió cinco clientes de siete. La solución: un sidecar nativo con intercepción transparente vía iptables. Los siete clientes pasaron por el proxy sin cambios de código, las credenciales desaparecieron de la aplicación y las métricas de tráfico de salida aparecieron por primera vez en la historia del servicio. Costo: 40 MB de memoria por pod, algo insignificante con doce réplicas.
Caso 3: gateway de egreso y autorización por IP
Un clúster de sesenta nodos con autoescalado llamaba a varias APIs de socios que exigen lista blanca de IP. Cada escalado de nodos cambiaba el conjunto de direcciones de salida y los socios bloqueaban el tráfico. Desplegaron un gateway de egreso en tres nodos dedicados con direcciones fijadas, introdujeron NetworkPolicy en todos los namespaces de aplicación y pasaron la autorización en el proxy Proxeon al modo por IP. Resultado: la cantidad de incidentes «el socio nos volvió a bloquear» cayó de tres o cuatro al mes a cero en un trimestre, y del clúster desaparecieron todos los Secrets con contraseñas de proxy. Resultado secundario: los logs centralizados del gateway revelaron dos servicios que llamaban a APIs externas de los que nadie sabía.
Caso 4: containerd y el misterioso ImagePullBackOff
Tras migrar el clúster a una red con proxy obligatorio, los pods con HTTP_PROXY funcionaban, pero las nodos nuevas no podían descargar ninguna imagen. La causa: la ausencia del drop-in para containerd. Tras añadir el archivo con el proxy y el registry interno en NO_PROXY, el problema se resolvió. La lección se convirtió en una verificación del script de bootstrap de nodos: antes de unirse al clúster, la nodo comprueba que containerd puede descargar una imagen de prueba del registry interno y del externo.
Checklist de despliegue
Repasa la lista antes de desplegar el proxy en un servicio de producción. Cada punto es el incidente nocturno de alguien.
Preparación
- Está elaborado el mapa de conexiones de salida del servicio: hosts externos, servicios internos, bases, colas, endpoints del cloud, protocolos.
- Se conocen el service CIDR, pod CIDR, node CIDR del clúster y los dominios internos de la empresa.
- Se verificó qué clientes HTTP usa la aplicación y cómo cada uno interpreta NO_PROXY (CIDR, sufijos, mayúsculas/minúsculas).
- Se eligió el nivel: contenedor, sidecar o gateway. La decisión está documentada.
Configuración
- NO_PROXY está en un ConfigMap del namespace y contiene: localhost, 127.0.0.1, .svc, .svc.cluster.local, .cluster.local, kubernetes.default.svc, service CIDR, pod CIDR, 169.254.169.254, dominios internos.
- Las variables están definidas en ambos formatos de mayúsculas/minúsculas.
- Las credenciales solo están en Secret; la contraseña está codificada en URL; el RBAC sobre los secretos está limitado.
- Para JVM está definido JAVA_TOOL_OPTIONS con nonProxyHosts.
- Si hay sidecar, es nativo (initContainers con restartPolicy: Always), con readinessProbe y límites de recursos.
- Si hay gateway, mínimo tres réplicas, anti-affinity, PDB, NetworkPolicy en los namespaces de aplicación.
- El container runtime en los nodos está configurado aparte; el registry interno está en su NO_PROXY.
Verificación en el entorno de pruebas
- env dentro del pod muestra los valores esperados.
- La petición al servicio echo devuelve la dirección del proxy; con --noproxy, la dirección de la nodo.
- Las peticiones a servicios internos por nombre corto, nombre completo y ClusterIP van directas (se ve en curl -v).
- Readiness y liveness pasan de forma estable durante al menos diez minutos.
- El cliente del API de Kubernetes (si existe) ve el control plane.
- Los logs del sidecar o del gateway no contienen llamadas a direcciones internas.
Despliegue y después
- Rollout por etapas con maxUnavailable no mayor al 25%; monitoreo de 5xx entre servicios y de errores 407/403 del proxy.
- Está documentado el procedimiento de rotación de contraseña: actualizar el Secret, rollout restart, desactivar el acceso antiguo.
- Están configuradas alertas para el crecimiento de la proporción de errores del proxy y para el acercamiento del conntrack al límite en los nodos de egreso.
- En una semana, revisión de los logs del gateway: si no aparecieron destinatarios externos inesperados.
Preguntas frecuentes
¿Se puede definir HTTP_PROXY globalmente para todo el clúster, en lugar de en cada Deployment?
No hay un mecanismo directo: las variables de entorno son parte de la especificación del pod. Las opciones prácticas son un ConfigMap común con envFrom en todos los manifiestos vía Helm o Kustomize, o un mutating admission controller que agregue automáticamente las variables a los pods con cierta anotación. La segunda vía es más potente, pero exige cuidado: también debe agregar NO_PROXY, de lo contrario la automatización romperá servicios en masa.
¿Por qué curl en el pod va por el proxy y la aplicación en Python no, si el entorno es el mismo?
Lo más probable es un tema de mayúsculas/minúsculas o de parseo de NO_PROXY. curl lee http_proxy solo en minúsculas, Python en ambos. Por otro lado, Python no entiende CIDR en NO_PROXY, y un curl reciente sí. Verifica ambas hipótesis: define las variables en ambos formatos y usa sufijos de dominio en lugar de CIDR.
¿Se necesita NO_PROXY si todo el tráfico interno ya va por un sidecar con reglas direct?
Técnicamente se puede prescindir, pero recomendamos dejarlo. Primero, un salto extra por localhost para miles de peticiones internas son latencias y carga para el sidecar no nulas. Segundo, si el sidecar cae o se reinicia, la aplicación con NO_PROXY correcto seguirá comunicándose con los servicios internos, y sin él perderá absolutamente todo.
¿Cómo saber que una petición a un socio salió exactamente desde la dirección del proxy y no desde la de la nodo?
La única forma fiable es una petición de control a un servicio echo que devuelva la IP del cliente, desde el mismo pod y con el mismo entorno. Compara la respuesta con la dirección que muestra el panel de Proxeon para tu acceso y con la respuesta al usar --noproxy. Los logs del propio socio también ayudan, pero normalmente no los tienes a mano.
¿Vale la pena cifrar la conexión hasta el proxy (esquema https:// en HTTPS_PROXY)?
Para el tráfico HTTPS hacia los sitios destino, el contenido ya está cifrado de extremo a extremo dentro del túnel CONNECT, el proxy solo ve el host destino. TLS hasta el proxy protege las credenciales de autorización y los nombres de host en el tramo del pod al proxy. Si ese tramo pasa por una red no confiable, TLS hasta el proxy está justificado, pero asegúrate de que tanto el cliente como el proxy lo soporten: parte de las librerías ignora el esquema https:// en HTTPS_PROXY. Dentro del clúster, entre el pod y el gateway de egreso normalmente bastan las políticas de red.
¿Qué hacer con el tráfico de bases de datos y brokers que debe salir fuera del clúster?
Por un proxy HTTP no pasará: no es HTTP. Opciones: modo SOCKS5 del proxy, si el proveedor y la librería cliente lo soportan; intercepción transparente a nivel del sidecar con tunelización TCP; o un gateway de egreso que trabaje a nivel de enrutamiento, no de protocolo. La elección depende de si necesitas específicamente cierta IP externa o basta con una salida centralizada.
¿Se puede usar el mismo proxy para containerd y para las aplicaciones?
Técnicamente sí, pero normalmente son tareas distintas. El runtime descarga imágenes de registries públicos y necesita el proxy del perímetro corporativo. Las aplicaciones llaman a APIs de socios y necesitan direcciones dedicadas con propiedades concretas. Mezclar hace que la descarga de imágenes consuma límites de tráfico pensados para tareas de aplicación. Sepáralos.
¿Cómo cambiar la contraseña del proxy sin downtime?
Crea un segundo acceso en el proveedor, actualiza el Secret con las nuevas credenciales, ejecuta rollout restart con actualización por etapas de las réplicas, verifica en los logs que no hay 407 y solo entonces desactiva el acceso antiguo. Si usas autorización por IP vía gateway, la rotación de contraseñas desaparece por completo de tu lista de tareas.
¿Cómo limitar que el proxy se use solo para dominios externos concretos?
NO_PROXY funciona al revés: describe adónde no ir con el proxy, y con eso no se construye una lista blanca. Esa lógica se implementa en el sidecar o en el gateway: reglas del tipo «dominios de la lista A van por el pool de direcciones de Proxeon, todo lo demás va directo o se prohíbe». Además, NetworkPolicy, para que la salida directa a internet no exista físicamente.
Conclusión
El tráfico de salida en Kubernetes se diferencia del de servidor no en detalles, sino en la propia estructura: cada pod tiene dos mundos, interno y externo, y el proxy debe ver solo el segundo. De ahí se deriva todo lo demás. NO_PROXY pasa de formalidad a elemento arquitectónico que debe describir toda la red interna del clúster y tener en cuenta que distintas librerías lo interpretan de forma diferente. Las variables de entorno funcionan solo dentro del proceso: kubelet, container runtime y SDK obstinados requieren atención aparte. Las credenciales no deben caer en los manifiestos y, en el mejor caso, deben desaparecer por completo del clúster gracias a un gateway con autorización por IP.
Los tres niveles de integración del proxy — contenedor, sidecar, gateway de egreso — no son competidores, sino herramientas para tareas distintas. El nivel de contenedor es rápido y granular. El sidecar elimina la dependencia del comportamiento de las librerías y añade métricas, rotación, reintentos. El gateway le da al clúster una cara predecible ante el mundo externo y convierte la salida por el proxy no en una recomendación, sino en una regla respaldada por políticas de red.
¿Qué hacer a continuación? Empieza por el inventario: arma el mapa de conexiones de salida de tus servicios y mira cuántas son internas. Luego saca NO_PROXY a un ConfigMap común y verifícalo en el entorno de pruebas con la metodología de la sección de diagnóstico. Si en el clúster hay más de un stack o hay requisitos de direcciones estables, diseña un gateway de egreso y conéctalo a las direcciones dedicadas de Proxeon con autorización por IP. Y ten el checklist de despliegue a mano: se paga solo con el primer incidente que no ocurrió.