Контейнери відповідають в офісній локальній мережі, а потім перестають відповідати тієї миті, коли підключається корпоративний VPN. У контейнері нічого не змінилося: ані перезапуску, ані оновлення образу, ані правки конфігурації. Змінилася таблиця маршрутизації хоста, де маршрут, доданий тунелем, тепер покриває адресу, яку видав Docker. Перш ніж правити підмережу або просити виняток для VPN, покладіть обидва плани адрес поруч, бо виправлення коштують дуже різного часу.

Прочитайте маршрут, перш ніж змінювати конфігурацію

Почніть із мережі, яку Docker справді побудував. Блок IPAM — це план адрес, а підмережа — той рядок, який важливий:

docker network inspect project_default --format '{{json .IPAM.Config}}'
# [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]

Типово Compose виділяє /16 кожному новому проєкту, тож два сервіси можуть уміститися в діапазоні з 65,536 адрес. Саме через цю ширину перекриття такі часті: випадково вибраний /16 достатньо великий, щоб поглинути сайт, до якого ви дістаєтеся тунелем, і ніхто цього не помічає, доки тунель не підніметься.

Далі порівняйте вибір маршруту для однієї адреси контейнера — один раз із вимкненим тунелем і один раз із увімкненим:

ip route get 172.18.0.7
# 172.18.0.7 dev br-3f2a9c src 172.18.0.1

Дві форми відповіді ведуть у різні місця. Якщо вивід називає tun0, wg0 або інший пристрій тунелю, префікс VPN щонайменше такий самий конкретний, як маршрут мосту, і пакет ніколи не доходить до контейнера. Якщо він досі називає міст, хост може дістатися контейнера, і ваша несправність — це фільтрація, DNS або зворотний шлях, що відмовляє лише в одному напрямку.

Чому колізію /16 легко отримати

Docker бере мостові мережі з приватних пулів, а типовий пул походить із блоку 172.16.0.0/12 шматками по /16. Корпоративні мережі часто використовують той самий приватний простір, а VPN віддаленого доступу роздає ці префікси кожному клієнту, що підключається. Коли обидві сторони вважають, що володіють 172.18.0.0/16, в одній таблиці маршрутизації опиняються два суперечливі твердження.

Переможця визначає довжина префікса, а не порядок маршрутів. Тунель, що просуває 10.0.0.0/8, не перемагає маршрут мосту 172.18.0.0/16, бо /8 менш конкретний. Тунель, що просуває 172.18.0.0/16 або 172.18.5.0/24 усередині неї, перемагає. Тому один ноутбук працює в офісній мережі й відмовляє в Wi-Fi готелю з тим самим VPN: клієнт отримує різні префікси.

Та сама таблиця керує й зворотним напрямком. Контейнер, якому потрібен внутрішній хост на 10.8.4.20, надсилає пакет на свій мостовий шлюз, а хост далі спрямовує його в тунель. Цей шлях зазвичай працює, бо NAT виконує хост, але відмовляє, коли корпоративна сторона приймає лише VPN-адресу ноутбука або коли фільтрація зворотного шляху відкидає асиметричну відповідь.

Порівняйте три виправлення

Є лише три виходи, і вони різняться тим, хто має їх затвердити і що ламається, поки ви застосовуєте одне з них.

RepairWho owns itWhat it costs
Renumber the container networkYouRecreates the network, restarts services, invalidates literal addresses in configs and scripts
Exclude your range from the tunnelNetwork teamChanges what every laptop sees, needs a change request and a client profile update
Reach the service through a published portYou and the clientOne port only, no direct container-to-container routing across the tunnel

Перенумерування — єдине виправлення, яке можна завершити без заявки, тому більшість текстів на цьому й зупиняються. На молодому проєкті це дешево, а дорого стає, коли інструмент міграції або збережений рядок підключення вказує на буквальну адресу. Порахуйте ці посилання до правки YAML, а не після.

Виняток із тунелю — найчистіша відповідь, коли перекриття лежить у діапазоні, яким ваша компанія справді володіє. Він же й найповільніший, і він марний для підрядника, який не може змінити профіль клієнта. Опублікований порт або невеликий зворотний проксі знімають суперечку: клієнт звертається до адреси, яку тунель уже маршрутизує правильно, і доступним має бути лише цей порт.

Закріпіть діапазон навмисно

Для одного проєкту оголосіть підмережу у файлі compose, щоб проєкт перестав брати зі спільного пулу:

networks:
  default:
    ipam:
      config:
        - subnet: 10.201.7.0/24

Беріть цей діапазон із реєстру, який веде ваша команда, а не з прикладу, знайденого під час пошуку. Перевірте його за префіксами, які просуває VPN, за домашніми мережами, з яких працюють люди, і за іншими мережами контейнерів на тому самому хості. Потім заплануйте перестворення, бо наявна мережа зберігає свою стару підмережу: docker compose down, потім up -d. docker network rm відмовляє, поки підключені кінцеві точки, а будь-який запис ipv4_address у файлі має потрапляти в новий діапазон.

Для мереж, які Docker створює сам, задайте пул демона в /etc/docker/daemon.json:

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

Пул застосовується до мереж, створених після перезапуску демона, а мости, що вже існують, зберігають свої адреси. Це зручно і легко забувається, тому вважайте старий проєкт таким, що досі перекривається, доки хтось не перестворить його мережу. документація з мереж рушія Docker описує правила розподілу, якщо треба захистити пул на рев'ю.

Міст Linux і Docker Desktop відмовляють по-різному

На хості Linux контейнери стоять на мостах у просторі імен хоста. Усе вирішує одна таблиця, тож наведене вище порівняння — це вся історія: прочитайте маршрут мосту, прочитайте маршрут тунелю і подивіться, який переможе для справжньої адреси контейнера.

Docker Desktop ставить контейнери за віртуальною машиною з власним NAT. Хост узагалі не має маршруту до адреси контейнера; до сервісу він дістається через опублікований порт, зазвичай прив'язаний до 127.0.0.1. Тому клієнт VPN може зламати сервіс без жодного перекриття підмереж: або встановивши маршрут для внутрішнього діапазону віртуальної машини, або заблокувавши loopback-трафік, поки тунель підключений. Якщо ip route get на хості повідомляє, що маршруту до адреси контейнера немає, це очікувано, а не помилка.

Практичний наслідок — куди дивитися першим. У Linux починайте з таблиці маршрутизації. У Desktop починайте з опублікованого порту й правил брандмауера клієнта VPN, а потім перевірте, чи внутрішня підмережа віртуальної машини не конфліктує з чимось, що просуває тунель.

Перевірте з увімкненим тунелем

Перепідключіть VPN двічі, перш ніж вважати питання закритим, бо профіль, який додає маршрути при підключенні й відкликає їх при відключенні, після першої спроби може виглядати правильним. Потім виконайте перевірки, які клієнт справді робить:

  1. ip route get з хоста, використовуючи справжню адресу контейнера.
  2. Запит з іншого контейнера, щоб довести, що сам сервіс здоровий.
  3. curl -sv http://127.0.0.1:8080/ з клієнта з увімкненим тунелем, потім traceroute -n до адреси контейнера з машини в локальній мережі.
  4. sysctl net.ipv4.conf.all.rp_filter, якщо запит доходить до хоста, але відповідь зникає.

Насамкінець запишіть вибраний діапазон у той самий реєстр, з якого ви його вибирали, із датою та проєктом, якому він належить. Наступне перекриття прийде з проєкту, який ніхто не задокументував, і єдиний захист — діапазон було зарезервовано навмисно, а не вибрано випадково.

Схожі статті