Je Compose-service loopt in een time-out tegen een API die de browser op dezelfde machine zonder klagen opent. Dat verschil is de aanwijzing: het verzoek dat werkt, verlaat een host met een VPN-tunnel, en het verzoek dat blijft hangen, verlaat een container met een eigen netwerkstack. Niets in de container kent de routes van de tunnel, dus de privébestemming blijft onbereikbaar hoe de naam ook wordt geschreven. De oplossing is een bewuste sprong via de host, niet een slimmer routetabel.

Begin met waarom de twee paden verschillen en beslis daarna waar de tunnel moet leven.

Waarom de host werkt en de container niet

De tunnel opzetten geeft de host een interface, een set routes en meestal een resolver-regel van de VPN. Een container erft daar niets van: hij start met één interface op de bridge van de engine en een standaardroute die naar die bridge wijst. Routes die op de host zijn geïnstalleerd, zijn onzichtbaar voor alles wat binnen die naamruimte draait.

Daarom lijken de symptomen inconsistent in plaats van gewoon kapot. De host lost de privénaam op en verbindt, omdat zowel de naam als de route daar bestaan. De container stuurt zijn pakket naar een privéadres dat niets op het bridgepad kan bereiken, en het verzoek loopt in een time-out. De naam aan het hosts-bestand van de container toevoegen verandert niets, omdat het adres zelf onbereikbaar is vanaf waar de container zit.

Bereik de host van binnenuit de container

Een proces in de container dat localhost kiest, kiest de container. Docker Desktop publiceert host.docker.internal als naam die terugwijst naar de machine die de engine draait; Linux-engines laten hem ongedefinieerd tenzij de service erom vraagt, en dat is precies wat de mapping hieronder doet. Daarmee richt de applicatie zich op een naam en een poort in plaats van het privéadres.

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

De tunnel blijft aan de hostkant van de sprong, en de poort in die variabele is degene waarop de proxy luistert. Een container die de tunnel verliest, faalt nu op één bekende sprong in plaats van een route te proberen die hij nooit had.

Kies waar de tunnel leeft

Er zijn drie vormen mogelijk voor een Compose-service die een privé-API nodig heeft, en ze verschillen in welk proces de tunnel bezit. Een VPN-client kan in de container-image draaien, wat vereist dat een tunnelapparaat wordt blootgesteld en de bijbehorende netwerkbeheermogelijkheid wordt gegeven. De applicatie kan de netwerknaamruimte van een aparte tunnelcontainer delen, zodat beide processen één stack en één routetabel zien. Of de tunnel kan op de host blijven, met een smalle doorgever van de host naar de container.

De eerste twee trekken profielbestanden, herverbindingsgedrag en resolverbeleid het project in, en maken elke service in het bestand afhankelijk van de gezondheid van de tunnel. Dat is veel koppeling voor één API-aanroep. Voor een ontwikkelstack die één intern eindpunt leest, is de doorgever aan de hostkant de kleinste wijziging die de inloggegevens toch laat waar het platformteam ze beheert.

Publiceer één smal pad op de host

Begin met wat de doorgever erft: draai hem onder het account dat de tunnel bezit, en zijn uitgaande sockets nemen de routes van de host en de resolver van de host over. Eén nginx location-blok dekt één pad.

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

Behandel het adres en de poort als plaatsvervangers en zet het paar neer waarop de API echt luistert. Elke volgende upstream verdient zijn eigen location-blok, en elk blok zou alleen de paden moeten toelaten die deze service aanroept, zodat de proxy geen algemene deur naar het privénetwerk wordt. Bind de luisteraar aan het bridge-adres voor clients die via host-gateway binnenkomen, aan loopback alleen wanneer de client de netwerknaamruimte van de host deelt, en nooit aan 0.0.0.0. Kies ook een poort die nog vrij is: een nummer dat al bezet is, laat de proxy niet luisteren terwijl de container blijft time-outen, wat niet te onderscheiden is van de netwerkstoring waarmee je begon.

Poorten, namen en de TLS-beslissing

Naamresolutie splitst op dezelfde manier als routing. De publieke opzoekingen van de container gaan via de resolver van de engine, dus een privénaam die op de host oplost, lost niet op in de container. De proxy is het onderdeel dat de privénaam oplost, omdat hij de resolver van de host erft.

TLS is de beslissing die wordt uitgesteld en daarna de volgende fout verklaart. Als de proxy bytes ongewijzigd doorgeeft, ziet de container het certificaat van de API zelf en heeft hij de interne CA in zijn truststore nodig. Als de proxy TLS beëindigt, verhuist dat vertrouwen naar de host en begint de applicatie links te bouwen op basis van de host-header die hij ontvangt. Kies één opzet en pas die toe op elke resource, want een omleiding van de andere wijst naar een hostnaam die de container niet kan oplossen.

Controleer vanuit de service

Stuur het verzoek vanuit de container, niet vanaf de host: een gewone curl tegen het proxypad, daarna de eigen healthcheck van de applicatie. Als de proxy antwoordt maar de applicatie nog steeds faalt, vergelijk dan drie dingen: de basis-URL die hij bouwt, elke proxyvariabele in zijn omgeving en de CA die hij vertrouwt. Allemaal zitten ze in de omgeving en logs van de container, en één ervan repareren laat de VPN met rust.

Schrijf de gekozen opzet in de README van het project zolang die vers is. Anders besteedt de volgende persoon die deze time-out tegenkomt dezelfde middag aan opnieuw ontdekken dat de tunnel op de host leeft.

Gerelateerde artikelen