Um contêiner que não alcança nada atrás da VPN corporativa normalmente vive em um espaço de nomes de rede diferente do túnel. As rotas, os servidores DNS e as marcas do firewall do cliente do host pertencem à pilha do host; um contêiner em rede bridge tem a sua. Esse único fato explica a maioria dos relatos que chegam a uma equipe de plataforma, e também é a razão pela qual «a VPN está quebrada» e «o Docker ignora a VPN» podem estar ambas erradas ao mesmo tempo. Duas configurações muito diferentes se escondem atrás de um sintoma, então identifique qual é a sua antes de mudar qualquer ajuste.

Descubra a quem pertence o espaço de nomes do túnel

No Docker Desktop para macOS ou Windows, os contêineres rodam dentro de uma máquina virtual Linux. Um cliente VPN instalado no desktop cria um túnel no sistema operacional do desktop, e a máquina virtual nunca o vê. Nenhuma configuração do contêiner corrige isso, porque a interface de que o contêiner precisaria não existe de forma alguma no espaço de nomes de rede dele.

Em um host Linux o túnel vive no espaço de nomes do host, e os contêineres em bridge normalmente o alcançam pela tabela de roteamento do host, com NAT de origem aplicado na saída. Quando esse caminho falha, a causa costuma ser política de encaminhamento, filtragem de caminho reverso ou uma sub-rede sobreposta, e não uma rota ausente. Confirme em qual dos dois mundos você está antes de ler adiante:

docker info --format '{{.OperatingSystem}}'
docker run --rm --net=host alpine ip route | head -5
docker run --rm alpine ip route | head -5

A diferença entre essas duas tabelas de roteamento é todo o problema em uma tela. Se a visão do host contém a interface do túnel e a visão da bridge não, você está diante de isolamento de espaços de nomes, e todo o resto é uma escolha sobre como atravessá-lo.

A rede do host é um teste, não um projeto

Executar um contêiner com a rede do host, que o Docker documenta como compartilhamento do espaço de nomes de rede do host, faz com que ele herde diretamente a interface do túnel e suas rotas. Se a mesma requisição então funcionar, a teoria das rotas está confirmada e a falha é isolamento de espaços de nomes, não credenciais, DNS ou uma regra de acesso.

docker run --rm --net=host curl -sS -o /dev/null -w '%{http_code}\n' https://api.internal/health

Mantenha isso como diagnóstico. A rede do host abre mão da publicação de portas e da maior parte do isolamento que você queria de um contêiner, e no Docker Desktop «host» significa a máquina virtual, não o desktop, então um sucesso ou fracasso ali pode enganá-lo se você esquecer qual host estava endereçando.

Um dispositivo tun e os privilégios que vêm com ele

Um cliente VPN dentro do contêiner precisa de um dispositivo de túnel e de permissão para configurá-lo. O atalho comum é --privileged, que concede todos os dispositivos e afrouxa a política de segurança em todo o contêiner. Isso é muito mais do que esta tarefa exige, e é difícil justificar em uma revisão.

sudo modprobe tun
docker run --rm -it --device /dev/net/tun --cap-add NET_ADMIN myvpn-client

A forma mais estreita mapeia o único nó de dispositivo e adiciona apenas a capacidade de administração de rede. Carregue primeiro o módulo no host para que o nó exista. Se o contêiner iniciar e o cliente informar que não consegue abrir o dispositivo de túnel, verifique o mapeamento antes de suspeitar do perfil, porque esse erro costuma ser literal.

Este projeto também significa que o cliente roda por contêiner. É a resposta certa quando cada carga de trabalho precisa do próprio túnel, e a errada quando você só queria que o túnel do host fosse visível, o que a equipe de plataforma provavelmente presumiu que você fazia.

O NAT da bridge muda a origem que a VPN vê

Com a bridge padrão, o tráfego de saída do contêiner é traduzido para o endereço do host. Por isso, atrás de uma VPN, o lado corporativo vê o endereço do túnel do host e não o do contêiner, as regras de acesso por origem se comportam como se o host tivesse feito a requisição, e dois contêineres ficam indistinguíveis para o servidor VPN.

macvlan ou ipvlan entrega ao contêiner um endereço LAN, o que ajuda na descoberta da LAN e faz o contêiner parecer um par no segmento. Também contorna completamente o túnel do host, então um contêiner assim não alcançará prefixos só-VPN a menos que a VPN também seja acessível a partir dessa LAN.

As duas formas são legítimas; respondem a requisitos diferentes. Se o requisito é «este contêiner precisa alcançar um serviço só-VPN», você escolhe entre rotear pelo host e terminar o túnel dentro do contêiner. Anote essa escolha antes da implantação, não depois da primeira requisição falha. Escolha o que escolher, registre o endereço de origem que a ponta remota verá, porque é isso que as regras de acesso e os logs dela mostrarão. Um projeto que não consegue declarar esse endereço não está pronto.

O DNS segue o daemon, não o túnel

Um contêiner recebe sua configuração de resolvedor na inicialização, copiada do daemon ou da imagem. Os endereços que a VPN envia após conectar chegam tarde demais para essa cópia, e são enviados ao resolvedor do host e não para dentro do contêiner.

Defina o resolvedor explicitamente para o serviço, ou aponte-o para um resolvedor interno acessível pelo túnel. Um contêiner que resolve um nome privado para um endereço público costuma usar um resolvedor público, não sofre de uma rota quebrada, e a correção pertence ao arquivo compose ou à configuração do daemon.

O mesmo raciocínio se aplica à questão do endereço de saída. Se cada contêiner precisa aparecer de um endereço VPN diferente, um único túnel do host não pode entregar isso porque o NAT os reduz a uma única origem. As formas viáveis são um cliente no espaço de nomes de cada contêiner ou um sidecar que possui o túnel e compartilha a pilha, e ambos mudam como a aplicação é endereçada.

Lista de verificação antes de fechar o chamado: identifique o espaço de nomes que possui o túnel, prove o caminho com a rede do host, mapeie apenas o dispositivo tun necessário, lembre o endereço de origem após o NAT, defina o resolvedor explicitamente e trate endereços de saída por contêiner como uma decisão de arquitetura e não como uma flag.

Artigos relacionados