Подробно о docker и iptables
Подробное описание, как пакеты докера идут через iptables. Если вам нужно больше деталей и вы действительно хотите разобраться.
После предыдущего поста у меня в голове не совсем укладывалось, как именно идут пакеты в контейнеры и обратно. Почему они проходят именно такие цепочки и именно в такой последовательности?
Я разложил для себя все по полочкам и собрал в статью. Получилось несколько нудно, но может быть такая детализация будет кому-то полезна.
Пост больше о работе iptables на роутере.
Итак, как же проходит трафик через iptables в контейнеры докера и из контейнеров докера? Как докер модифицирует iptables, чтобы вся система работала?
Ментальная модель
Самая простая для понимания ментальная модель такая. Представьте, что контейнеры докера - это отдельные компьютеры во внутренней сети. Каждый из них имеет свой внутренний айпишник (обычно 172.17.0.Х). А Docker Host - это компьютер, соединяющий внутреннюю сеть с интернетом.

Поскольку наш Docker Host подключен к двум сетям, у него есть две "сетевые карты", два сетевых интерфейса - eno1 подключен к внешней сети, а docker0 - ко внутренней.
Конечно, в вашем случае названия интерфейсов и их количество могут различаться, но принцип не меняется.
И вот тут самое главное: комьютер Docker Host работает как роутер для "компьютеров" внутренней сети. Он пересылает пакеты с одной сетевой карты на другую, а пути делает разные модификации, чтобы все работало - MASQUERADE, DNAT, SNAT.
Мы не будем погружаться в механизмы трансляции адресов (nat). Для нас важно понять только тот факт, что весь трафик в конейтеры и обратно является FORWARD-трафиком. То есть, он не адресован компьютеру, на котором запущен демон докера.
А где фильтруется пересылаемый трафик?
Фильтрация трафика в iptables
iptables так устроен, что правила фильтрации трафика прописываются только в одном месте. Это очень удобно и очень просто.
Точнее таких места два - цепочка INPUT и цепочка FORWARD. Но они не пересекаются.
В цепочку INPUT попадают пакеты, адресованные локальным программам.
В цепочку FORWARD - пакеты, которые пересылаются.
Поскольку контейнеры докера - это отдельные компьютеры во внутренней сети, как мы помним из нашей ментальной модели ☝️, то весь трафик в контейнеры докера проходит только цепочку FORWARD.
Давайте посмотрим, какие именно правила добавляет докер в таблицу FORWARD:
$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPTПользовательские правила располагаются в цепочке DOCKER-USER. Если вы в нее ничего не добавили, то там нет никакой фильтрации. О настройке этой цепочки читайте мою статью: как настроить пользовательские правила iptables для docker.
Трафик из контейнеров матчится правилами -i docker0. Помните, мы разбирали путь пакетов выше? Входят через интерфейс docker0, а выходят через какой-то другой во внешнюю сеть.
container -> -i docker0 -o eno1 -> internetТрафик в конейнеры попадает под действие правил -o docker0.
internet -> -i eno1 -o docker0 -> containerПосмотрим несколько примеров, чтобы вы понимали, какие конкретно правила проходят пакеты.
Пакеты из контейнеров наружу
Пакеты, которые идут из контейнеров во внешний мир, никак не задерживаются благодаря вот этому правилу:
-A FORWARD -i docker0 ! -o docker0 -j ACCEPTТо есть любые исходящие соединения разрешены без каких-либо ограничений.
Пакеты из интернета в контейнеры на опубликованные порты
Если мы опубликуем порт, то в цепочку DOCKER таблицы filter добавится новое правило. Например, я запустил контейнер nginx и опубликовал порт -p 7777:80. Добавилось новое правило:
$ sudo iptables -S DOCKER
-N DOCKER
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPTТо есть пропускать на порт 80 --dport 80 все пакеты, которые идут в контейнеры -o docker0 в конкретный контейнер с айпи -d 172.17.0.2
Пакеты из интернета в контейнеры по ответным соединениям
Допустим, мы изнутри контейнера пингуем гугл. Исходящие пакеты не задерживаются, это мы уже разобрали выше:
# сначала проходит цепочку (она пустая)
-A FORWARD -j DOCKER-USER
# потом еще какую-то (она тоже ничего не делает)
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
# исходящий пакет разрешен к передаче
-A FORWARD -i docker0 ! -o docker0 -j ACCEPTНо когда от гугла приходит ответный пакет, как он попадает в систему? Вот так:
# так же проходит две цепочки
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
# дальше по цепочке DOCKER, но там нет матча
-A FORWARD -o docker0 -j DOCKER
# и в конце срабатывает вот это правило:
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Поскольку ответ на пинг является RELATED-соединением, он пропускается.
Люблю IPTABLES
P. S. Сейчас подумал, что не очень эффективно дублировать все эти правила в DOCKER-USER, как сделано в предыдущей статье. Может быть будет проще дописать туда только явно запрещающие входящие правила - вроде "запрещать входящий трафик в контейнеры на любые порты, кроме 80, 443, ХХХ". Результат!