Трафик на 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 обикновено не може да достигне собствения си хост. Това е очаквано поведение, а не неизправност, и мястото му е в бележката, която оставяте.
За гост, който се нуждае само от LAN на хоста, клиентът на доставчика може да има липсващия превключвател: с Mullvad mullvad lan set allow възстановява локалната достъпност, без да пипа libvirt. А когато контейнер трябва да е достъпен само през тунела, вържете публикувания порт към адреса на тунела, -p 10.8.0.5:8080:8080, вместо да го публикувате на всеки адрес.