Учим Docker в SNAT

Последнее время всё чаще начал сталкиваться с тем, что IP сервера висит на Dummy интерфейсе и вещаются в сторону сетевиков организации/хостера/провайдера по BGP.

Вроде бы всё выглядит и ничего, и всё работает, но:

  1. У меня есть Docker на таких серверах
  2. И он в упор отказывается SNAT-ить контейнеры с IP Dummy-ка

Это надо как-то исправить.

Смотрим, то что мы имеем

Такс, у нас есть:

Железонька стандартная, с двумя сетевыми интерфейсами, которые воткнуты в сеть провайдера.

  • На интерфейсе p1p0 у нас адрес 10.0.0.1/31 (у провайдера соответственно 10.0.0.0/31)

  • На интерфейсе p1p1 у нас адрес 10.0.0.3/31 (у провайдера соответственно 10.0.0.2/31)

  • На интерфейсе dummy-moew у нас висит адрес 188.222.33.1/32

  • На сервере стоит гордый птыц bird, который делает bgp с провайдером

Зачем 32е и 31е маски? Потому что могу, да и ну его, айпишники тратить на 30е маски…

Делаем докериную сетку (bridge) и запускаем контейнер (например alpine:3), проверяем сетку:

docker network create -d bridge br-dzhigurda
docker run -it --rm --network br-dzhigurda alpine:3
ping -c 1 8.8.8.8

Получаем:

PING 8.8.8.8 (8.8.8.8): 56 data bytes

--- 8.8.8.8 ping statistics ---

1 packets transmitted, 0 packets received, 100% packet loss

Херня, товарищи! Идём смотреть правила в iptables:

-A POSTROUTING -s 198.18.66.0/24 ! -o br-dzhigurda -j MASQUERADE

Видим, что Docker делает MASQUERADE. Это обычный, всем привычный NAT, но! оно работает только с физическими сетевыми интерфейсами. И весь трафик от сервера выходит с IP именно физических интерфейсов. Нам такое не подходит.

Имеем, то что мы смотрим

Закапываемся в доку Docker и трём глаза в изумлении, ничего не нашли. Отлично, полезем в кишочки разбираться. Нам надо попробовать сделать вместо MASQUERADE - SNAT.

Идём в сырцы Docker (а точнее в moby/moby), грепаем, копаемся, и видим волшебные грибы которые потребляли разработчики строчки:

// Файл integration/network/network_linux_test.go

    ipv4SNATAddr := "172.0.0.172"

    // Create a bridge network with --opt com.docker.network.host_ipv4=172.0.0.172

    bridgeName := "hostIPv4Bridge"

    network.CreateNoError(ctx, t, c, bridgeName,

        network.WithDriver("bridge"),

        network.WithOption("com.docker.network.host_ipv4", ipv4SNATAddr),

        network.WithOption("com.docker.network.bridge.name", bridgeName),

    )

Ага. В доке этого не было. Поехали, попробуем!

docker network create -d bridge br-fuckingsnat --opt com.docker.network.bridge.name=br-fuckingsnat --opt com.docker.network.host_ipv4=188.222.33.1

docker run -it --rm --network br-fuckingsnat alpine:3

ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

64 bytes from 8.8.8.8: icmp_seq=1 ttl=108 time=16.5 ms

--- 8.8.8.8 ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 16.497/16.497/16.497/0.000 ms

Отлично, связь есть. Смотрим в iptables:

-A POSTROUTING -s 198.18.66.0/24 ! -o br-fuckingsnat -j SNAT --to-source 188.222.33.1

Поразительно. Не, я точно слепой, такую же фичу не могли в документации пропустить… Или могли? Идём гуглим, и получаем всего один результат! И тот в Release Notes…

Итоги

Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Мы имеем работающие Docker контейнеры, которые SNAT-ятся с нужного нам IP, кровь из глаз после вида исходников, и непреодолимое желание написать разработчикам, чтобы поправили доку.