Браузер на хосте показывает адрес выхода VPN, контейнер сообщает о вашем домашнем подключении, и кто-то называет это утечкой. Ничего не утекло. Туннель хоста — это набор интерфейсов, маршрутов и правил, принадлежащих сетевому пространству имён хоста, а контейнер на мосту — другое пространство имён со своим маршрутом по умолчанию. Я сидел на стеке Compose, где клиент VPN был подключён неделями, а ни один пакет контейнера так в него и не попал.

Проверьте пространство имён до значка в трее

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

Два поддерживаемых приёма выводят вас из изоляции. --network host помещает контейнер в пространство имён хоста, поэтому он наследует маршруты хоста и, следовательно, туннель. --network container:vpn разделяет пространство имён другого контейнера, и именно так большинство схем со sidecar отправляют рабочую нагрузку через контейнер VPN. Оба варианта — явный выбор; обычно только один из них тот, что имел в виду развёртывающий.

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

Проследите пакет за пределы моста

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

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

Docker сам поддерживает эти правила через тот бэкенд межсетевого экрана, с которым был настроен демон. Правила, написанные вручную вне поддерживаемого хука для пересылаемого трафика, DOCKER-USER, могут быть переупорядочены или удалены при перезапуске демона или обновлении хоста. Документация Docker по фильтрации пакетов показывает порядок цепочек; практический смысл в том, что каждому правилу нужен владелец и путь перезагрузки, который вы действительно проверили.

Проверяйте две системы по отдельности

Доказательства из одного пространства имён ничего не говорят о другом. Запрос, выполненный внутри контейнера VPN, доказывает, что туннель работает для этого контейнера. Это не доказательство о хосте, и та же команда, выполненная из контейнера с --network host, не является доказательством о мосте.

# host namespace
ip route get 1.1.1.1
ip -brief addr show

# a fresh container on the same bridge network
docker run --rm --network appnet alpine sh -c 'ip route; cat /etc/resolv.conf; ip -6 route'

Затем повторите тест внутри настоящего контейнера приложения, а не похожего образа. Приложение может нести дополнительный маршрут, переменную окружения прокси или другой резолвер, и каждое из этого меняет ответ. Каждый раз проверяйте разрешение имён и второе семейство адресов: контейнеры на мостах, определённых пользователем, разрешаются через встроенный резолвер Docker по адресу 127.0.0.11, а маршрут по умолчанию IPv6 на мосту может вынести трафик наружу, пока вы заняты тестированием туннеля только для IPv4.

Прочитайте закономерность, прежде чем трогать учётные данные

Когда хост работает, а контейнер нет, причина почти всегда структурная, и смена ключей или переустановка клиента тратит впустую полдня. Сначала разберите симптом, и исправление обычно называет себя само.

What you seeUsual causeFirst check
Host exits through the VPN, container shows the ISP addressThe container never entered the tunnelDefault route inside the container
Container works until the daemon restarts, then stopsRules outside the supported hook, or a changed backendDaemon's reported firewall backend and DOCKER-USER
Container loses internal hosts after the tunnel comes upVPN prefix overlaps the Docker bridge subnetip route on the host, then docker network inspect
Small replies arrive, large ones hangTunnel MTU or missing MSS clampingLink MTU on both ends of the tunnel
Container falls back to the ISP address when the tunnel dropsNothing enforces the kill switch for forwarded trafficStop the tunnel with a request in flight

Строка о перекрытии заслуживает отдельного предложения. Профиль VPN, который проталкивает маршрут для 172.17.0.0/16 или для 10.0.0.0/8, случайно содержащего префикс вашего моста, заставляет туннель претендовать на подсеть контейнеров, и ответы на транслированные адреса пойдут не туда или исчезнут. Перенумерация сети Docker обычно чище накопления исключений.

Сделайте покрытие явным, а затем намеренно сломайте его

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

Как бы то ни было, важен тест на отказ. Остановите туннель, когда запрос контейнера в пути, и посмотрите, куда уходит пакет. Если он возвращается на прямой путь, покрытие никогда не применялось, потому что kill switch клиента управляет собственным трафиком клиента, а не пересылаемыми пакетами с моста. Затем перезапустите демон и хост и подтвердите результат второй раз; именно в этот момент написанные вручную правила исчезают.

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

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