Containerul nu ajunge la API-ul disponibil doar prin VPN? Pune un proxy pe gazdă
Tunelul trăiește pe gazdă, așa că containerul nu are rută către adresa privată. Publică un proxy invers îngust și apelează-l ca host.docker.internal.
Serviciul tău Compose ajunge la expirare împotriva unui API pe care browserul de pe aceeași mașină îl deschide fără nicio plângere. Această diferență este indiciul: cererea care funcționează pleacă de pe o gazdă cu tunel VPN, iar cererea care se blochează pleacă dintr-un container cu propria stivă de rețea. Nimic din interiorul containerului nu cunoaște rutele tunelului, așa că destinația privată rămâne inaccesibilă oricum ar fi scris numele. Soluția este un salt deliberat prin gazdă, nu un tabel de rute mai deștept.
Începe cu de ce cele două căi diferă, apoi decide unde ar trebui să trăiască tunelul.
De ce gazda funcționează și containerul nu
Ridicarea tunelului îi dă gazdei o interfață, un set de rute și, de obicei, o intrare de resolver furnizată de VPN. Un container nu moștenește nimic din asta: pornește cu o singură interfață pe puntea motorului și o rută implicită care indică acea punte. Rutele instalate pe gazdă sunt invizibile pentru tot ce rulează în acel spațiu de nume.
De aceea simptomele par inconsistente, nu pur și simplu stricate. Gazda rezolvă numele privat și se conectează, pentru că acolo există atât numele, cât și ruta. Containerul își trimite pachetul către o adresă privată pe care nimic de pe calea punții nu o poate atinge, iar cererea expiră. Adăugarea numelui în fișierul hosts al containerului nu schimbă nimic, pentru că adresa însăși este inaccesibilă din locul unde stă containerul.
Ajunge la gazdă din interiorul containerului
Un proces din container care apelează localhost apelează containerul. Docker Desktop publică host.docker.internal ca un nume care indică înapoi mașina care rulează motorul; motoarele Linux îl lasă nedefinit dacă serviciul nu îl cere, iar asta face maparea de mai jos. Cu asta, aplicația țintește un nume și un port în loc de adresa privată.
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
INTERNAL_API_HOST: "host.docker.internal:8666"Tunelul rămâne pe partea gazdă a saltului, iar portul din acea variabilă este cel pe care proxy-ul va asculta. Un container care pierde tunelul eșuează acum la un singur salt cunoscut, în loc să încerce o rută pe care nu a avut-o niciodată.
Alege unde trăiește tunelul
Pentru un serviciu Compose care are nevoie de un API privat sunt posibile trei forme, și ele diferă prin ce proces deține tunelul. Un client VPN poate rula în imaginea containerului, ceea ce cere expunerea unui dispozitiv de tunel și acordarea capacității de administrare a rețelei care vine cu el. Aplicația poate partaja spațiul de nume de rețea al unui container de tunel separat, astfel încât ambele procese văd o stivă și un tabel de rute. Sau tunelul poate rămâne pe gazdă, cu un redirector îngust de pe gazdă în container.
Primele două aduc fișiere de profil, comportament de reconectare și politică de resolver în proiect și fac fiecare serviciu din fișier dependent de sănătatea tunelului. Este prea mult cuplaj pentru un singur apel de API. Pentru o stivă de dezvoltare care citește un singur endpoint intern, redirectorul de pe partea gazdei este cea mai mică schimbare care tot păstrează credențialele acolo unde le administrează echipa de platformă.
Publică o singură cale îngustă pe gazdă
Începe cu ce moștenește redirectorul: rulează-l cu contul care deține tunelul, iar socketurile lui de ieșire preiau rutele gazdei și resolverul gazdei. Un singur bloc location nginx acoperă o cale.
server {
listen 8666;
location /search/ {
proxy_pass http://10.20.30.40:8090/;
proxy_set_header Host internal-api.example;
proxy_read_timeout 60s;
}
}Tratează adresa și portul ca substituenți și pune perechea pe care API-ul chiar ascultă. Fiecare upstream suplimentar merită propriul bloc location, iar fiecare bloc ar trebui să admită doar căile pe care le apelează acest serviciu, ca proxy-ul să nu devină o ușă generală în rețeaua privată. Leagă ascultătorul de adresa punții pentru clienții care vin prin host-gateway, de loopback doar când clientul partajează spațiul de nume de rețea al gazdei, și niciodată de 0.0.0.0. Alege și un port încă liber: un număr deja ocupat lasă proxy-ul fără să asculte în timp ce containerul continuă să expire, ceea ce nu se poate distinge de defecțiunea de rețea cu care ai început.
Porturi, nume și decizia TLS
Rezolvarea numelor se împarte la fel ca rutarea. Interogările publice ale containerului trec prin resolverul motorului, așa că un nume privat care se rezolvă pe gazdă nu se va rezolva în interiorul containerului. Proxy-ul este componenta care rezolvă numele privat, pentru că moștenește resolverul gazdei.
TLS este decizia care se amână și apoi explică următoarea defecțiune. Dacă proxy-ul transmite octeții neatinși, containerul vede certificatul propriu al API-ului și are nevoie de AC-ul intern în magazinul său de încredere. Dacă proxy-ul termină TLS, acea încredere se mută pe gazdă și aplicația începe să construiască linkuri din antetul host pe care îl primește. Alege o singură aranjare și aplică-o fiecărei resurse, pentru că o redirecționare emisă de cealaltă indică un nume de gazdă pe care containerul nu îl poate rezolva.
Verifică din interiorul serviciului
Trimite cererea din container, nu de pe gazdă: un curl simplu către calea proxy-ului, apoi verificarea de sănătate a aplicației înseși. Când proxy-ul răspunde dar aplicația tot eșuează, compară trei lucruri: URL-ul de bază pe care îl construiește, orice variabilă de proxy din mediul ei și AC-ul în care are încredere. Toate sunt în mediul și în jurnalele containerului, iar repararea oricăreia lasă VPN-ul în pace.
Scrie aranjarea aleasă în README-ul proiectului cât timp este proaspătă. Altfel următoarea persoană care dă peste această expirare va petrece aceeași după-amiază redescoperind că tunelul trăiește pe gazdă.