Docker: encaminhe um contêiner pela VPN e mantenha outro direto
Dê ao túnel o seu próprio contêiner, deixe apenas os serviços que precisam dele compartilhar esse espaço de nomes e depois leve em conta o que segue o proprietário: portas, localhost, DNS.
Um host, duas cargas de trabalho e um requisito que as separa: um cliente de download que precisa sair por uma VPN comercial e uma interface de administração que precisa continuar usando a conexão do host. Instalar o cliente VPN no host viola o requisito de imediato, porque as duas cargas passam então a compartilhar uma única saída. O padrão que realmente funciona é mover o túnel para o seu próprio contêiner e permitir que apenas as cargas que precisam dele entrem no espaço de nomes de rede desse contêiner. Todo o resto mantém o caminho habitual do Docker, e os detalhes que se seguem são consequências de quem é dono do espaço de nomes.
Por que um espaço de nomes compartilhado muda as regras
Um contêiner que entra no espaço de nomes de outro não ganha a sua própria pilha de rede. Ele vê as mesmas interfaces, a mesma tabela de encaminhamento e o mesmo loopback do contêiner em que entrou, e é exatamente por isso que o padrão é útil: a carga resolve e conecta-se normalmente, e o túnel por baixo captura o tráfego sem que a aplicação saiba. O custo é que cada ajuste de rede passa a ter um único dono, e o contêiner que entra não é esse dono.
Essa regra de propriedade responde à maioria das perguntas de configuração antes mesmo de serem feitas. Se um ajuste vive na pilha de rede, pertence ao contêiner lateral. Se descreve o processo, fica com a carga.
O loopback é a parte que surpreende as pessoas. Dentro do contêiner que entra, localhost resolve para o loopback do contêiner lateral, e não para um loopback privado próprio, então um serviço que se liga a 127.0.0.1 à espera de isolamento passa a compartilhar esse endereço com o contêiner do túnel.
Publique as portas no contêiner que possui o espaço de nomes
O mapeamento de portas pertence ao dono do espaço de nomes, mesmo quando o processo à escuta corre no outro contêiner. Declare o mapeamento no contêiner lateral, e deixe a carga conservar a sua própria configuração de processo intacta. Um mapeamento no serviço que entra não pode ter efeito, quer o motor o rejeite, quer o ignore em silêncio consoante a forma como o contêiner é iniciado, e é por isso que uma interface que era acessível há um instante desaparece assim que o padrão é aplicado.
services:
gluetun:
image: qmcgaw/gluetun
cap_add: [NET_ADMIN]
ports: ["8080:8080"]
client-vpn:
image: example/client
network_mode: "service:gluetun"
client-direct:
image: example/client
ports: ["8081:8080"]Leia esse bloco como três decisões em vez de três serviços. O contêiner lateral possui o túnel e a porta publicada; o cliente que entra não declara rede nenhuma; o cliente direto conserva um mapeamento próprio na rede predefinida. A capacidade NET_ADMIN é o que permite ao contêiner lateral criar interfaces e alterar rotas, e é a razão pela qual este contêiner precisa de mais privilégios do que as cargas que serve.
O serviço que fica direto conserva a sua própria rede
A carga que não deve passar pelo túnel conserva a ponte predefinida ou uma rede definida pelo utilizador própria, com a sua própria porta publicada e a sua própria saída. É tentador ligá-la à rede do contêiner lateral para que os dois contêineres possam falar por nome, mas esse único passo desloca o seu tráfego para o túnel sem avisar. Se precisarem de se alcançar, construa uma rede definida pelo utilizador à parte, ligue ambos a ela e deixe o contêiner lateral como a única via de saída.
Mantenha as duas perguntas separadas: que contêineres se podem alcançar e qual sai por onde. Fundi-las é a forma mais comum de este desenho voltar a ser uma VPN para todo o host.
Uma matriz de falhas para a primeira hora
A maioria das primeiras tentativas falha de uma de quatro maneiras, e cada uma tem uma causa diferente. Verifique-as por esta ordem, porque as duas primeiras explicam quase todos os relatos.
| Symptom | Likely cause |
|---|---|
| Web UI unreachable after the change | Port mapping declared on the joining container instead of the sidecar |
| Both containers show the provider's exit address | The direct service was attached to the sidecar network |
| Internal names stop resolving | Resolver settings live on the host, not in the shared namespace |
| Health check fails but the app works | The check probes localhost, which now means the sidecar |
A quarta linha é a que as pessoas diagnosticam mal como um erro da aplicação. Uma sonda escrita contra o loopback próprio da carga está agora a testar o contêiner do túnel, por isso ou falha ou relata um serviço completamente errado.
Quando um único nome de host precisa do túnel
Por vezes um túnel completo é demais: um nome interno tem de passar por uma VPN empresarial enquanto um socket local ou uma ligação de ecrã continua a funcionar. Um túnel completo dentro do contêiner quebra esse caminho e tudo o que é endereçado ao host, por isso encaminhe apenas o prefixo que precisa. Um invólucro de encaminhamento dividido à volta do cliente faz isso, enviando o host nomeado pelo túnel e deixando a rota anterior intacta para todo o resto.
Escolha isto quando só precisa de alcançar um serviço e não está a tentar mover todo o tráfego do contêiner. É uma ferramenta diferente do contêiner lateral e responde a uma pergunta diferente.
Verifique e depois faça com que sobreviva a um reinício
Pergunte a cada contêiner de que endereço parece vir. O que está no túnel deve imprimir o endereço do fornecedor e o direto a saída do host. Respostas idênticas significam que o serviço direto herdou o espaço de nomes errado ou que uma VPN ao nível do host continua a capturar tudo, e essas duas correções são diferentes. Execute a verificação novamente após cada alteração ao compose, porque um único bloco de serviço copiado basta para reintroduzir o problema.
Três coisas tendem a quebrar-se mais tarde. O DNS segue o dono do espaço de nomes, por isso os ajustes de resolvedor e de domínio de pesquisa pertencem ao contêiner lateral e não ao host. Uma VPN com reencaminhamento de portas muda a sua porta de escuta ao reconectar, por isso a porta reencaminhada do fornecedor e a porta de escuta do lado do contêiner têm de ser reescritas na configuração do cliente ou no mapeamento de portas, normalmente por um pequeno atualizador que lê a API do fornecedor. E qualquer sonda escrita contra localhost interroga agora o contêiner do túnel e não a carga.
Escreva o padrão, incluindo qual contêiner possui o espaço de nomes e que portas lhe pertencem. A próxima pessoa acrescentará um serviço, copiará o bloco errado e reproduzirá a primeira falha deste artigo.