Dev Container не достучится до хоста, доступного только через VPN: где искать
У браузера и контейнера разные контексты резолвера, прокси и маршрутов. Прежде чем менять любую настройку VPN, выясните, какой из них использует сбойный процесс.
Приватный API открывается в браузере на рабочем столе и падает в контейнере разработки. Это не противоречие, и виноват в этом редко сам VPN. Запрос браузера и падающий запрос сделали разные процессы, с разными резолверами, прокси и маршрутами, и часто из совершенно разных сетевых пространств имён. «На моём ноутбуке» может означать хост Windows, дистрибутив WSL, виртуальную машину Docker Desktop и контейнер внутри этой ВМ — четыре контекста, каждый из которых может ответить на одно и то же имя хоста по-разному. Выясните, какой из них использует сбойный процесс, прежде чем трогать хоть одну настройку.
Найдите процесс, прежде чем менять настройку
Начните с движка контейнеров, а не с клиента VPN. Спросите, с каким контекстом говорит командная строка и на какой операционной системе работает этот движок, потому что контейнер Linux под управлением настольной ВМ идёт другим путём, чем контейнер под управлением локального демона:
docker context ls
docker info --format '{{.OperatingSystem}} / {{.Name}}'
docker run --rm alpine ip route
В Windows wsl -l -v показывает, работает ли дистрибутив под WSL 2, что ставит его за собственный виртуальный коммутатор. Эта деталь решает, делит ли контейнер таблицу маршрутизации хоста или сидит за NAT, которому нужен проброшенный порт или имя хоста, чтобы добраться до хоста.
Держите один запрос неизменным и сравнивайте
Выберите одну безобидную конечную точку на приватном хосте и используйте её для каждого сравнения: из браузера, из терминала хоста, из дистрибутива WSL и изнутри контейнера. Записывайте четыре вещи для каждой попытки: разрешённый адрес, использовался ли прокси, класс сбоя и прошедшее время. Поиск имени, установление соединения, проверка TLS и HTTP-статус — это четыре разные проблемы, и смешивать их в «не работает» означает потерять утро.
Выполняйте сравнение внутри сбойного контейнера, с тайм-аутом, чтобы молчащий брандмауэр не держал терминал минутами. Новый контейнер воспроизводит сетевой контекст движка, но не пространство имён, переменные прокси или резолвер контейнера приложения, поэтому docker exec в настоящий контейнер — авторитетная проверка, а побочный контейнер лишь способ отделить, в движке разница или в контейнере:
docker exec -it app /bin/sh -c "getent hosts api.internal; curl -v --max-time 8 https://api.internal/ 2>&1 | tail -20"
docker run --rm --network container:app curlimages/curl:latest -sv --max-time 8 https://api.internal/ 2>&1 | tail -20
Базовый образ alpine содержит busybox и не содержит curl, и именно такая деталь валит скопированный диагностический блок: выберите образ с клиентом или добавьте его в том же запуске. Сравнение с посторонними публичными сайтами ничего не изолирует. Если https://example.com работает из контейнера, вы узнали, что у контейнера есть доступ в интернет, и ничего о том, доходит ли он до приватного адреса через туннель. Точно так же открытие нового терминала доказывает лишь то, что новый терминал работает, а не то, что он делит среду выполнения с упавшим приложением.
Docker Desktop может работать с VPN
Собственная документация Docker о VPN и связности с хостом описывает это как поддерживаемую конфигурацию, и честное резюме таково: это работает, а не ломается автоматически. Не считайте, что настольная ВМ обходит туннель хоста по замыслу; во многих конфигурациях трафик корректно идёт через туннель хоста, и именно поэтому миф «Docker игнорирует VPN» порождает уверенные и неверные советы.
Что по-прежнему важно — используемый backend, политика раздельного туннеля клиента и конфигурация резолвера, которую получила ВМ. Клиент VPN, который ставит маршруты, но не DNS, или проталкивает DNS-сервер, недоступный ВМ, порождает сбой поиска имени внутри контейнера, тогда как хост работает, потому что у него есть дополнительный резолвер или кэшированный ответ. Это проблема резолвера в костюме контейнера. Она переживает каждое переподключение VPN, которое вы попробуете, потому что туннель уже несёт трафик, который вы просили его нести.
Перекрытия адресов и MTU — разные сбои
Два сбоя заслуживают собственной диагностики, потому что их часто путают с политикой доступа. Первый — перекрытие адресов: если VPN проталкивает диапазон, который конфликтует с подсетью моста по умолчанию контейнерной сети, маршрут контейнера для этого диапазона указывает на локальный мост и никогда не доходит до туннеля. Проверяйте реальные подсети, а не гадайте:
docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
ip route get 10.20.30.40
Второй — проблема MTU. Если соединение устанавливается и TLS завершается, но большие ответы застревают, у вас проблема размера пакета, а не авторизации, и замена DNS-записей никогда это не исправит. Проверьте малым и большим запросом, прежде чем что-то менять, и запишите оба результата.
Решение для перекрытия — другая подсеть контейнеров или ограниченный маршрут, выбранный тем, кто владеет планом адресов. Решение для застревания MTU — зажать или уменьшить MTU туннеля или интерфейса до значения, которое выдерживает путь, применив это на том уровне, который вы контролируете.
Настройка прокси — для процесса, а не для машины
Внутри контейнера localhost называет собственный сетевой контекст этого контейнера, поэтому прокси, работающий на хосте, там под этим именем недоступен. Docker Desktop предоставляет host.docker.internal для доступа к хосту, а в Linux вы добавляете его явно с помощью --add-host=host.docker.internal:host-gateway. В любом случае это имя даёт вам маршрут к хосту; оно не создаёт прокси-сервис и не даёт права им пользоваться.
Используйте адрес и порт прокси, выданные для реального окружения, и проверьте, кто их читает. Переменные, экспортированные в интерактивной оболочке, не настраивают автоматически каждое расширение редактора, языковой сервер, средство сборки и фоновый демон. Важен процесс, сделавший сбойный запрос, поэтому изучайте его окружение, а не окружение вашего терминала.
Наконец, пусть исправление объясняет само себя. Полезное решение называет границу, которая была неверной, настройку, которая её изменила, и набор процессов, на которые оно теперь влияет. Перезапуск VPN, редактора и движка по очереди может изменить симптом, но не оставляет ничего, чем мог бы воспользоваться следующий человек.