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

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

Две неисправности, которые выглядят одинаково

Первая — проблема обратного пути. Опубликованный порт транслируется в контейнер, запрос приходит, и ответ маршрутизируется по маршруту по умолчанию хоста, который клиент OpenVPN направил на tun0. Ответ уходит с адреса, которого удалённая сторона не ожидает, и отбрасывается. Вторая — конфликт префиксов: стандартный мост Docker владеет 172.17.0.0/16, а корпоративный туннель проталкивает тот же диапазон.

Одна исправляется таблицей маршрутизации, другая перенумерацией моста. Редактировать, не зная, какой случай перед вами, — так и ломают работающий контейнер.

Решите с помощью трёх вопросов

Вопрос первый: исчезает ли неисправность, когда вы останавливаете VPN? Если при выключенном туннеле всё работает, подозревайте обратный путь. Вопрос второй: перечисляет ли ip route show table all тот же префикс дважды, один раз на мосту и один раз на tun0? Это и есть конфликт. Вопрос третий: затронутый адрес — это контейнер, гость NAT или гость за мостом?

Третий вопрос важен, потому что у каждого своё правильное исправление, а применение исправления для контейнера к гостю libvirt тратит впустую целый вечер.

ip route
ip route show table all | grep -E '172\.1[7-9]|10\.8'
docker network inspect bridge --format '{{.IPAM.Config}}'

Обратный путь: таблица для одного адреса источника

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

ip rule add from 172.22.0.3 lookup 10
ip route add default via 172.22.0.1 table 10

Каждый пакет, источник которого — этот адрес, сначала обращается к таблице 10, и таблица 10 отправляет его на шлюз моста вместо туннеля.

Проверьте с помощью ip route get 1.1.1.1 from 172.22.0.3. Ответ должен называть интерфейс моста, а не tun0. Правила, добавленные так, исчезают при перезагрузке, поэтому поместите их в unit или сетевой хук, которому принадлежит контейнер.

Тот же шаблон работает для контейнера WireGuard, где обратному пути тоже нужно явное правило.

Конфликт префиксов: перенумеруйте мост

Таблица маршрутизации отправляет один префикс в одно место. Если VPN проталкивает 172.17.0.0/16, а мост Docker уже там, побеждает меньшая метрика: Docker устанавливает свой подключённый маршрут с метрикой 0, а протолкнутый маршрут приходит с 50. Тогда туннель выглядит сломанным, пока Docker продолжает работать, или наоборот, в зависимости от порядка установки. Перенумеруйте мост вместо борьбы с метрикой.

{
  "bip": "10.200.0.1/24",
  "default-address-pools": [{"base": "10.201.0.0/16", "size": 24}]
}

После изменения перезапустите Docker и заново создайте сети, построенные на старом пуле. Изменение метрики лишь переносит конфликт на следующую загрузку.

Виртуальным машинам снова нужен другой ответ

Гость libvirt в стандартной сети NAT наследует маршрутизацию хоста и перестаёт выходить наружу, как только туннель хоста завладевает маршрутом по умолчанию. Перевод гостя на мостовое или macvtap-устройство даёт ему собственный путь в физической сети. В virt-manager добавьте оборудование, выберите Network, задайте источником macvtap-устройство хоста, оставьте virtio моделью и затем отключите старый интерфейс NAT.

Гость macvtap обычно не может достичь собственного хоста. Это ожидаемое поведение, а не неисправность, и ему место в заметке, которую вы оставляете.

Для гостя, которому нужна только локальная сеть хоста, в клиенте поставщика может быть недостающий переключатель: с Mullvad mullvad lan set allow восстанавливает локальную доступность, не трогая libvirt. А когда контейнер должен быть доступен только через туннель, привяжите опубликованный порт к адресу туннеля, -p 10.8.0.5:8080:8080, вместо публикации на каждом адресе.

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