Вашата услуга в Compose изтича по време на заявка към API, което браузърът на същата машина отваря без оплакване. Тази разлика е уликата: заявката, която работи, излиза от хост с VPN тунел, а заявката, която виси, излиза от контейнер със собствен мрежов стек. Нищо в контейнера не знае маршрутите на тунела, затова частната дестинация остава недостъпна, както и да се изпише името. Поправката е умишлен скок през хоста, а не по-умна таблица за маршрутизация.

Започнете с това защо двата пътя се различават, после решете къде трябва да живее тунелът.

Защо хостът работи, а контейнерът не

Вдигането на тунела дава на хоста интерфейс, набор от маршрути и обикновено запис в резолвера, предоставен от VPN. Контейнерът не наследява нищо от това: той стартира с един интерфейс на моста на двигателя и маршрут по подразбиране, който сочи към този мост. Маршрутите, инсталирани на хоста, са невидими за всичко, което работи в това именно пространство.

Ето защо симптомите изглеждат противоречиви, а не просто счупени. Хостът разрешава частното име и се свързва, защото и името, и маршрутът съществуват там. Контейнерът изпраща пакета си към частен адрес, до който нищо по пътя през моста не може да стигне, и заявката изтича. Добавянето на името във файла hosts на контейнера не променя нищо, защото самият адрес е недостъпен от мястото, където се намира контейнерът.

Достигнете хоста отвътре в контейнера

Процес в контейнера, който се обръща към localhost, се обръща към контейнера. Docker Desktop публикува host.docker.internal като име, което сочи обратно към машината, изпълняваща двигателя; Linux двигателите го оставят недефинирано, освен ако услугата не го поиска, което прави съпоставянето по-долу. С това приложението се насочва към име и порт вместо към частния адрес.

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

Тунелът остава от страната на хоста при този скок, а портът в тази променлива е този, на който проксито ще слуша. Контейнер, който загуби тунела, сега се проваля на един известен скок, вместо да опитва маршрут, който никога не е имал.

Изберете къде да живее тунелът

Възможни са три форми за услуга в Compose, която се нуждае от частно API, и те се различават по това кой процес притежава тунела. VPN клиент може да работи в образа на контейнера, което изисква да се изложи устройство за тунел и да се даде способността за администриране на мрежата, която върви с него. Приложението може да споделя мрежовото именно пространство на отделен контейнер за тунела, така че двата процеса да виждат един стек и една таблица за маршрутизация. Или тунелът може да остане на хоста, с тесен препращач от хоста към контейнера.

Първите две вкарват профилни файлове, поведение при пресвързване и политика на резолвера в проекта и правят всяка услуга във файла зависима от здравето на тунела. Това е много свързване за едно извикване на API. За стек за разработка, който чете една вътрешна крайна точка, препращачът от страната на хоста е най-малката промяна, която все пак оставя удостоверенията там, където екипът на платформата ги управлява.

Публикувайте един тесен път на хоста

Започнете от това какво наследява препращачът: стартирайте го под акаунта, който притежава тунела, и изходящите му сокети поемат маршрутите на хоста и резолвера на хоста. Един блок location в nginx покрива един път.

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

Отнасяйте се към адреса и порта като към заместители и ги заменете с двойката, на която API всъщност слуша. Всеки следващ upstream заслужава свой блок location и всеки блок трябва да допуска само пътищата, които тази услуга вика, за да не може проксито да стане обща врата към частната мрежа. Вържете слушателя към адреса на моста за клиенти, които идват през host-gateway, към loopback само когато клиентът споделя мрежовото именно пространство на хоста, и никога към 0.0.0.0. Изберете и порт, който все още е свободен: зает номер оставя проксито да не слуша, докато контейнерът продължава да изтича, което е неразличимо от мрежовата повреда, с която започнахте.

Портове, имена и решението за TLS

Разрешаването на имена се разделя по същия начин като маршрутизацията. Публичните търсения на контейнера минават през резолвера на двигателя, затова частно име, което се разрешава на хоста, няма да се разреши в контейнера. Проксито е компонентът, който разрешава частното име, защото наследява резолвера на хоста.

TLS е решението, което се отлага и после обяснява следващия провал. Ако проксито препраща байтовете непокътнати, контейнерът вижда собствения сертификат на API и се нуждае от вътрешния CA в своето хранилище за доверие. Ако проксито терминира TLS, това доверие се мести на хоста и приложението започва да строи връзки от получения хедър host. Изберете една подредба и я прилагайте към всеки ресурс, защото пренасочване, издадено от другата, сочи към име на хост, което контейнерът не може да разреши.

Проверете отвътре в услугата

Изпратете заявката от контейнера, не от хоста: обикновен curl към пътя на проксито, после собствената проверка за здраве на приложението. Когато проксито отговаря, а приложението все още се проваля, сравнете три неща: базовия URL, който строи, всяка променлива за прокси в средата му и CA, на който се доверява. Всички те са в средата и регистрите на контейнера и поправянето на което и да е от тях оставя VPN на мира.

Запишете избраната подредба в README на проекта, докато е прясна. Иначе следващият човек, който срещне това изтичане, ще прекара същия следобед в преоткриване, че тунелът живее на хоста.

Подобни статии