Um pod OpenVPN no Kubernetes não encaminha nada? Verifique ip_forward
Os clientes fazem ping ao pod da VPN e nada além disso. O namespace tem o encaminhamento desligado, e a correção precisa sobreviver ao admission, aos reinícios e ao caminho de volta.
O seu portátil mostra um túnel verde, o registo do cliente indica que as rotas foram enviadas, e o único endereço que responde é o IP do túnel do próprio servidor VPN. Os pings para lá morrem independentemente do alvo, seja um ClusterIP, o endereço de outro pod ou um nome de anfitrião público. Essa combinação é diagnóstica, não vaga: a desencapsulação funciona e o segundo salto não.
Encontrei isto num nó Debian com uma imagem OpenVPN instalada por chart, e o túnel era a parte fácil. O cliente obteve uma concessão 10.8.0.x, 10.8.0.1 respondeu e 10.96.0.10, o serviço DNS do cluster, expirou. Nada no registo do servidor parecia doente, e é exatamente por isso que as pessoas começam a reescrever regras de iptables que nunca precisaram de tocar.
Um pod VPN é um router, e vale a pena dizê-lo sem rodeios. Os pacotes cifrados chegam, são desembrulhados no namespace de rede do próprio pod e depois têm de sair por outra interface. O Linux recusa esse segundo passo a menos que o encaminhamento esteja ativado dentro desse mesmo namespace.
O que o cliente consegue e não consegue alcançar
O endereço que responde pertence ao extremo do túnel, e o processo VPN possui-o localmente, por isso o encaminhamento não intervém de todo. Tudo o resto, incluindo outro pod, um VIP de serviço ou a internet, precisa que o kernel mova um pacote de tun0 para eth0. Quando esse movimento é recusado, o sintoma é um silêncio uniforme e não uma rejeição, porque nada gera um erro ICMP de volta pelo túnel. Obtém tempos limite, não mensagens de inalcançável, e essa é a pista de que ninguém está a encaminhar de todo.
Dois minutos de classificação estreitam a camada. Se os nomes DNS falham enquanto pings em bruto a um IP de serviço funcionam, o problema é a canalização DNS, não o encaminhamento. Se todos os destinos além do endereço do par expiram, incluindo um IP nu, comece pelo namespace.
Leia a definição dentro do namespace
Cada pod recebe o seu próprio namespace de rede, e net.ipv4.ip_forward é um sysctl por namespace. Alterá-lo no nó não lhe diz nada sobre o contentor, e é uma forma fiável de passar uma tarde na máquina errada.
kubectl exec -it deploy/openvpn -- sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0
Um zero confirma o diagnóstico. O valor também não é herdado da configuração de execução do nó: começa no valor predefinido da imagem sempre que um contentor é criado. Por isso uma correção aplicada à mão com kubectl exec ... sysctl -w desaparece no reinício seguinte e volta como uma falha que parece intermitente.
Ativá-lo para que o manifesto o mantenha
A opção duradoura é um contentor init que corre no mesmo pod sandbox e escreve o valor antes de o contentor VPN arrancar. Os contentores de um pod partilham um namespace de rede, por isso a definição passa para o contentor principal.
initContainers:
- name: enable-forwarding
image: busybox:1.36
command: ["sysctl", "-w", "net.ipv4.ip_forward=1"]
securityContext:
privileged: true
É precisamente privileged: true a linha a que os revisores se opõem, e com razão. Escrever um sysctl exige privilégio ou uma capacidade estritamente limitada, e um cluster que aplique o Pod Security Standard restrito pode recusar liminarmente um contentor init privilegiado. Se o admission rejeitar o pod, sabe-o no momento da criação, o que pelo menos é honesto e fácil de ler.
A alternativa mais limpa é a lista securityContext.sysctls ao nível do pod, que permite ao kubelet aplicar sysctls com namespace sem conceder privilégios a nenhum contentor. Só cobre nomes da lista de permitidos do cluster. Se net.ipv4.ip_forward está nessa lista depende de como o kubelet foi configurado, e um pod que peça um sysctl fora dela é recusado no admission ou fica preso em CreateContainerConfigError enquanto o sandbox é repetido. Verifique a lista de permitidos antes de assumir que o campo está avariado.
Antes de construir à mão qualquer das abordagens, veja se o chart já expõe o interruptor, muitas vezes como algo semelhante a ipForwardInitContainer: true. Usar o valor do chart mantém o seu ficheiro de substituição pequeno e sobrevive à atualização seguinte, o que as edições de Deployment corrigidas à mão não fazem.
A segunda metade: caminho de volta e política
O encaminhamento ligado é necessário, não suficiente. Se os clientes usarem um conjunto privado para o qual a rede do cluster não tem rota, as respostas não encontram o caminho de volta para o túnel, e o pod tem de mascarar esse tráfego em direção ao seu destino.
A política assenta nisso. Os objetos NetworkPolicy e as regras de firewall da nuvem são avaliados contra o endereço de origem que um pacote transporta após a tradução, pelo que uma regra escrita para o conjunto de clientes pode deixar de corresponder em silêncio assim que o mascaramento é ativado.
Quando os clientes só precisam de alcançar serviços dentro do cluster, terminar a VPN no VIP do serviço é um desenho mais pequeno do que encaminhar para a rede de pods. Também mantém o conjunto de clientes totalmente fora da tabela de rotas do CNI.
Isolar o salto que falha do cliente
Trabalhe para fora e pare no primeiro passo silencioso. Comece pelo par da VPN, depois experimente o endereço de algum pod, depois um VIP de serviço e só depois um destino público. Cada sucesso desloca a fronteira um salto, e cada falha nomeia a camada que vale a pena inspecionar. Após qualquer implementação, entre com exec e leia o sysctl de novo, porque um pod substituído parte do valor predefinido da imagem e uma correção que nunca esteve no manifesto já desapareceu.
A documentação do Kubernetes sobre a utilização de sysctls num cluster lista os nomes que podem ser definidos através do contexto de segurança do pod, e é o primeiro lugar a consultar quando o admission recusa a alteração.