Подробно о 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, ХХХ". Результат!