Трафік Docker і віртуальних машин за OpenVPN: маршрут чи перенумерація?
Контейнери й гості на хості з клієнтом OpenVPN або втрачають зворотний шлях, або стикаються з префіксом тунелю. Три питання обирають рішення.
Хост працює через тунель, а контейнер поруч не дістає нічого. Публікувати порт із контейнера, який має власний клієнт 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, замість публікації на кожній адресі.