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.

SymptomLikely cause
Web UI unreachable after the changePort mapping declared on the joining container instead of the sidecar
Both containers show the provider's exit addressThe direct service was attached to the sidecar network
Internal names stop resolvingResolver settings live on the host, not in the shared namespace
Health check fails but the app worksThe 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.

Artigos relacionados