Контейнерите отговарят в офисната LAN, а после спират да отговарят в момента, в който се свърже корпоративният 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 изтласква, домашните LAN, от които работят хората, и другите контейнерни мрежи на същия хост. След това планирайте пресъздаването, защото съществуваща мрежа запазва старата си подмрежа: 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 поставя контейнерите зад VM със собствен NAT. Хостът изобщо няма маршрут до адреса на контейнера; той стига до услугата през публикуван порт, обикновено вързан към 127.0.0.1. Затова VPN клиент може да счупи услугата без никакво припокриване на подмрежи — или като инсталира маршрут за вътрешния диапазон на VM, или като блокира loopback трафик, докато тунелът е свързан. Ако ip route get на хоста съобщава, че няма маршрут до адреса на контейнера, това е очаквано, а не дефектът.

Практическото следствие е откъде да започнете. На Linux започнете от таблицата за маршрутизация. На Desktop започнете от публикувания порт и правилата на защитната стена на VPN клиента, после проверете дали вътрешната подмрежа на VM не се сблъсква с нещо, което тунелът изтласква.

Проверете с включен тунел

Свържете VPN два пъти, преди да го обявите за поправено, защото профил, който изтласква маршрути при свързване и ги оттегля при прекъсване, може да изглежда правилен след първия опит. След това изпълнете проверките, които клиентът наистина изпълнява:

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

Накрая запишете избрания диапазон в същия регистър, от който го взехте, с датата и проекта, който го притежава. Следващото припокриване ще дойде от проект, който никой не е документирал, и единствената защита е диапазонът да е резервиран нарочно, а не изтеглен случайно.

Подобни статии