Один хост, две рабочие нагрузки и требование, которое их разделяет: клиент загрузки, который должен выходить через коммерческий VPN, и интерфейс управления, который должен продолжать использовать соединение хоста. Установка VPN-клиента на хосте сразу нарушает требование, потому что тогда обе нагрузки делят один выход. Схема, которая действительно работает, — перенести туннель в собственный контейнер и разрешить только тем нагрузкам, которым он нужен, присоединиться к сетевому пространству имён этого контейнера. Всё остальное сохраняет обычный путь Docker, а детали, которые следуют далее, — следствия того, кто владеет пространством имён.

Почему общее пространство имён меняет правила

Контейнер, присоединяющийся к пространству имён другого, не получает собственный сетевой стек. Он видит те же интерфейсы, ту же таблицу маршрутизации и тот же loopback, что и контейнер, к которому он присоединился, и именно поэтому схема полезна: нагрузка разрешает имена и подключается обычным образом, а туннель под ней перехватывает трафик без ведома приложения. Цена в том, что у каждой сетевой настройки теперь один владелец, и присоединяющийся контейнер — не он.

Это правило владения отвечает на большинство вопросов о конфигурации ещё до того, как их задают. Если настройка живёт в сетевом стеке, она принадлежит боковому контейнеру. Если она описывает процесс, она остаётся у нагрузки.

Loopback — та часть, которая удивляет людей. Внутри присоединяющегося контейнера localhost разрешается в loopback бокового контейнера, а не в собственный приватный, поэтому служба, которая привязывается к 127.0.0.1 в ожидании изоляции, теперь делит этот адрес с контейнером туннеля.

Публикуйте порты на контейнере, который владеет пространством имён

Сопоставление портов принадлежит владельцу пространства имён, даже когда слушающий процесс работает в другом контейнере. Объявите сопоставление на боковом контейнере, а нагрузка пусть сохранит собственную конфигурацию процесса нетронутой. Сопоставление на присоединяющейся службе не может вступить в силу — отклонит ли его движок или тихо проигнорирует в зависимости от того, как запущен контейнер, — и поэтому интерфейс, который был доступен мгновение назад, исчезает, как только схема применена.

services:
  gluetun:
    image: qmcgaw/gluetun
    cap_add: [NET_ADMIN]
    ports: ["8080:8080"]
  client-vpn:
    image: example/client
    network_mode: "service:gluetun"
  client-direct:
    image: example/client
    ports: ["8081:8080"]

Читайте этот блок как три решения, а не три службы. Боковой контейнер владеет туннелем и опубликованным портом; присоединяющийся клиент вообще не объявляет сеть; прямой клиент сохраняет собственное сопоставление в сети по умолчанию. Возможность NET_ADMIN — это то, что позволяет боковому контейнеру создавать интерфейсы и менять маршруты, и она же причина, по которой этому контейнеру нужно больше привилегий, чем нагрузкам, которым он служит.

Служба, которая остаётся прямой, сохраняет собственную сеть

Нагрузка, которая не должна проходить через туннель, сохраняет мост по умолчанию или собственную сеть, определённую пользователем, со своим опубликованным портом и своим выходом. Соблазнительно подключить её к сети бокового контейнера, чтобы два контейнера могли общаться по имени, но этот единственный шаг тихо переносит её трафик в туннель. Если им нужно достигать друг друга, постройте отдельную сеть, определённую пользователем, подключите к ней оба и оставьте боковой контейнер единственным путём наружу.

Держите два вопроса раздельно: какие контейнеры могут достигать друг друга и какой куда выходит. Их слияние — самый распространённый способ, которым этот дизайн снова превращается в VPN для всего хоста.

Матрица отказов на первый час

Большинство первых попыток терпят неудачу одним из четырёх способов, и у каждого своя причина. Проверяйте их в этом порядке, потому что первые два объясняют почти каждый отчёт.

SymptomLikely cause
Web UI unreachable after the changePort mapping declared on the joining container instead of the sidecar
Both containers show the provider's exit addressThe direct service was attached to the sidecar network
Internal names stop resolvingResolver settings live on the host, not in the shared namespace
Health check fails but the app worksThe check probes localhost, which now means the sidecar

Четвёртая строка — та, которую люди ошибочно диагностируют как ошибку приложения. Проверка, написанная против собственного loopback нагрузки, теперь тестирует контейнер туннеля, поэтому она либо терпит неудачу, либо сообщает о совершенно неверной службе.

Когда одному имени хоста нужен туннель

Иногда полный туннель — это слишком много: одно внутреннее имя должно проходить через корпоративный VPN, пока локальный сокет или соединение дисплея продолжает работать. Полный туннель внутри контейнера ломает этот путь и всё, что адресовано хосту, поэтому маршрутизируйте только тот префикс, которому это нужно. Обёртка раздельной маршрутизации вокруг клиента делает это: отправляет указанный хост через туннель и оставляет прежний маршрут нетронутым для всего остального.

Выбирайте это, когда нужно достичь только одной службы и вы не пытаетесь переместить весь трафик контейнера. Это другой инструмент, нежели боковой контейнер, и он отвечает на другой вопрос.

Проверьте, а затем сделайте так, чтобы это пережило перезапуск

Спросите каждый контейнер, с какого адреса он, судя по всему, приходит. Тот, что в туннеле, должен вывести адрес провайдера, а прямой — выход хоста. Одинаковые ответы означают, что прямая служба унаследовала неверное пространство имён или VPN на уровне хоста всё ещё перехватывает всё, и эти две правки различны. Запускайте проверку снова после каждой правки compose, потому что одного скопированного блока службы достаточно, чтобы вернуть проблему.

Три вещи обычно ломаются позже. DNS следует за владельцем пространства имён, поэтому настройки распознавателя и домена поиска принадлежат боковому контейнеру, а не хосту. VPN с переадресацией портов меняет свой порт прослушивания при переподключении, поэтому переадресованный порт провайдера и порт прослушивания на стороне контейнера нужно записать обратно в конфигурацию клиента или в сопоставление портов, обычно небольшим обновлятором, который читает API провайдера. И любая проверка, написанная против localhost, теперь спрашивает контейнер туннеля, а не нагрузку.

Запишите схему, включая то, какой контейнер владеет пространством имён и какие порты ему принадлежат. Следующий человек добавит службу, скопирует неверный блок и воспроизведёт первый отказ из этой статьи.

Похожие статьи