O teu servizo de Compose esgota o tempo contra unha API que o navegador da mesma máquina abre sen queixa. Esa diferenza é a pista: a petición que funciona sae dun host cun túnel VPN, e a que se colga sae dun contedor coa súa propia pila de rede. Nada dentro do contedor coñece as rutas do túnel, así que o destino privado segue inalcanzable se escriba como se escriba o nome. A solución é un salto deliberado a través do host, non unha táboa de rutas máis astuta.

Comeza por por que difiren os dous camiños e logo decide onde debe vivir o túnel.

Por que o host funciona e o contedor non

Levantar o túnel dálle ao host unha interface, un conxunto de rutas e, normalmente, unha entrada de resolutor que achega a VPN. Un contedor non herda nada diso: arrinca cunha interface na ponte do motor e unha ruta por defecto que apunta a esa ponte. As rutas instaladas no host son invisibles para todo o que se executa dentro dese espazo de nomes.

Por iso os síntomas parecen incoherentes e non simplemente rotos. O host resolve o nome privado e conecta, porque alí existen tanto o nome como a ruta. O contedor envía o seu paquete a un enderezo privado que nada do camiño da ponte pode alcanzar, e a petición caduca. Engadir o nome ao ficheiro hosts do contedor non cambia nada, porque o propio enderezo é inalcanzable desde onde está o contedor.

Chega ao host desde dentro do contedor

Un proceso dentro do contedor que marca localhost está marcando o contedor. Docker Desktop publica host.docker.internal como un nome que apunta de volta á máquina que executa o motor; os motores de Linux déixano indefinido agás que o servizo o pida, que é o que fai a asignación de abaixo. Con iso, a aplicación apunta a un nome e un porto en lugar do enderezo privado.

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      INTERNAL_API_HOST: "host.docker.internal:8666"

O túnel queda no lado do host do salto, e o porto desa variable é o que usará o proxy para escoitar. Un contedor que perde o túnel agora falla nun único salto coñecido en vez de tentar unha ruta que nunca tivo.

Elixe onde vive o túnel

Hai tres formas posibles para un servizo de Compose que necesita unha API privada, e difiren en que proceso posúe o túnel. Un cliente VPN pode executarse dentro da imaxe do contedor, o que esixe expor un dispositivo de túnel e conceder a capacidade de administración de rede que o acompaña. A aplicación pode compartir o espazo de nomes de rede dun contedor de túnel aparte, de modo que ambos procesos ven unha pila e unha táboa de rutas. Ou o túnel pode quedar no host, cun reenviador estreito do host ao contedor.

As dúas primeiras arrastran ficheiros de perfil, comportamento de reconexión e política de resolutor ao proxecto, e fan que cada servizo do ficheiro dependa da saúde do túnel. É demasiado acoplamento para unha soa chamada á API. Para unha pila de desenvolvemento que le un único punto final interno, o reenviador do lado do host é o cambio máis pequeno que aínda mantén as credenciais onde as xestiona o equipo de plataforma.

Publica un único camiño estreito no host

Comeza polo que herda o reenviador: execútao coa conta que posúe o túnel e os seus sockets de saída tomarán as rutas do host e o resolutor do host. Un bloque location de nginx cobre un camiño.

server {
    listen 8666;
    location /search/ {
        proxy_pass http://10.20.30.40:8090/;
        proxy_set_header Host internal-api.example;
        proxy_read_timeout 60s;
    }
}

Trata o enderezo e o porto como substitutos e pon o par no que a API escoita de verdade. Cada upstream adicional merece o seu propio bloque location, e cada bloque debería admitir só os camiños que chama este servizo, para que o proxy non se converta nunha porta xeral á rede privada. Ata o oínte ao enderezo da ponte para os clientes que chegan por host-gateway, a loopback só cando o cliente comparte o espazo de nomes de rede do host, e nunca a 0.0.0.0. Elixe tamén un porto que siga libre: un número xa ocupado deixa o proxy sen escoitar mentres o contedor segue caducando, o que non se distingue do fallo de rede co que comezaches.

Portos, nomes e a decisión de TLS

A resolución de nomes divídese igual que o encamiñamento. As consultas públicas do contedor pasan polo resolutor do motor, así que un nome privado que resolve no host non resolverá dentro do contedor. O proxy é o compoñente que resolve o nome privado, porque herda o resolutor do host.

TLS é a decisión que se apraza e logo explica o seguinte fallo. Se o proxy reenvía os bytes intactos, o contedor ve o certificado propio da API e necesita a CA interna no seu almacén de confianza. Se o proxy remata TLS, esa confianza pasa ao host e a aplicación comeza a construír ligazóns desde a cabeceira host que recibe. Elixe un só arranxo e aplícao a cada recurso, porque unha redirección emitida polo outro apunta a un nome de host que o contedor non pode resolver.

Verifica desde dentro do servizo

Envía a petición desde o contedor, non desde o host: un curl simple contra o camiño do proxy e logo a comprobación de saúde da propia aplicación. Cando o proxy responde pero a aplicación segue fallando, compara tres cousas: a URL base que constrúe, calquera variable de proxy no seu contorno e a CA na que confía. Todas están no contorno e nos rexistros do contedor, e arranxar calquera delas deixa a VPN en paz.

Escribe o arranxo elixido no README do proxecto mentres está fresco. Se non, a próxima persoa que tropece con este tempo esgotado pasará a mesma tarde redescubrindo que o túnel vive no host.

Artigos relacionados