Os contentores respondem na LAN do escritório e deixam de responder no momento em que a VPN da empresa liga. Nada mudou no contentor: sem reinício, sem atualização da imagem, sem edição da configuração. O que mudou foi a tabela de rotas do anfitrião, onde uma rota acrescentada pelo túnel passou a cobrir o endereço que o Docker atribuiu. Antes de editar uma sub-rede ou pedir uma exclusão da VPN, ponha os dois planos de endereços lado a lado, porque as reparações custam quantidades de tempo muito diferentes.

Leia a rota antes de alterar a configuração

Comece pela rede que o Docker realmente construiu. O bloco IPAM é o plano de endereços, e a sub-rede é a linha que importa:

docker network inspect project_default --format '{{json .IPAM.Config}}'
# [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]

Por omissão, o Compose atribui um /16 a cada novo projeto, pelo que dois serviços podem caber num intervalo de 65,536 endereços. É esta largura que torna as sobreposições comuns: um /16 escolhido ao acaso é suficientemente grande para engolir um site a que chega pelo túnel, e ninguém dá por isso até o túnel subir.

Compare depois a seleção de rota para um endereço de contentor, uma vez com o túnel desligado e outra com ele ligado:

ip route get 172.18.0.7
# 172.18.0.7 dev br-3f2a9c src 172.18.0.1

Duas formas de resposta levam a sítios diferentes. Se a saída nomear tun0, wg0 ou outro dispositivo de túnel, o prefixo da VPN é pelo menos tão específico como a rota da ponte e o pacote nunca chega ao contentor. Se continuar a nomear a ponte, o anfitrião alcança o contentor e a sua falha está na filtragem, no DNS ou num caminho de retorno que falha apenas num sentido.

Porque é fácil atingir uma colisão de /16

O Docker retira as redes de ponte dos conjuntos privados, e o conjunto predefinido vem do bloco 172.16.0.0/12 em fatias de /16. As redes empresariais usam frequentemente o mesmo espaço privado, e uma VPN de acesso remoto empurra esses prefixos para todos os clientes que se ligam. Quando ambos os lados acreditam ser donos de 172.18.0.0/16, uma tabela de rotas contém duas reivindicações contraditórias.

O comprimento do prefixo decide o vencedor, não a ordem das rotas. Um túnel que empurra 10.0.0.0/8 não vence uma rota de ponte de 172.18.0.0/16, porque /8 é menos específico. Um túnel que empurra 172.18.0.0/16, ou 172.18.5.0/24 dentro dele, vence. É por isso que um portátil funciona na rede do escritório e falha no Wi-Fi do hotel com a mesma VPN: o cliente recebe prefixos diferentes.

A mesma tabela governa o sentido inverso. Um contentor que precisa de um anfitrião interno em 10.8.4.20 envia o pacote para a sua gateway de ponte, e o anfitrião encaminha-o depois para o túnel. Esse caminho normalmente funciona porque o anfitrião faz o NAT, mas falha quando o lado empresarial aceita apenas o endereço VPN do portátil, ou quando a filtragem de caminho inverso descarta a resposta assimétrica.

Compare as três reparações

Há apenas três saídas, e diferem em quem as tem de aprovar e no que se quebra enquanto aplica uma.

RepairWho owns itWhat it costs
Renumber the container networkYouRecreates the network, restarts services, invalidates literal addresses in configs and scripts
Exclude your range from the tunnelNetwork teamChanges what every laptop sees, needs a change request and a client profile update
Reach the service through a published portYou and the clientOne port only, no direct container-to-container routing across the tunnel

A renumeragem é a única reparação que consegue concluir sem um pedido formal, e é por isso que a maioria dos textos fica por aí. É barata num projeto jovem e cara assim que uma ferramenta de migração ou uma cadeia de ligação guardada aponta para um endereço literal. Conte essas referências antes de editar o YAML, não depois.

Uma exclusão do túnel é a resposta mais limpa quando a sobreposição está num intervalo que a sua empresa realmente possui. É também a mais lenta e é inútil para um prestador que não pode alterar o perfil do cliente. Uma porta publicada ou um pequeno proxy inverso evitam a discussão: o cliente fala com um endereço que o túnel já encaminha corretamente, e só essa porta tem de estar acessível.

Fixe um intervalo de propósito

Para um único projeto, declare a sub-rede no ficheiro compose para que o projeto deixe de retirar do conjunto partilhado:

networks:
  default:
    ipam:
      config:
        - subnet: 10.201.7.0/24

Retire esse intervalo de um registo que a sua equipa mantém, não de um exemplo que encontrou a pesquisar. Verifique-o contra os prefixos que a VPN empurra, as LAN domésticas de onde as pessoas trabalham e as outras redes de contentores no mesmo anfitrião. Depois planeie a recriação, porque uma rede existente mantém a sub-rede antiga: docker compose down, e depois up -d. O docker network rm recusa enquanto houver pontos finais ligados, e qualquer entrada ipv4_address no ficheiro tem de ficar dentro do novo intervalo.

Para redes que o Docker cria sozinho, defina um conjunto do daemon em /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.201.0.0/16", "size": 24 },
    { "base": "10.202.0.0/16", "size": 24 }
  ]
}

O conjunto aplica-se às redes criadas depois de o daemon reiniciar, e as pontes que já existem mantêm os seus endereços. Isso é cómodo e fácil de esquecer, por isso trate um projeto antigo como ainda sobreposto até alguém recriar a sua rede. A documentação de redes do motor do Docker descreve as regras de atribuição, caso precise de defender o conjunto numa revisão.

A ponte Linux e o Docker Desktop falham de forma diferente

Num anfitrião Linux, os contentores ficam em pontes no espaço de nomes do anfitrião. Uma única tabela decide tudo, pelo que a comparação acima é a história completa: leia a rota da ponte, leia a rota do túnel e veja qual vence para um endereço de contentor real.

O Docker Desktop coloca os contentores atrás de uma VM com o seu próprio NAT. O anfitrião não tem rota nenhuma para o endereço do contentor; chega ao serviço através de uma porta publicada, normalmente ligada a 127.0.0.1. Um cliente VPN pode, portanto, quebrar o serviço sem qualquer sobreposição de sub-redes: instalando uma rota para o intervalo interno da VM ou bloqueando o tráfego de loopback enquanto o túnel está ligado. Se ip route get no anfitrião indicar que não há rota para o endereço do contentor, isso é o esperado e não o erro.

A consequência prática é onde olha primeiro. Em Linux, comece pela tabela de rotas. No Desktop, comece pela porta publicada e pelas regras de firewall do cliente VPN, e verifique depois se a sub-rede interna da VM colide com algo que o túnel empurra.

Verifique com o túnel ligado

Volte a ligar a VPN duas vezes antes de considerar o problema resolvido, porque um perfil que empurra rotas ao ligar e as retira ao desligar pode parecer correto após a primeira tentativa. Depois execute as verificações que o cliente realmente executa:

  1. ip route get a partir do anfitrião, com o endereço real do contentor.
  2. Um pedido a partir de outro contentor, para provar que o serviço em si está saudável.
  3. curl -sv http://127.0.0.1:8080/ a partir do cliente com o túnel ligado, e depois traceroute -n para o endereço do contentor a partir de uma máquina na LAN.
  4. sysctl net.ipv4.conf.all.rp_filter se o pedido chegar ao anfitrião mas a resposta desaparecer.

Por fim, escreva o intervalo escolhido no mesmo registo que usou para o escolher, com a data e o projeto que o possui. A próxima sobreposição virá de um projeto que ninguém documentou, e a única defesa é o intervalo ter sido reservado de propósito em vez de sorteado.

Artigos relacionados