Tráfego de saída no Kubernetes e proxy: env do pod, sidecar, gateway de egress e o papel do NO_PROXY
Sumário do artigo
- Introdução: por que configurar "como num servidor comum" não funciona no cluster
- Fundamentos: como o tráfego de saída do pod funciona fisicamente
- Três níveis onde dá para colocar o proxy: container, sidecar, gateway de egress
- Variáveis de ambiente no manifesto: http_proxy, https_proxy, no_proxy e por que sem um no_proxy correto o cluster quebra
- O que não herda essas variáveis: kubelet, imagens com configuração própria, sdks
- Segredos: credenciais do proxy via secret, não no manifesto
- Abordagem de sidecar: quando se justifica e o que entrega além do proxy
- Gateway de egress do cluster: ponto único de saída
- Diagnóstico no cluster: como verificar o ip de saída do pod e onde olhar as falhas
- Erros típicos
- Ferramentas e recursos
- Casos e resultados
- Checklist de implantação
- Faq
- Conclusão
Introdução: por que configurar "como num servidor comum" não funciona no cluster
Numa máquina virtual comum, configurar um proxy leva cinco minutos. Você define HTTP_PROXY no profile, adiciona alguns endereços nas exceções, reinicia o serviço e vai tomar um café. No Kubernetes, esse cenário termina de outro jeito: metade dos serviços de repente responde 502, o health-check começa a derrubar pods e o serviço de pagamentos reclama que não consegue falar com o pod vizinho. E, ainda assim, as requisições externas — que motivaram tudo isso — continuam funcionando.
Por que isso acontece? Porque no cluster um mesmo processo vive simultaneamente em dois mundos de comunicação de rede. O primeiro é a internet externa: APIs de parceiros, marketplaces, fontes de dados para scraping, serviços públicos. O segundo é a rede interna do cluster: outros serviços via ClusterIP, o API server do Kubernetes, DNS, bancos de dados, filas, métricas. Num servidor comum, o segundo mundo é praticamente vazio, então uma variável global de proxy não quebra nada. No cluster, o segundo mundo é enorme, e qualquer proxy que receber uma requisição para um endereço como payments.default.svc.cluster.local simplesmente não vai conseguir resolvê-lo. O proxy fica do lado de fora e não sabe nada do seu DNS interno.
Some a isso as particularidades do próprio Kubernetes. As variáveis de ambiente do pod não afetam o kubelet nem o container runtime. As imagens frequentemente trazem configuração própria que ignora o ambiente. Os SDKs de linguagens diferentes têm entendimentos distintos do que significa uma exceção de proxy. E tudo isso acontece enquanto o IP de saída do pod nem sequer é visível para o mundo externo: o que sai é o endereço do nó ou do gateway NAT da nuvem.
Neste guia, vamos destrinchar o tráfego de saída do Kubernetes "por camadas": como o pacote sai fisicamente do pod, onde exatamente dá para encaixar um proxy, por que o NO_PROXY no cluster deixa de ser formalidade e vira elemento crítico de arquitetura, como passar credenciais com segurança, quando o sidecar se justifica e quando é exagero, e como depurar falhas sem ficar adivinhando. Não vamos repetir a sintaxe básica de variáveis de ambiente — aqui é só a especificidade do cluster. Todos os trechos de manifesto são funcionais e podem servir de base.
Fundamentos: como o tráfego de saída do pod funciona fisicamente
Antes de instalar o proxy, vale entender por qual caminho o pacote passa sem ele. Caso contrário, o diagnóstico vira adivinhação.
Do container até o nó
Cada pod recebe um IP próprio do pod CIDR, por exemplo 10.244.3.17. Dentro do pod, todos os containers compartilham o mesmo namespace de rede: um IP, um conjunto de interfaces, um localhost comum. Esse é o fato-chave para a abordagem de sidecar, à qual voltaremos. O pacote sai do container pelo adaptador virtual do pod até o nó, onde o plugin CNI cuida dele. A partir daí, dois cenários.
- Destino dentro do cluster. Se a aplicação acessa o ClusterIP de um serviço (por exemplo 10.96.14.2), o kube-proxy ou o dataplane eBPF do CNI substitui o endereço de destino pelo IP do pod de backend específico. O tráfego vai até o nó correto pelo overlay ou direto via L3 e nunca sai do cluster.
- Destino fora do cluster. Se o endereço não pertence nem ao pod CIDR nem ao service CIDR, o pacote sai pela rota padrão do nó. Na saída do nó, quase sempre entra um SNAT: o endereço de origem 10.244.3.17 é trocado pelo IP do nó. Na nuvem, por cima disso pode haver um NAT gateway, e aí o mundo externo vê o endereço dele.
Daí a primeira conclusão prática: o serviço externo nunca vê o IP do pod. Ele vê o IP do nó ou o NAT da nuvem. Se você tem autoscaling de nós, os IPs de saída mudam junto com eles. É exatamente por isso que autorização no proxy por endereço IP sem um nó de egress dedicado funciona mal no cluster — falamos disso mais adiante.
DNS dentro do cluster
Os pods usam o DNS do cluster (normalmente o CoreDNS). No arquivo resolv.conf do pod estão definidos os domínios de busca: namespace.svc.cluster.local, svc.cluster.local, cluster.local. Graças a isso, o nome payments é expandido para payments.default.svc.cluster.local. Um proxy externo não faz ideia desse esquema. Ele recebe uma requisição para o host payments, tenta resolver pelo DNS público e devolve erro. Essa é a razão fundamental pela qual o NO_PROXY no cluster é mais importante do que em qualquer servidor isolado.
O que significa, afinal, "instalar um proxy" no cluster
Os engenheiros entendem essa frase de três formas diferentes, e confundi-las é perigoso:
- Fazer uma aplicação específica sair pela internet através de um servidor proxy (por exemplo, pelos endereços dedicados da Proxeon), sem mexer nos outros serviços.
- Garantir um IP de saída estável e previsível para o cluster inteiro ou para parte dele — tarefa de rede corporativa, compliance, auditoria.
- Centralizar o gerenciamento do tráfego externo: log, limites, ponto único de saída.
A primeira tarefa se resolve no nível do pod. A segunda e a terceira, no nível do cluster. Misturar os níveis gera a maior parte dos problemas que vamos discutir.
Três níveis onde dá para colocar o proxy: container, sidecar, gateway de egress
Pense no cluster como um prédio de escritórios. Dá para dar um proxy para cada funcionário individualmente (container), colocar um assistente ao lado de cada mesa (sidecar) ou montar uma única portaria na saída do prédio (gateway de egress). Cada esquema tem seu custo.
Nível 1: o próprio container
A aplicação recebe o endereço do proxy por variáveis de ambiente, arquivo de configuração ou flags de inicialização e encaminha o tráfego por ele por conta própria. É o jeito mais simples e mais comum.
Pontos positivos:
- Zero infraestrutura adicional: mudanças apenas no manifesto do Deployment.
- Granularidade: um serviço precisa de proxy, o vizinho não.
- O proxy "entende" o protocolo: um proxy HTTP vê o host de destino, pode gerar log, aplicar regras.
Pontos negativos:
- Só funciona se o código ou a biblioteca respeitarem a configuração. Java, parte dos clientes Node.js, algumas implementações de gRPC exigem configuração à parte.
- Dependência total de um NO_PROXY correto: um erro e a comunicação interna do cluster quebra.
- As credenciais do proxy ficam no ambiente do processo e acessíveis a qualquer um que consiga executar exec no container.
- Rotação de proxy ou mudança de endereço exige recriar os pods.
Nível 2: container sidecar
Ao lado da aplicação, no mesmo pod, sobe um segundo container — um proxy de encaminhamento local. A aplicação envia tráfego para localhost:3128 (ou ele é interceptado por regras de iptables), e o sidecar decide para onde e como mandar adiante: por qual proxy upstream, com qual autorização, com qual timeout.
Pontos positivos:
- A aplicação não conhece as credenciais externas: elas vivem só no sidecar.
- Ponto único de lógica: rotação, retentativas, escolha do pool de endereços, métricas — sem mexer no código da aplicação.
- Modo transparente via iptables funciona até com bibliotecas que ignoram HTTP_PROXY.
- Atualizar a lógica de proxy é atualizar uma única imagem de sidecar.
Pontos negativos:
- Container adicional em cada pod: memória, CPU, mais um objeto para monitorar.
- A interceptação transparente exige NET_ADMIN no init-container, o que nem sempre é permitido por políticas de segurança.
- Mais difícil de depurar: dois lugares onde pode quebrar.
Nível 3: gateway de egress do cluster
Um conjunto dedicado de pods (ou nós) se torna o único ponto de saída. Os demais pods ou passam explicitamente pelo Service interno do gateway, ou são forçados a isso por políticas de rede e roteamento. O gateway, por sua vez, sai para a internet direto ou através de um proxy externo.
Pontos positivos:
- Um IP de saída estável (ou um conjunto pequeno) para o cluster inteiro. Ideal para autorização por IP no lado do provedor de proxy e para listas de permissão de parceiros.
- Logs, limites e auditoria centralizados.
- As aplicações não guardam nenhuma credencial: elas veem só o endereço interno do gateway.
- A rotação de endereços externos acontece num único lugar.
Pontos negativos:
- Ponto único de falha: o gateway precisa ser tolerante a falhas (várias réplicas, PodDisruptionBudget, anti-affinity).
- Um salto de rede extra e potencial gargalo de throughput.
- Exige NetworkPolicy bem pensada, senão fica um "buraco" de saída direta contornando o gateway.
- Granularidade mais difícil: se equipes diferentes precisam de IPs externos distintos, será preciso subir vários gateways.
Como escolher
Regra prática que usamos no dia a dia de projeto:
- Uma ou duas aplicações, código sob controle, clientes HTTP padrão — nível do container.
- Muitos serviços com stacks diferentes, exigência de rotação e retentativas, pouca vontade de distribuir credenciais — sidecar.
- Exigências de compliance, autorização por IP, auditoria de todo o tráfego externo, dezenas de equipes — gateway de egress, muitas vezes combinado com sidecar para lógica fina.
Repare: os níveis não se excluem. O esquema maduro típico é um gateway de egress como fronteira obrigatória mais variáveis de ambiente ou sidecar onde é preciso resolver especificidades.
Variáveis de ambiente no manifesto: HTTP_PROXY, HTTPS_PROXY, NO_PROXY e por que sem um NO_PROXY correto o cluster quebra
A sintaxe das próprias variáveis você já conhece. O que interessa é o que acontece com elas dentro do pod e por que o valor padrão de NO_PROXY "localhost,127.0.0.1" é catastróficamente pouco aqui.
Anatomia da falha
Vamos percorrer a cadeia. O serviço orders chama payments pelo endereço http://payments:8080/charge. No ambiente está definido HTTP_PROXY. O cliente HTTP olha o host de destino, não o encontra no NO_PROXY e, em vez de abrir uma conexão TCP com payments, abre conexão com o proxy e envia a ele a requisição GET http://payments:8080/charge. O proxy tenta resolver o nome payments no resolver dele. O nome não existe. O proxy devolve 502 ou 503. Seu serviço recebe o erro do proxy, mas no log ele aparece como erro de payments. A equipe vai consertar o payments, que está absolutamente saudável.
Há também uma segunda variante, mais traiçoeira. Se o serviço acessa o ClusterIP diretamente, por um endereço como 10.96.14.2, o proxy recebe uma requisição para um endereço privado. Alguns proxies descartam essas requisições de imediato; outros tentam se conectar a 10.96.14.2 na própria rede deles. No pior caso, existe algum outro host com esse endereço lá.
Uma terceira variante diz respeito ao próprio Kubernetes. As bibliotecas cliente que falam com o API server (operators, controllers, aplicações com leader election) usam o endereço kubernetes.default.svc ou o IP da variável KUBERNETES_SERVICE_HOST. Sem exceção, elas também vão passar pelo proxy e perder a conexão com o control plane.
O que precisa estar no NO_PROXY de um pod
Conjunto mínimo que recomendamos como ponto de partida. Substitua pelos seus CIDRs — eles podem ser obtidos na configuração do cluster ou nas anotações dos serviços:
- localhost, 127.0.0.1, ::1 — endereços locais, incluindo a comunicação com o sidecar.
- .svc, .svc.cluster.local, .cluster.local — todos os nomes de serviço. O ponto inicial significa "qualquer subdomínio".
- kubernetes.default.svc, kubernetes.default.svc.cluster.local — explícitos, para clientes da API.
- Service CIDR, por exemplo 10.96.0.0/12 — acessos diretos ao ClusterIP.
- Pod CIDR, por exemplo 10.244.0.0/16 — serviços headless e comunicação pod-to-pod.
- Node CIDR — se a aplicação acessa portas do nó (métricas, serviços com hostNetwork).
- 169.254.169.254 — metadados da nuvem, se usados.
- Domínios internos da empresa, por exemplo .corp.local, se houver rota direta a partir do cluster.
- Endereços de bancos e filas internos, se estiverem fora do cluster mas na rede privada.
Uma armadilha que nem todo mundo conhece: nem todos os clientes suportam CIDR
Aqui começa a verdadeira especificidade. O formato do NO_PROXY não é padronizado. Cada biblioteca o interpreta do seu jeito:
- Go (net/http) entende notação CIDR, sufixos com ponto inicial e hosts individuais. É o melhor caso, e a maioria das ferramentas de Kubernetes é escrita em Go.
- curl só suporta CIDR a partir da versão 7.86. Em imagens antigas baseadas em distribuições desatualizadas, a entrada 10.96.0.0/12 será simplesmente ignorada.
- Python (urllib, requests) não entende CIDR de jeito nenhum. A comparação é por sufixo de string. A entrada 10.96.0.0/12 nunca vai funcionar; já 10.96. funciona como uma correspondência parecida com prefixo apenas para hosts terminados nessa string, ou seja, também não ajuda.
- Node.js ignora completamente as variáveis de ambiente no cliente HTTP nativo. É preciso um ProxyAgent no undici ou um agente de terceiros, e é ele que decide como tratar o NO_PROXY.
- Java usa a própria variável http.nonProxyHosts, com sintaxe de wildcards tipo *.svc.cluster.local e separador por barra vertical.
Conclusão prática: se o cluster tem um zoológico de linguagens, a única forma confiável de excluir o tráfego interno por CIDR é não depender do NO_PROXY do cliente, e sim resolver isso no nível do sidecar ou do gateway, onde o parser está sob seu controle. Para nomes DNS, os sufixos com ponto funcionam em quase tudo, então nas aplicações vale acessar serviços por nome, não por ClusterIP.
Maiúsculas e minúsculas nas variáveis
Mais um ponto que consome horas de depuração com frequência. O curl lê http_proxy apenas em minúsculas: HTTP_PROXY em maiúsculas é ignorado de propósito, por causa de uma vulnerabilidade histórica com CGI. Já HTTPS_PROXY e NO_PROXY o curl aceita em ambos os formatos. Go, Python e .NET leem as duas formas. Por isso, no manifesto, sempre definimos as duas — é barato e elimina uma classe inteira de problemas.
Trecho funcional: ConfigMap com NO_PROXY e Deployment
Coloque o NO_PROXY num ConfigMap no nível do namespace. Assim você muda a lista num único lugar, e não em vinte 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"E o Deployment que o utiliza. O endereço do proxy ainda aparece sem credenciais — como guardá-las, veremos numa seção à parte:
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: 8080Preste atenção no esquema em HTTPS_PROXY: na maioria das vezes é http://, porque o cliente se conecta ao próprio proxy por TCP comum e só dentro dele estabelece um túnel CONNECT para requisições HTTPS. O valor https:// aqui significa TLS até o próprio proxy, o que pouquíssimos clientes e pouquíssimos servidores proxy suportam. Confirme no painel da Proxeon qual tipo de conexão o seu plano oferece.
Verifique se a probe não passa pelo proxy
As probes de liveness e readiness são executadas pelo kubelet, não pela aplicação, então elas não veem as variáveis de ambiente e não sofrem com o proxy. Mas se a sua aplicação, dentro do handler /healthz, chama dependências por endereços internos, essas chamadas cairão sob o HTTP_PROXY. É exatamente essa a cara clássica do sintoma "depois de adicionar o proxy, os pods pararam de passar na readiness".
O que NÃO herda essas variáveis: kubelet, imagens com configuração própria, SDKs
As variáveis de ambiente do pod vivem dentro do processo da aplicação. Tudo o que está fora dele ou ignora o ambiente de propósito exige configuração separada. Vamos ver três grupos.
Kubelet e container runtime
A dúvida mais frequente ao migrar um cluster para trás de um proxy: "por que as imagens não baixam, se definimos HTTP_PROXY no Deployment?". Resposta: quem baixa as imagens não é o pod, e sim o container runtime (containerd ou CRI-O) no nó, por ordem do kubelet. São serviços de sistema rodando via systemd, e eles leem apenas o ambiente deles.
A configuração é feita com um arquivo drop-in para a unit do systemd em cada nó. Para o containerd, fica assim (arquivo /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"Depois disso, systemctl daemon-reload e systemctl restart containerd. O NO_PROXY precisa obrigatoriamente incluir o registry interno, senão o runtime vai buscar suas próprias imagens através do proxy externo. Para o kubelet, um drop-in separado é necessário com menos frequência — só se ele mesmo acessa APIs da nuvem ou webhooks externos.
Em clusters gerenciados, pode não haver acesso aos nós, e a configuração do runtime é feita pelos mecanismos do provedor de nuvem ou por um DaemonSet com container privilegiado que edite a configuração no host. A segunda opção é um meio-termo que vale alinhar com o time de segurança.
Imagens com configuração própria
Muitas imagens prontas vêm com um arquivo de configuração que sobrescreve ou ignora o ambiente:
- Aplicações JVM. Java não lê HTTP_PROXY. São necessárias propriedades de sistema. O jeito prático é passá-las via JAVA_TOOL_OPTIONS, que a JVM absorve sem mexer nos scripts de inicialização:
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.*"- Imagens com config própria de cliente HTTP (alguns agentes de monitoramento, conectores, ferramentas de ETL) leem o proxy do próprio YAML ou TOML. Elas veem o ambiente, mas não o usam. Leia a documentação da imagem e procure chaves do tipo proxy_url.
- Imagens com resolver DNS embutido no código quebram a própria ideia de NO_PROXY por nome: a aplicação resolve o nome sozinha, obtém o IP e compara o IP com uma lista onde você só tem sufixos de domínio.
- Scripts de entrypoint que sobrescrevem variáveis. Acontece com menos frequência, mas vale checar: kubectl exec pod -- cat /proc/1/environ | tr byte nulo em quebra de linha mostra o ambiente real do processo principal.
SDKs e protocolos específicos
A lista aqui é longa; citamos o mais comum:
- gRPC. Muitas implementações só suportam HTTP_PROXY via CONNECT e com limitações; algumas ignoram o NO_PROXY. Para tráfego gRPC dentro do cluster, isso é duplamente perigoso.
- Node.js. O fetch nativo e os módulos http/https não leem o ambiente. É preciso um agente explícito. A biblioteca axios lê HTTP_PROXY através da dependência dela, mas o fetch nativo não. Numa mesma aplicação, dois clientes podem se comportar de formas diferentes.
- SDKs de nuvem. A maioria respeita HTTP_PROXY, mas o acesso ao endpoint de metadados (169.254.169.254) precisa estar nas exceções, senão a obtenção de credenciais temporárias quebra.
- WebSocket e TCP cru. Clientes de banco de dados, brokers de mensagens e Redis usam protocolos próprios sobre TCP e não passam por proxy HTTP de jeito nenhum. Se esse tráfego precisa obrigatoriamente sair por um proxy, é preciso modo SOCKS ou interceptação transparente no nível do sidecar.
- Clientes DNS próprios nas aplicações (resolução via biblioteca à parte) não interagem com o proxy e vão direto por UDP 53. Isso é normal, mas é bom lembrar ao escrever a NetworkPolicy.
Conclusão da seção: antes de aplicar o proxy num serviço, monte um mapa das conexões de saída dele por protocolos e bibliotecas. Meia hora de inventário economiza um dia de depuração.
Segredos: credenciais do proxy via Secret, não no manifesto
Endereço de proxy com usuário e senha no corpo do Deployment é um vazamento esperando a hora certa. Os manifestos ficam no git, caem nos logs do CI, aparecem em kubectl get deploy -o yaml para qualquer um com permissão de leitura no namespace. Vamos fazer certo.
Opção 1: URL completa do proxy no Secret
O esquema mais simples: o Secret guarda a string pronta com as credenciais, e a aplicação a recebe como variável de ambiente. O secret é criado fora do git (via variáveis de CI, gerenciador externo de segredos ou 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"No Deployment, em vez de env explícito — referência ao Secret e ao ConfigMap com o NO_PROXY:
containers:
- name: app
image: registry.internal/price-collector:2.7.0
envFrom:
- configMapRef:
name: egress-proxy-common
- secretRef:
name: egress-proxy-credsDetalhe importante: se o usuário ou a senha tiverem caracteres especiais (@, :, /, %), eles precisam ser codificados para URL. Uma senha com o caractere @ sem codificação quebra a interpretação da URL, e o cliente tenta conectar no host errado. No painel da Proxeon, ao gerar os acessos, é prático já escolher uma senha sem caracteres especiais ou copiar a URL pronta.
Opção 2: campos separados e montagem da URL no container
Quando usuário e senha ficam separados (por exemplo, sincronizados de um armazenamento externo de segredos), a URL é montada no entrypoint ou na própria aplicação:
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"Essa opção é mais prática para rotacionar senha: muda uma chave do Secret, não quatro strings prontas.
Opção 3: arquivo em vez de variável
As variáveis de ambiente do processo são lidas de /proc e frequentemente acabam em crash dumps e relatórios de erro. Se a política de segurança for rígida, monte o Secret como arquivo com permissão 0400 e leia no aplicativo ou no 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: trueOpção 4: eliminar as credenciais de vez
Se você tem um gateway de egress com IP de saída estável, a autorização no proxy pode migrar de usuário e senha para lista de permissão por IP. Nesse caso, o cluster não guarda nada secreto: os pods falam com o Service interno do gateway, o gateway sai por um endereço fixo e o provedor libera o tráfego por endereço. A Proxeon suporta os dois modos de autenticação, e para clusters com egress dedicado geralmente recomendamos o modo por IP, por ser mais seguro e simples de operar.
Higiene em torno do Secret
- Ative a criptografia de Secrets em repouso no etcd — por padrão, ela vem desligada na maioria das instalações autogerenciadas.
- Restrinja o RBAC: a permissão de ler Secrets do namespace deve ser só dos service accounts que realmente precisam. A aplicação que recebe o secret via envFrom não deve ler o objeto Secret pela API.
- Não gere log do ambiente. Verifique se o framework não imprime env na inicialização em modo debug.
- Combine a rotação. Trocar a senha do proxy deve ser uma operação de rotina: atualizou o Secret, fez rollout restart, desabilitou a senha antiga.
- Lembre-se de que, com envFrom, atualizar o Secret não muda o ambiente dos pods já em execução. É preciso rollout restart. Com montagem por arquivo, o kubelet atualiza o conteúdo em um ou dois minutos, mas a aplicação precisa reler o arquivo.
Abordagem de sidecar: quando se justifica e o que entrega além do proxy
Sidecar, no contexto de egress, é um container proxy local no pod que recebe o tráfego da aplicação e o envia adiante através do proxy externo. Ele transforma a questão "a biblioteca respeita HTTP_PROXY?" na questão "o sidecar consegue interceptar o tráfego?", e a segunda pergunta é mais fácil de responder.
Quando o sidecar se justifica
- A aplicação foi escrita numa stack onde gerenciar proxy é chato (JVM com dezenas de clientes diferentes, Node.js com mistura de fetch e axios, legado em C++).
- Você precisa rotacionar endereços por agendamento ou por erro sem mexer no código: o sidecar mantém um pool de vários proxies da Proxeon e troca sozinho.
- Quer tirar as credenciais da aplicação: o sidecar é o único que sabe a senha, e a aplicação fala com localhost sem autorização.
- Precisa de política unificada de retentativas, timeouts e limites de requisições para o mundo externo em cada serviço.
- É necessário tráfego que não seja apenas HTTP: modo SOCKS ou interceptação TCP transparente.
Quando o sidecar é exagero
- Um único serviço em Go ou Python com um cliente HTTP só. As variáveis de ambiente bastam.
- Limites rígidos de recursos: milhares de pods pequenos, onde 30 a 50 MB a mais por sidecar se multiplicam em toneladas de memória.
- Já existe um gateway de egress com a lógica necessária, e duplicá-la em cada pod não faz sentido.
Containers sidecar nativos
A partir do Kubernetes 1.29 surgiu e no 1.33 se estabilizou o mecanismo de sidecar nativo: init-container com restartPolicy: Always. Esse container sobe antes do principal, vive por toda a vida do pod e termina corretamente depois do principal. Isso resolve uma dor antiga: antes, a aplicação podia subir antes do proxy e receber connection refused nas primeiras requisições, e um Job com sidecar nunca terminava, porque o sidecar continuava rodando.
Trecho funcional: pod com sidecar proxy
Aqui o sidecar é um pequeno proxy de encaminhamento (imagem fictícia egress-sidecar) que escuta em 127.0.0.1:3128, lê as credenciais do upstream no Secret e sabe quais endereços mandar direto:
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"Repare: o NO_PROXY da aplicação continua necessário. O sidecar sabe mandar o tráfego interno direto, mas por que passar por um salto extra no localhost? Além disso, a interpretação de CIDR no sidecar está sob seu controle, e na aplicação não. A proteção dupla, aqui, faz sentido.
Modo transparente via iptables
Se a aplicação ignora o ambiente por completo, adiciona-se um init-container que redireciona todo o tráfego TCP de saída do pod, exceto redes internas e o tráfego do próprio sidecar, para a porta do sidecar. O sidecar, nesse caso, precisa rodar em modo de proxy transparente e reconstruir o endereço de destino original. O init-container precisa da capability NET_ADMIN, e o sidecar deve rodar com um UID à parte, para que o tráfego dele mesmo não entre em loop:
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_REDIRECTAqui, 1337 é o UID sob o qual o sidecar roda, e 15001 é a porta transparente dele. É o mesmo princípio que as service meshes usam para interceptar tráfego. Se o cluster já tem uma mesh instalada, muito provavelmente os recursos de egress dela bastam e um sidecar separado é desnecessário.
O que o sidecar entrega além do proxy
- Métricas de tráfego de saída por host de destino, código de resposta e tempo: a aplicação não as vê, mas o sidecar vê tudo.
- Circuit breaker para APIs externas: numa sequência de erros, o sidecar para de martelar o parceiro e devolve uma falha rápida.
- Pool de endereços e rotação: lista de proxies da Proxeon na configuração, troca por estratégia round-robin ou por erro 407/403.
- Cache DNS local para nomes externos, reduzindo a carga no CoreDNS.
- Limitação de velocidade por destino, para respeitar limites de APIs de parceiros e não cair nas restrições deles.
Gateway de egress do cluster: ponto único de saída
O gateway de egress resolve uma questão de outra escala: não "como este serviço passa pelo proxy", mas "por quais endereços o cluster, afinal, sai para o mundo". Vamos ver uma implementação prática sem amarrar a um CNI específico.
Arquitetura
- Define-se um grupo de nós com IPs externos fixos (na nuvem, com endereço público fixo ou atrás de um NAT gateway dedicado). Os nós recebem o rótulo node-role: egress e um taint, para que pods alheios não se acomodem neles.
- Nesses nós, sobe um Deployment de proxy de encaminhamento com várias réplicas e anti-affinity. Na frente dele, um Service do tipo ClusterIP.
- Os pods de aplicação recebem HTTP_PROXY apontando para esse Service. O NO_PROXY deles inclui as redes internas.
- NetworkPolicy proíbe qualquer egress dos pods de aplicação para fora do cluster, exceto DNS e tráfego até o Service do gateway.
- O gateway sai para a internet direto (e aí o IP dele é o endereço de saída do cluster) ou através de um proxy externo da Proxeon com autorização por IP do gateway.
Trecho funcional: Deployment do gateway e 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: 3128A política para o namespace de aplicação, que fecha a saída direta para 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/12Essa política transforma o gateway de recomendação em obrigação: um pod com HTTP_PROXY esquecido não sai para a internet por fora dele, apenas recebe timeout. Isso é descoberto no ambiente de teste em minutos, e não em produção em semanas.
Tolerância a falhas e capacidade
- No mínimo três réplicas em nós diferentes e um PodDisruptionBudget com minAvailable: 2.
- Monitoramento de conexões: abertas, tempo de estabelecimento do CONNECT, proporção de 5xx do upstream.
- Limite de conexões simultâneas por pod cliente, para que um serviço com problema não entupa o gateway.
- Atenção ao conntrack nos nós do gateway: com dezenas de milhares de conexões por segundo, a tabela transborda e você passa a ter perdas de pacote misteriosas. Aumente o nf_conntrack_max com antecedência.
Diagnóstico no cluster: como verificar o IP de saída do pod e onde olhar as falhas
Problemas de proxy no cluster são traiçoeiros porque o sintoma aparece longe da causa. A seguir, uma metodologia que permite localizar a falha em poucos passos.
Passo 1: confirme que o ambiente realmente foi aplicado
kubectl -n crawlers exec deploy/price-collector -- env | grep -i proxyProcure as duas formas de caixa, a completude do NO_PROXY e a ausência de espaços extras. Se a aplicação inicia por um script de entrypoint, verifique também o ambiente do processo principal via /proc/1/environ — ele pode ser diferente do que o exec, que abre um novo shell, mostra.
Passo 2: verifique o IP de saída
Suba no cluster ou num host externo da empresa um serviço de echo simples, que devolve o endereço do cliente e os cabeçalhos. Depois, de dentro do 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/ipO primeiro comando segue as regras do ambiente; o segundo força o caminho sem proxy. Se os endereços coincidem, o proxy não está sendo usado. Se o primeiro retorna o IP do provedor de proxy e o segundo, o IP do nó ou do NAT da nuvem, o esquema está funcionando. Essa também é a única forma confiável de garantir que você está vendo exatamente o endereço que o painel da Proxeon informou, e não o endereço do nó.
Passo 3: veja o diálogo com o proxy pelos olhos do cliente
kubectl -n crawlers exec deploy/price-collector -- curl -sv https://api.partner.example/status 2>&1 | head -30No resultado, procure a linha com CONNECT e o código de resposta do proxy a ela. Sinais típicos:
- 407 Proxy Authentication Required — as credenciais não chegaram ou estão erradas. Verifique a codificação para URL da senha e se o proxy espera exatamente autenticação Basic.
- 403 do proxy no CONNECT — o host ou a porta de destino está proibido pela política do proxy, ou o seu IP não está na lista de permissão na autenticação por IP.
- 502/503 no CONNECT — o proxy não conseguiu chegar ao destino. Se o destino é interno, esse é o diagnóstico de "buraco no NO_PROXY".
- Connection refused ao próprio proxy — endereço ou porta errados, ou a NetworkPolicy não deixa o tráfego chegar ao gateway.
- Timeout sem resposta — na maioria das vezes, é política de rede ou rota inexistente. Verifique se o DNS resolve de fato.
Passo 4: verifique as chamadas internas do cluster
kubectl -n crawlers exec deploy/price-collector -- curl -sv http://payments.default.svc.cluster.local:8080/healthz 2>&1 | grep -iE "connected|proxy|HTTP/"Se o resultado mostra uma conexão com o proxy em vez de uma conexão direta com o ClusterIP, o NO_PROXY não cobriu esse endereço. Repita o mesmo para o nome curto payments e para o IP do serviço — os três devem ir direto.
Passo 5: container efêmero para imagens sem ferramentas
Imagens mínimas não têm curl nem nslookup. Use o kubectl debug, que conecta um container de depuração ao namespace de rede do pod:
kubectl -n crawlers debug -it pod/price-collector-7d9f4-x2k8q --image=registry.internal/netdebug:latest --target=app -- shDentro dele, estão disponíveis o mesmo IP, o mesmo resolv.conf e o mesmo sidecar no localhost. Só o ambiente da aplicação não é herdado: verifique as variáveis via /proc/PID/environ do processo principal.
Passo 6: logs e eventos
- Logs do sidecar ou do gateway: códigos de resposta do upstream, tempo de CONNECT, hosts acessados. É exatamente onde se vê a tentativa de mandar um endereço interno para fora.
- Eventos do pod: kubectl describe pod mostra ImagePullBackOff — indício de que o container runtime não está configurado para o proxy ou de que o registry não está no NO_PROXY dele.
- Logs do CoreDNS em modo debug: picos de NXDOMAIN para nomes externos indicam que a aplicação resolve hosts externos por conta própria, quando deveria entregá-los ao proxy.
- Métricas de conntrack nos nós de egress: aumento do nf_conntrack_count perto do limite é prenúncio de perdas.
Passo 7: separe o problema do proxy do problema do destino
Um mesmo 403 pode vir do proxy ou do site de destino. Diferenciar ajuda com o cabeçalho Via ou cabeçalhos específicos do provedor na resposta, e também comparando com uma requisição via --noproxy. Se pelo proxy dá 403 e direto dá 200, a questão é a configuração do proxy ou o fato de o recurso de destino restringir tráfego dos endereços daquele pool. Se dá 403 nos dois casos, o problema está na própria requisição.
Erros típicos
Vamos reunir os tropeços mais frequentes. Muitos já foram discutidos; aqui vai um resumo para conferência rápida.
- NO_PROXY copiado de um servidor. O valor localhost,127.0.0.1 no cluster quebra garantidamente a comunicação entre serviços. Precisa de .svc, .cluster.local, service CIDR e pod CIDR.
- Apostar em CIDR no NO_PROXY do Python ou de curl antigo. A biblioteca ignora a entrada silenciosamente, e o tráfego para o ClusterIP vai pelo proxy. Acesse por nomes DNS e use sufixos.
- Só variáveis em maiúsculas. O curl não lê HTTP_PROXY em maiúsculas. Defina as duas formas.
- Credenciais no manifesto. Senha no git e na saída de kubectl get -o yaml. Só Secret, e melhor ainda autorização por IP via gateway.
- Senha com caracteres especiais sem codificação para URL. O cliente interpreta a URL de forma errada e você recebe connection refused para um host inexistente.
- Esperar que kubelet e containerd absorvam o env do pod. As imagens não baixam, todos vão procurar o problema no Deployment, e o que faltava era um drop-in do systemd nos nós.
- Esquecer o registry no NO_PROXY do runtime. O registry interno fica inacessível, porque o containerd foi buscá-lo através do proxy externo.
- Java com HTTP_PROXY. A JVM não lê essas variáveis. Precisa de propriedades de sistema via JAVA_TOOL_OPTIONS.
- Sidecar como container comum, e não como init nativo com restartPolicy: Always. A aplicação sobe antes do proxy e quebra nas primeiras requisições; o Job nunca termina.
- Gateway de egress sem NetworkPolicy. O gateway existe, mas metade dos pods passa por fora dele, e você só descobre quando o parceiro manda uma lista de IPs desconhecidos.
- Atualizou o Secret e não fez rollout restart. Os pods antigos seguem com a senha antiga, que você já desabilitou no painel.
- Ignorar os metadados da nuvem. 169.254.169.254 fora do NO_PROXY e o SDK da nuvem não consegue obter credenciais temporárias, fazendo a aplicação cair com erros de autorização misteriosos.
- Sobrecarregar um único endereço de proxy com todas as réplicas. Trinta réplicas martelam um único endereço de saída, e a API do parceiro responde com limites. Distribua a carga por um pool de endereços via sidecar ou gateway.
Ferramentas e recursos
Um conjunto que vale ter à mão ao trabalhar com tráfego de saída no cluster.
Ferramentas nativas do Kubernetes
- kubectl exec e kubectl debug — verificação do ambiente e das conexões de rede dentro do pod, incluindo containers efêmeros para imagens mínimas.
- kubectl describe pod e eventos — ImagePullBackOff, falhas de probe, erros de montagem de secrets.
- NetworkPolicy — forçar a saída pelo gateway e testar hipóteses de "quem está passando por fora".
- Containers sidecar nativos (restartPolicy: Always em initContainers) — ciclo de vida correto do ajudante proxy.
Diagnóstico de rede
- Imagem de depuração com curl, dig, tcpdump, ss, iptables — monte a sua sobre uma distribuição mínima e mantenha no registry interno.
- Serviço de echo próprio, que devolve o IP do cliente e os cabeçalhos — a forma mais honesta de ver o endereço de saída.
- tcpdump no nó com filtro pelo IP do pod — quando é preciso ver exatamente para qual endereço a conexão é aberta.
- Logs do CoreDNS em modo debug — para entender quem resolve o quê.
Gerenciamento de segredos e configuração
- Gerenciador externo de segredos com sincronização para o Secret do cluster — para que as credenciais do proxy não vivam no git e sejam rotacionadas de forma centralizada.
- Kustomize ou Helm com um ConfigMap comum para o NO_PROXY — uma única verdade para todos os Deployments do namespace.
- Políticas de admissão (admission controllers) que verifiquem a presença de NO_PROXY em cada pod com HTTP_PROXY — proteção automática contra copiar e colar "do servidor".
Provedor de proxy
Do lado do proxy, três coisas importam: possibilidade de autorização por IP (para o esquema com gateway), endereços estáveis com prazo de fixação claro e API de gerenciamento de acessos, para que a rotação de senhas possa ser automatizada a partir do CI. Na Proxeon, esses mecanismos existem no painel e via API, o que permite integrar o provedor ao processo de GitOps em vez de copiar strings de conexão à mão.
Casos e resultados
Caso 1: plataforma de ETL e serviços internos "quebrados"
O time de dados adicionou HTTP_PROXY em vinte Deployments para que os coletores de catálogos de marketplaces saíssem pelos endereços dedicados. O NO_PROXY foi copiado da documentação de servidor: localhost e 127.0.0.1. Em uma hora, a proporção de 5xx entre serviços internos subiu de 0,1% para 34%, e as probes de readiness de três serviços começaram a oscilar. Depois de adicionar .svc, .cluster.local e os dois CIDRs no ConfigMap comum e fazer rollout restart, os erros sumiram em cinco minutos. Efeito adicional: mover o NO_PROXY para o ConfigMap reduziu o tempo da próxima alteração da lista de meio dia (editar vinte manifestos) para um único commit.
Caso 2: integrações JVM e sidecar
Um integrador financeiro em Java com sete clientes HTTP diferentes dentro de uma mesma aplicação (parte deles via bibliotecas que não leem propriedades de sistema de proxy). A tentativa de configurar tudo via JAVA_TOOL_OPTIONS cobriu cinco dos sete clientes. A solução foi um sidecar nativo com interceptação transparente via iptables. Todos os sete clientes passaram pelo proxy sem mudança de código, as credenciais saíram da aplicação e as métricas de tráfego de saída apareceram pela primeira vez na história do serviço. Custo: 40 MB de memória por pod, o que, em doze réplicas, mostrou-se irrelevante.
Caso 3: gateway de egress e autorização por IP
Um cluster de sessenta nós com autoscaling acessava várias APIs de parceiros que exigem lista de IPs permitidos. Cada escalonamento de nós mudava o conjunto de endereços de saída, e os parceiros bloqueavam o tráfego. Foi montado um gateway de egress em três nós dedicados com endereços fixos, aplicaram-se NetworkPolicies em todos os namespaces de aplicação e a autorização no proxy da Proxeon migrou para modo por IP. Resultado: o número de incidentes "o parceiro nos bloqueou de novo" caiu de três ou quatro por mês para zero no trimestre, e todos os Secrets com senhas de proxy sumiram do cluster. Efeito colateral: os logs centralizados do gateway revelaram dois serviços que acessavam APIs externas sobre os quais ninguém sabia.
Caso 4: containerd e um ImagePullBackOff misterioso
Depois de mover o cluster para uma rede com proxy obrigatório, os pods com HTTP_PROXY definido funcionavam, mas os nós novos não conseguiam baixar imagem alguma. Causa: a ausência do drop-in para o containerd. Depois de adicionar o arquivo com o proxy e o registry interno no NO_PROXY, o problema foi resolvido. A lição virou uma verificação no script de bootstrap dos nós: antes de entrar no cluster, o nó verifica que o containerd consegue baixar uma imagem de teste do registry interno e do externo.
Checklist de implantação
Percorra a lista antes de aplicar o proxy num serviço de produção. Cada item é o incidente noturno de alguém.
Preparação
- Mapa das conexões de saída do serviço montado: hosts externos, serviços internos, bancos, filas, endpoints de nuvem, protocolos.
- Service CIDR, pod CIDR, node CIDR do cluster e domínios internos da empresa conhecidos.
- Verificado quais clientes HTTP a aplicação usa e como cada um interpreta o NO_PROXY (CIDR, sufixos, caixa).
- Nível escolhido: container, sidecar ou gateway. Decisão documentada.
Configuração
- NO_PROXY movido para um ConfigMap do namespace e contendo: localhost, 127.0.0.1, .svc, .svc.cluster.local, .cluster.local, kubernetes.default.svc, service CIDR, pod CIDR, 169.254.169.254, domínios internos.
- Variáveis definidas nas duas formas de caixa.
- Credenciais só no Secret; senha codificada para URL; RBAC de secrets restrito.
- Para JVM, JAVA_TOOL_OPTIONS com nonProxyHosts definido.
- Se houver sidecar — que seja nativo (initContainers com restartPolicy: Always), com readinessProbe e limites de recursos.
- Se houver gateway — no mínimo três réplicas, anti-affinity, PDB, NetworkPolicy nos namespaces de aplicação.
- Container runtime dos nós configurado à parte; registry interno no NO_PROXY dele.
Validação no ambiente de teste
- O env dentro do pod mostra os valores esperados.
- Requisição ao serviço de echo retorna o endereço do proxy; com --noproxy, o endereço do nó.
- Requisições a serviços internos por nome curto, nome completo e ClusterIP vão direto (visível no curl -v).
- Readiness e liveness passam de forma estável por pelo menos dez minutos.
- O cliente da API do Kubernetes (se houver) enxerga o control plane.
- Os logs do sidecar ou do gateway não contêm acessos a endereços internos.
Implantação e depois
- Rollout gradual com maxUnavailable não superior a 25%; monitoramento de 5xx entre serviços e de erros 407/403 do proxy.
- Procedimento de rotação de senha documentado: atualizar o Secret, rollout restart, desabilitar o acesso antigo.
- Alertas configurados para o aumento da proporção de erros do proxy e para a aproximação do conntrack do limite nos nós de egress.
- Depois de uma semana — revisão dos logs do gateway: não apareceram destinatários externos inesperados?
FAQ
Dá para definir HTTP_PROXY globalmente para o cluster todo, em vez de em cada Deployment?
Não existe mecanismo direto: as variáveis de ambiente fazem parte da especificação do pod. As alternativas práticas são um ConfigMap comum com envFrom em todos os manifestos via Helm ou Kustomize, ou um mutating admission controller que adiciona as variáveis automaticamente a pods com determinada anotação. A segunda opção é mais poderosa, mas exige cuidado: ele também deve adicionar o NO_PROXY, senão a automação vai quebrar serviços em massa.
Por que o curl no pod passa pelo proxy, mas a aplicação em Python não, se o ambiente é o mesmo?
Provavelmente é questão de caixa ou de interpretação do NO_PROXY. O curl lê http_proxy só em minúsculas; o Python lê nas duas. Por outro lado, o Python não entende CIDR no NO_PROXY, e o curl recente entende. Teste as duas hipóteses: defina as variáveis nas duas formas de caixa e use sufixos de domínio em vez de CIDR.
Precisa de NO_PROXY se todo o tráfego interno já passa por um sidecar com regras de direct?
Tecnicamente, dá para viver sem, mas recomendamos manter. Primeiro, um salto extra pelo localhost em milhares de requisições internas gera latências e carga não nulas no sidecar. Segundo, se o sidecar cair ou reiniciar, a aplicação com NO_PROXY correto continua falando com os serviços internos; sem ele, perde tudo.
Como saber que uma requisição a um parceiro saiu do endereço do proxy, e não do endereço do nó?
A única forma confiável é uma requisição de controle ao serviço de echo, que devolve o IP do cliente, a partir do mesmo pod e com o mesmo ambiente. Compare a resposta com o endereço exibido no painel da Proxeon para o seu acesso e com a resposta obtida com --noproxy. Os logs do próprio parceiro também ajudam, mas normalmente não estão à mão.
Vale cifrar a conexão até o proxy (esquema https:// em HTTPS_PROXY)?
Para tráfego HTTPS aos sites de destino, o conteúdo já está cifrado de ponta a ponta dentro do túnel CONNECT; o proxy vê apenas o host de destino. O TLS até o proxy protege as credenciais de autorização e os nomes de host no trecho entre o pod e o proxy. Se esse trecho passa por uma rede não confiável, o TLS até o proxy se justifica, mas confirme que tanto o cliente quanto o proxy o suportam: parte das bibliotecas ignora o esquema https:// em HTTPS_PROXY. Dentro do cluster, entre o pod e o gateway de egress, normalmente as políticas de rede bastam.
O que fazer com tráfego de bancos de dados e brokers que precisa sair do cluster?
Por proxy HTTP ele não passa — não é HTTP. As opções: modo SOCKS5 no proxy, se o provedor e a biblioteca cliente o suportarem; interceptação transparente no nível do sidecar com tunelamento TCP; ou um gateway de egress que atue no nível de roteamento, e não de protocolo. A escolha depende de você precisar de um IP externo específico ou de bastar uma saída centralizada.
Dá para usar o mesmo proxy para o containerd e para as aplicações?
Tecnicamente sim, mas normalmente são tarefas diferentes. O runtime baixa imagens de registries públicos e precisa de um proxy do perímetro corporativo. As aplicações acessam APIs de parceiros e precisam de endereços dedicados com propriedades específicas. Misturar faz com que o download de imagens consuma limites de tráfego destinados às tarefas da aplicação. Separe.
Como trocar a senha do proxy com segurança e sem downtime?
Crie um segundo acesso no provedor, atualize o Secret com as credenciais novas, faça rollout restart com atualização gradual das réplicas, confirme pelos logs que não há 407 e só então desabilite o acesso antigo. Se você usa autorização por IP via gateway, a rotação de senhas simplesmente desaparece da sua lista de tarefas.
Como restringir que o proxy seja usado só para domínios externos específicos?
O NO_PROXY funciona ao contrário — ele descreve para onde não passar pelo proxy, e com base nele não dá para construir uma lista de permissão. Essa lógica se implementa no sidecar ou no gateway: regras do tipo "domínios da lista A — pelo pool de endereços da Proxeon, todo o resto — direto ou bloqueado". E também NetworkPolicy, para que a saída direta para a internet simplesmente não exista.
Conclusão
O tráfego de saída no Kubernetes difere do de um servidor não pelos detalhes, mas pela própria estrutura: cada pod tem dois mundos — interno e externo — e o proxy deve enxergar só o segundo. Daí decorre todo o resto. O NO_PROXY deixa de ser formalidade e vira um elemento de arquitetura, que deve descrever toda a rede interna do cluster e levar em conta que bibliotecas diferentes o interpretam de formas diferentes. As variáveis de ambiente só funcionam dentro do processo — kubelet, container runtime e SDKs teimosos exigem atenção à parte. As credenciais não devem entrar nos manifestos e, idealmente, devem até sumir do cluster graças a um gateway com autorização por IP.
Os três níveis de encaixe do proxy — container, sidecar, gateway de egress — não são concorrentes, e sim ferramentas para tarefas diferentes. O nível do container é rápido e granular. O sidecar elimina a dependência do comportamento das bibliotecas e acrescenta métricas, rotação e retentativas. O gateway dá ao cluster um rosto previsível no mundo externo e faz da saída pelo proxy não uma recomendação, mas uma regra reforçada por políticas de rede.
O que fazer agora? Comece pelo inventário: monte o mapa das conexões de saída dos seus serviços e veja quantas são internas. Depois, mova o NO_PROXY para um ConfigMap comum e valide-o no ambiente de teste pela metodologia da seção de diagnóstico. Se o cluster tem mais de uma stack ou exige endereços estáveis — projete um gateway de egress e conecte a ele endereços dedicados da Proxeon com autorização por IP. E mantenha o checklist de implantação por perto: ele se paga já no primeiro incidente que não aconteceu.