Docker проти iptables/nftables: зупиніть війну фаєрволів і виправте мережу

Було корисно?

Це завжди починається однаково: «контейнери не можуть вийти в інтернет», «порт 443 перестав публікуватися» або улюблена формулювання «вчора працювало». Ви перевіряєте Docker — він запущений. Перевіряєте додаток — він здоровий. Потім дивитесь на фаєрвол і виявляєте, що ви ніяк не відлагоджуєте «мережу». Ви відлагоджуєте дві системи фаєрволів, які кожна вважає себе головною.

У сучасному Linux iptables може бути сумісницькою прослойкою поверх nftables. А може бути й традиційним бінарним інструментом. Docker досі працює через iptables. Ваш дистрибутив може віддавати перевагу nftables. Додайте менеджер фаєрволу, який перезавантажує правила при будь-якому чиху, і отримаєте продакшн-рівневу війну, що криється виключно в таблицях ядра.

Що насправді відбувається, коли Docker «ламає мережу»

Модель мереж Docker за замовчуванням на одному хості оманливо проста:

  • Створити Linux-брідж (зазвичай docker0).
  • Приєднати veth-пари контейнерів до цього бріджа.
  • Зробити NAT (MASQUERADE), щоб контейнери могли виходити назовні.
  • Додати фільтрувальні правила, щоб форвардинг працював і публічні порти потрапляли в потрібний контейнер.

Кожен із цих кроків торкається netfilter. Проблема в тому, що «iptables» — це не лише інструмент; це також інтерфейс до таблиць ядра. І останніми роками у користувацькому просторі відбуваються перестановки:

  • iptables-legacy: спілкується зі старим інтерфейсом xtables.
  • iptables-nft: команда iptables, яка записує правила в nftables (через шар сумісності).
  • nft: нативний інструмент і мова nftables.

Якщо Docker записує правила в один бекенд, а ваша система перевіряє або керує іншим, ви «не побачите нічого», повірите, що нічого не налаштовано, і потім з упевненістю «виправите» це, роблячи гірше.

Також є три рівні політики, які регулярно стикаються:

  1. Налаштування ядра, такі як net.ipv4.ip_forward та sysctl для bridge netfilter.
  2. Менеджери фаєрволу (firewalld, ufw, власні systemd-юніти, конфігураційні системи), які встановлюють політику за замовчуванням і перезавантажують правила.
  3. Управління правилами Docker, яке передбачає вставлення ланцюгів типу DOCKER і DOCKER-USER і стрибків у них.

Режими відмови послідовні:

  • Правила NAT існують в одному наборі правил, але пакети оцінюються іншим.
  • Форвардинг блокується, бо політика за замовчуванням — DROP, або перезавантаження фаєрволу забуло стрибки до Docker-ланцюгів.
  • Публічні порти не працюють, бо DNAT-правила відсутні або ланцюг DOCKER не викликається.
  • Трафік між контейнерами вмирає, бо ввімкнено фільтрацію бріджа і ви цього не дозволили.

Оберіть сторону. Зробіть її стабільною. Перестаньте дозволяти трьом інструментам писати в один і той же зошит.

Один короткий жарт, як обіцяно: NAT — як офісна політика: все працює, поки хтось не «спростить» правила, і раптом ніхто з кимось не може поговорити.

Швидкий план діагностики (перші/другі/треті перевірки)

Перше: підтвердити бекенд фаєрволу і куди записуються правила

  • Чи використовує iptables nft чи legacy?
  • Показує nft list ruleset ланцюги/правила Docker?
  • Ви інспектуєте той самий бекенд, який програмує Docker?

Друге: підтвердити, що форвардинг і NAT існують для Docker-бриджа

  • net.ipv4.ip_forward=1?
  • Є правило MASQUERADE для підмережі docker0?
  • Ланцюг FORWARD дозволяє трафік docker0 → зовні та повернення?

Третє: знайти, хто перезаписує правила після старту Docker

  • firewalld перезавантажується? ufw вмикається? власні скрипти харднінгу?
  • Порядок systemd: Docker стартує перед чи після сервісу фаєрволу?
  • Чи зникають правила після перезавантаження фаєрволу?

Коли ви під тиском

Якщо це продакшн і сайт впав, найшвидший безпечний шлях зазвичай такий:

  1. Переконайтеся, що Docker і хост використовують один бекенд (часто шляхом вибору iptables-legacy або узгодження всього під nftables).
  2. Відновіть форвардинг + NAT і підтвердіть за лічильниками пакетів.
  3. Запобіжте майбутнім стиранням правил, виправивши порядок сервісів і використовуючи DOCKER-USER для вашої політики.

Потім поверніться і зробіть це правильно, а не просто «щоб зараз працювало».

Цікаві факти та коротка історія (щоб дивні речі стали зрозумілі)

  • nftables з’явився в Linux 3.13 (2014) як наступник iptables, пропонуючи гнучкіший набір правил і кращу продуктивність при великих кількостях правил.
  • iptables-nft — це шар сумісності, а не просто «інший вивід iptables». Він перекладає правила iptables в об’єкти nftables, і переклад має крайові випадки.
  • Деякі дистрибутиви перемкнули iptables на nft за замовчуванням (через alternatives), що тихо змінило те, що означає iptables -S на тому самому командному рядку.
  • Модель мереж Docker передувала nftables, і її операційні припущення були побудовані навколо ланцюгів iptables, які Docker може вставляти і керувати ними інкрементально.
  • nftables підтримує атомарні оновлення наборів правил, що добре для коректності; але це також означає, що інструмент може замінити весь набір правил одним ударом і випадково видалити ланцюги Docker.
  • firewalld еволюціонував в бік nftables, але багато середовищ досі працюють із змішаними інструментами, де Docker використовує iptables, а firewalld — nftables напряму.
  • Ланцюг DOCKER-USER існує не просто так: Docker потребував стабільної точки вставки для політик користувача, щоб вони не затиралися оновленнями Docker.
  • Bridge netfilter — поширена пастка: пакети, що проходять через Linux-бріджі, можуть передаватися до iptables/nftables залежно від sysctl, що змінює шлях, який мають відповідати ваші правила.
  • Збої в мережі контейнерів часто є «політичними» проблемами, а не «маршрутизаційними»: маршрути виглядають вірними, ARP працює, але netfilter тихо відкидає або не робить NAT.

Практичні завдання: команди, виводи та рішення, яке ви приймаєте

Ось перевірки, які я виконую, коли графік червоний, а Slack гуде. Кожне завдання включає: команду, реалістичний вивід, що це означає та яке рішення прийняти.

Завдання 1: Визначити, який бекенд iptables ви використовуєте

cr0x@server:~$ sudo iptables --version
iptables v1.8.9 (nf_tables)

Що це означає: Ваша команда iptables записує в nftables (iptables-nft). Якщо Docker використовує той самий бінарник, його правила живуть в nftables, а не в legacy xtables.

Рішення: Інспектуйте за допомогою nft або послідовно використовуйте iptables. Якщо інший компонент використовує iptables-legacy — у вас split-brain.

Завдання 2: Перевірити alternatives (Debian/Ubuntu) для iptables та подібних

cr0x@server:~$ sudo update-alternatives --display iptables
iptables - auto mode
  link best version is /usr/sbin/iptables-nft
  link currently points to /usr/sbin/iptables-nft
  link iptables is /usr/sbin/iptables
/usr/sbin/iptables-legacy - priority 10
/usr/sbin/iptables-nft - priority 20

Що це означає: Системна перевага — iptables з бекендом nft. Ваші інструменти й очікування повинні відповідати цьому.

Рішення: Якщо Docker або ваш фаєрвол використовують legacy, узгодьте їх (або переведіть усе на nft, або навмисно переключіться на legacy для простоти епохи Docker).

Завдання 3: Підтвердити Docker-бридж і підмережі

cr0x@server:~$ docker network inspect bridge --format '{{json .IPAM.Config}}'
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]

Що це означає: Підмережа бріджа за замовчуванням — 172.17.0.0/16. NAT і правила форвардингу повинні посилатися на цей CIDR або інтерфейс.

Рішення: Якщо ви використовуєте кастомні підмережі — вони мають бути тут, і потрібно перевірити, що фаєрвол відповідає. Невідповідні підмережі = NAT ніколи не спрацьовує.

Завдання 4: Перевірити, що форвардинг ядра увімкнено

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Що це означає: Ядро не буде форвардити пакети між інтерфейсами. Контейнери можуть спілкуватися з хостом, але не далі.

Рішення: Увімкніть форвардинг постійно через sysctl. Тимчасові перемикання — лише для реакції на інцидент.

cr0x@server:~$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

Завдання 5: Перевірити sysctl bridge netfilter (поширено в хардендованих збірках)

cr0x@server:~$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables 2>/dev/null
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1

Що це означає: Трафік через брідж передається в iptables/nftables для фільтрації. Це може бути нормально, але означає, що політика FORWARD має велике значення.

Рішення: Якщо ви не планували фільтрувати трафік в межах бріджа, подумайте про встановлення 0, або напишіть коректні правила. Не гадати.

Завдання 6: Подивитися, чи Docker створив очікувані ланцюги (вид iptables)

cr0x@server:~$ sudo iptables -S | sed -n '1,40p'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-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 вставив ланцюги, але політика FORWARD за замовчуванням — DROP. Docker намагається зробити отвори; менеджери фаєрволу іноді скидають політики і ламають це.

Рішення: Якщо трафік все ще не проходить — перевірте лічильники і впевніться, що ці стрибки все ще існують після перезавантаження фаєрволу. Якщо ні — менеджер фаєрволу їх перезаписує.

Завдання 7: Підтвердити, що NAT MASQUERADE існує для виходу контейнерів

cr0x@server:~$ sudo iptables -t nat -S | sed -n '1,80p'
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

Що це означає: MASQUERADE присутній. Якщо контейнери все ще не можуть в інтернет, або форвардинг блокується, або DNS зламався, або пакети йдуть іншим інтерфейсом, ніж ви думаєте.

Рішення: Якщо рядок MASQUERADE відсутній — спочатку вирішіть конфлікт бекендів правил. Додавання правила вручну — пластир; Docker пізніше буде з ним боротися.

Завдання 8: Інспектувати nftables ruleset напряму (детектор правди)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,80p'
table inet filter {
	chain input {
		type filter hook input priority 0; policy accept;
	}
	chain forward {
		type filter hook forward priority 0; policy drop;
		jump DOCKER-USER
		jump DOCKER-ISOLATION-STAGE-1
	}
	chain output {
		type filter hook output priority 0; policy accept;
	}
	chain DOCKER-USER {
		return
	}
}
table ip nat {
	chain PREROUTING {
		type nat hook prerouting priority -100; policy accept;
	}
	chain POSTROUTING {
		type nat hook postrouting priority 100; policy accept;
		ip saddr 172.17.0.0/16 oifname != "docker0" masquerade
	}
}

Що це означає: Правила, що стосуються Docker, існують в nftables. Якщо iptables -S нічого не показує, а nft показує — ви, ймовірно, дивитесь на iptables-legacy.

Рішення: Стандартизуйтесь на одному інструменті для інспекції та керування. Під час інциденту найшвидший спосіб витратити годину — дивитися не на той набір правил.

Завдання 9: Доведіть, чи перезавантаження фаєрволу затирає правила Docker

cr0x@server:~$ sudo systemctl reload firewalld
cr0x@server:~$ sudo iptables -t nat -S | grep -E 'MASQUERADE|DOCKER' | head

Приклад виводу (поганий знак):

cr0x@server:~$ sudo iptables -t nat -S | grep -E 'MASQUERADE|DOCKER' | head

Що це означає: Після перезавантаження NAT-правила Docker зникли (або ви дивитесь не на той бекенд). Контейнери втратять egress і публічні порти.

Рішення: Виправте інтеграцію: налаштуйте firewalld на збереження Docker-ланцюгів або забезпечте, щоб Docker стартував після фаєрволу і відновлював правила, або припиніть використовувати менеджер, який бездумно замінює набори правил.

Завдання 10: Перевірити ланцюг DOCKER-USER як точку вставки вашої політики

cr0x@server:~$ sudo iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -j RETURN

Що це означає: Docker створив ланцюг, але ви ним не користуєтесь. Це нормально, але саме тут варто розміщувати правила типу «заборонити цій підмережі контейнерів доступ до тієї мережі».

Рішення: Поміщайте корпоративну політику в DOCKER-USER, а не редагуйте Docker-ланцюг DOCKER. Docker трактує власні ланцюги як робочий майданчик.

Завдання 11: Підтвердити, що публікація порту створює DNAT

cr0x@server:~$ docker run -d --name webtest -p 8080:80 nginx:alpine
Unable to find image 'nginx:alpine' locally
nginx:alpine: Pulling from library/nginx
Status: Downloaded newer image for nginx:alpine
3c1c0f4c8b2a3d0a5a1f2d7b3f4d9c2d12e4f0f3c2c7c9bb8a1a3c0f8ad9f1b2
cr0x@server:~$ sudo iptables -t nat -S DOCKER | grep 8080
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80

Що це означає: Docker опублікував порт 8080, створивши DNAT-правило. Якщо додаток все ще недоступний, або форвардинг блокує, або сервіс у контейнері не слухає.

Рішення: Якщо DNAT не з’являється, Docker не вдається запрограмувати iptables (часто через відсутні права, прапорки демона або невідповідність бекендів).

Завдання 12: Перевірити з’єднання зсередини контейнера (не з хоста)

cr0x@server:~$ docker exec -it webtest sh -c 'ip route; wget -qO- --timeout=3 https://example.com | head'
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

Що це означає: Маршрут вірний. Якщо wget зависає або помиляється — підозрюйте NAT/форвардинг/фаєрвол або DNS. Якщо працює — проблема, ймовірно, у публікації портів, а не в egress.

Рішення: Розділіть проблему: вихідний трафік і вхідний падають з різних причин і вимагають різних виправлень.

Завдання 13: Спостерігати лічильники пакетів, щоб побачити, чи правила взагалі збігаються

cr0x@server:~$ sudo iptables -t nat -L POSTROUTING -v -n | sed -n '1,10p'
Chain POSTROUTING (policy ACCEPT 120 packets, 7200 bytes)
 pkts bytes target     prot opt in     out     source               destination
  38  2280 MASQUERADE  all  --  *      eth0    172.17.0.0/16        0.0.0.0/0

Що це означає: Лічильники, що зростають = правило NAT збігається. Якщо лічильники нульові, хоча контейнери намагаються виходити — трафік може не форвардитись, може йти іншим інтерфейсом або блокуватися раніше.

Рішення: Використовуйте лічильники, щоб уникнути культу правил. Якщо лічильники не рухаються — ваше «виправлення» не в тому місці.

Завдання 14: Знайти, хто останнім торкав netfilter (підказки в журналі)

cr0x@server:~$ sudo journalctl -u docker -u firewalld -u ufw --since "2 hours ago" | tail -n 25
Jan 02 09:14:22 server dockerd[1289]: time="2026-01-02T09:14:22.902311" level=info msg="Loading containers: done."
Jan 02 09:14:23 server dockerd[1289]: time="2026-01-02T09:14:23.110022" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.1/16. Daemon option --bip can be used to set a preferred IP address"
Jan 02 09:41:07 server firewalld[1022]: Reloaded firewall rules
Jan 02 09:41:07 server dockerd[1289]: time="2026-01-02T09:41:07.514010" level=warning msg="Failed to program NAT chain: iptables: No chain/target/match by that name."

Що це означає: Відбулося перезавантаження firewalld, потім Docker не зміг запрограмувати NAT-ланцюг. Це ваш курящий пістолет: набори правил були замінені, і очікувані посилання Docker зламалися.

Рішення: Виправте порядок сервісів, конфігурацію менеджера фаєрволу або зменшіть кількість «власників» правил (див. розділ зі стратегіями). Припиніть перезапускати Docker як «рішення».

Обрати стратегію: legacy iptables, nftables-перший або «Docker не чіпає мій фаєрвол»

Більшість простоїв тут походять від невизначеності. Команди випадково запускають гібрид: Docker очікує iptables, дистрибутив — nftables, а менеджер фаєрволу вважає, що він володіє всім набором правил. Оберіть модель і реалізуйте її свідомо.

Стратегія A: Узгодитись на iptables-legacy («нудний і передбачуваний» шлях)

Якщо вам потрібна максимальна сумісність зі старими інструментами або ви розв’язуєте роки скриптів — iptables-legacy може бути найменш поганим шляхом. Він зменшує дивні переклади і узгоджується з оригінальними припущеннями Docker.

Коли обирати:

  • Старі дистрибутиви або конфігурації ядра/користувацького простору, де інструменти nft непослідовні.
  • Багато оперативного «м’язового пам’яті» навколо виводу iptables і скриптів.
  • Маленьке бажання мігрувати політику фаєрволу саме зараз.

Вартість: Ви хазяйнуєте проти напрямку дистрибутиву. Це може бути ок, але прийміть рішення свідомо.

Ескіз впровадження (Debian/Ubuntu):

cr0x@server:~$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives: using /usr/sbin/iptables-legacy to provide /usr/sbin/iptables (iptables) in manual mode

Контрольна точка: Після переключення перезапустіть Docker і підтвердіть, що правила з’являються через iptables -S. Якщо правила все ще не з’являються — щось інше не так (дозволи, прапорки демона, менеджер фаєрволу, що затирає).

Стратегія B: Піти nftables-першим («сучасне ядро, сучасні правила»)

Туди йде Linux. Саме там люди отримують проблеми, коли напівмігрують. Якщо обираєте nftables-першим, треба дисципліновано визначити власність правил і спосіб інспекції.

Що означає «nftables-першим»:

  • Використовувати iptables лише як інтерфейс сумісності за потреби, але інспектувати через nft.
  • Переконатися, що ваш менеджер фаєрволу використовує nftables і не затирає Docker-ланцюги, або явно інтегрується з Docker.
  • Правильно зберігати правила nftables і уникати інструментів, які «очищують і замінюють все» без координації.

Важливий операційний пункт: Docker може досі емісувати команди iptables, але якщо це iptables з бекендом nft, то це прийнятно. Ключ — не змішувати nft і legacy таблиці і сподіватися на узгодженість.

Стратегія C: Сказати Docker не керувати iptables (для тих, хто реально контролює край)

У Docker є прапорець демона: --iptables=false. Це заманливо. Це також гострий ніж, який рубає тихо.

Коли це виправдано:

  • У вас є окрема команда мережевої платформи, що керує всіма NAT/фільтр-правилами через nftables.
  • Ви використовуєте фіксовані мережі контейнерів, а публічні порти явно визначені в правилах фаєрволу.
  • Ви погоджуєтесь, що Docker «не працюватиме сам із коробки» для розробників.

Коли це пастка:

  • Ви покладаєтесь на динамічну публікацію портів або епhemeral compose-стеки.
  • У вас декілька команд деплоять довільні контейнери на одному хості.

Моя думка: Якщо ви не готові писати й підтримувати еквівалентні правила NAT і форвардингу замість Docker, не відключайте Docker iptables. Ядро не винагородить ваш оптимізм.

Другий короткий жарт: Вимкнути iptables у Docker — як забрати кермо, бо вам «подобається відчуття дороги». Ви точно відчуєте дорогу.

Єдиний ланцюг, якому варто довіряти: DOCKER-USER

Docker дає підтримуване місце для політики: DOCKER-USER. Воно оцінюється перед власними правилами Docker для форвардингу. Помістіть туди ваші allow/deny правила. Тримайте їх невеликими, читабельними й аудитованими.

Приклад політики: заборонити контейнерам доступ до мереж RFC1918, крім потрібної підмережі сервісу. (Підлаштуйте під своє середовище.)

cr0x@server:~$ sudo iptables -I DOCKER-USER 1 -d 10.0.0.0/8 -j REJECT
cr0x@server:~$ sudo iptables -I DOCKER-USER 2 -d 172.16.0.0/12 -j REJECT
cr0x@server:~$ sudo iptables -I DOCKER-USER 3 -d 192.168.0.0/16 -j REJECT
cr0x@server:~$ sudo iptables -A DOCKER-USER -j RETURN

Що це означає: Контейнери все ще можуть виходити в інтернет (NAT), але не можуть вільно переміщатися по внутрішній мережі. Ви використовуєте підтримуваний hook Docker замість боротьби з його ланцюгами.

Рішення: Якщо потрібна політика — використовуйте DOCKER-USER. Якщо потрібна складна політика, розгляньте перенесення мереж контейнерів на рішення з явними мережевими політиками (і прийміть супутню операційну складність).

Одна цитата (перефразована ідея), щоб не забувати

Richard Cook (перефразована ідея): «Успіх ховає складність; невдача її виявляє.»

Коли мережа Docker працює — ніхто не запитує, хто володіє фаєрволом. Коли вона падає — ви дізнаєтеся, скільки інструментів писали правила за спиною.

Три корпоративні міні-історії з передової

Міні-історія 1: Інцидент через неправильне припущення

Команда мала рутинну процедуру: деплой нового образу контейнера, дивитись health check, і йти далі. Одного вівторка вони прокатили незначний патч у платіжний сервіс, який публікував порт на хості. Сервіс став здоровим, але трафік клієнтів обірвався. З погляду аплікації нічого не було не так. З погляду балансувальника — бекенд перестав приймати з’єднання.

Перший респондер зробив те, що робить кожен втомлений інженер: перевірив docker ps, підтвердив -p 443:8443, а потім перевірив iptables. DNAT-правил не було. Ніяких посилань на DOCKER. «Docker не запрограмував iptables», — сказали вони, і перезапустили Docker. Він ожив на хвилину. Потім знову зламався після перезавантаження фаєрволу. Ось тоді інцидент став цікавим.

Неправильне припущення було тонким: вони думали, що система використовує legacy iptables, бо iptables -S нічого корисного не показував. Насправді хост давно перемкнувся на nftables під час оновлення ОС. Їхній рунбук все ще використовував інструменти iptables-legacy, і «крок верифікації» дивився не в той бекенд. Правила існували, просто не там, де вони дивились.

Тим часом робота з харднінгу безпеки виконувалась кожні 15 хвилин і перезавантажувала політику фаєрволу через nftables, атомарно замінюючи весь набір правил. Правила Docker, що використовували iptables-nft, не зберігались, бо харднінг-скрипт їх не включав. Кожні п’ятнадцять хвилин публічний порт зникав. Це був ідеальний метроном простою.

Виправлення було нудне: припинити повну заміну набору nft, інтегрувати потрібні ланцюги Docker і зафіксувати інструменти інспекції. Урок запам’ятався, бо болісно: «простий redeploy» перетворився на урок по бекендах фаєрволу, поданий на рівні продакшн.

Міні-історія 2: Оптимізація, що відбилася бумерангом

Інша компанія гналася за прискоренням завантаження. Хтось помітив, що старт Docker іноді триває довше на завантаженому хості. Пропозиція: відключити керування iptables у Docker і дозволити «справжньому фаєрволу» обробляти це. Швидший старт, менше рухомих частин, краща безпека. На слайді звучало як зрілість.

Вони впровадили зміну на стейджингу, де додатки мали стабільні порти й фіксовані мережі. Все пройшло. Потім це потрапило у продакшн, де команди використовували Compose-стеки й тимчасові сервіси, які публікували порти для дебагу. Ці порти почали падати найнеприємнішим способом: контейнери працювали, вони логували «listening», і локальний curl з хоста працював через IP контейнера. Але публічні хост-порти були мертвими, бо DNAT-правил не створювалось.

Щоб заклеїти, вони додали статичні nftables-правила для «важливих сервісів». Це допомогло доти, доки наступна команда не задеплоїла щось нове. Правила фаєрволу стали ручним реєстром портів, що віддаляється від реальності. Оптимізація стартап-тайм скорочення виявилася податком на операційну роботу: кожен новий сервіс вимагав PR у правила фаєрволу. Кожен інцидент вимагав фахівця з nftables і Docker мереж.

Вони врешті вирішили знову ввімкнути керування iptables у Docker і перемістили політику в DOCKER-USER. Час старту трохи погіршився. Інциденти різко впали. Мораль: «виграші» з продуктивності, що прибирають автоматизацію, часто просто переміщують роботу на людей, а люди непередбачувані.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Третя команда працювала в регульованому середовищі. Вони ставилися до стану фаєрволу як до коду: версійований конфіг, явна відповідальність і інтеграційні тести, що перевіряли наявність потрібних ланцюгів після перезавантажень. Ніяких героїв, лише дисципліна. Це те, як виглядає «повільно», поки не стане швидко.

Під час рутинного оновлення ОС дистрибутив змінив альтернативу iptables з legacy на nft. Після першого перезавантаження половина контейнерів втратила вихідне підключення. Це виглядало як типовий «Docker зламався після патчу», що руйнує вікенди.

Але у них був моніторинг з таргетованою перевіркою: підтвердити, що MASQUERADE для підмережі контейнерів існує і що лічильники пакетів інкрементуються під синтетичним трафіком. Алерт спрацював за кілька хвилин після перезавантаження. On-call діяла за рунбуком, що починався з «підтверди бекенд» і «перевір NAT-лічильники», а не «перезапустити Docker, поки не підуть очі».

Вони швидко знайшли невідповідність: менеджер фаєрволу завантажував legacy-правила, в той час як біна iptables тепер цілиться в nftables. Дві паралельні всесвіти. Виправлення було теж нудне: зафіксувати альтернативи iptables у конфігураційному менеджменті і додати пост-перезавантажувальну валідацію, яка перевіряє активний ruleset через nft list ruleset.

Нічого магічного не сталося. Просто було менше припущень. Ось як «нудне» перетворюється на ефективність.

Поширені помилки: симптом → корінна причина → виправлення

1) Контейнери не мають інтернету, але DNS резолвить

Симптом: dig працює; curl зависає або таймаутиться з контейнерів.

Корінна причина: Відсутнє правило MASQUERADE або форвардинг заблокований (політика FORWARD — DROP, відсутні ACCEPT-правила).

Виправлення: Підтвердьте NAT у правильному бекенді; впевніться, що net.ipv4.ip_forward=1; забезпечте FORWARD-правила, що дозволяють egress з docker0 і RELATED,ESTABLISHED для повернення.

2) Публічні порти перестали працювати після перезавантаження фаєрволу

Симптом: docker run -p працює до перезавантаження firewalld/ufw; потім вхідний трафік ламається.

Корінна причина: Менеджер фаєрволу замінює набір правил і скидає DNAT-ланцюги/стрибки Docker.

Виправлення: Налаштуйте менеджер фаєрволу так, щоб він зберігав ланцюги Docker, або впевненіться, що Docker стартує після фаєрволу і перепрограмовує правила. Віддавайте перевагу політиці у DOCKER-USER.

3) iptables не показує правил Docker, але контейнери працюють

Симптом: iptables -S виглядає порожнім; ви думаєте, Docker не встановив правила.

Корінна причина: Ви дивитесь на iptables-legacy, а Docker пише через iptables-nft (або навпаки).

Виправлення: Перевірте iptables --version та alternatives. Інспектуйте через nft list ruleset, якщо бекенд — nft.

4) Трафік між контейнерами на одному бріджі блокується

Симптом: Контейнери можуть вийти в інтернет, але не можуть дістатись один до одного по IP.

Корінна причина: Ввімкнено bridge netfilter з обмежувальними FORWARD-правилами, або Docker isolation-ланцюги плюс кастомні правила блокують внутрішній трафік.

Виправлення: Дозвольте -i docker0 -o docker0 на шляху форвардингу (або еквівалентне nft-правило). Перевірте sysctl bridge-nf-call-iptables.

5) Docker не стартує з помилкою «Failed to program NAT chain»

Симптом: Логи демона Docker містять помилки про відсутні ланцюги/таргети.

Корінна причина: Інший інструмент очистив таблиці, поки Docker конфігурувався, або у вас невідповідні модулі/бекенди iptables.

Виправлення: Виправте порядок сервісів; припиніть очищення таблиць під час конфігурації Docker; узгодьте бекенд iptables; перезапустіть Docker після стабілізації стану фаєрволу.

6) Лише деякі хости у флоті мають проблему

Симптом: «На хості A все ок, на хості B зламалося» після того самого деплою.

Корінна причина: Різні альтернативи iptables, різні менеджери фаєрволу, або різні sysctl ядра через базові жорсткі налаштування.

Виправлення: Уніфікуйте: зафіксуйте alternatives, забезпечте sysctl, і перевіряйте автоматичними тестами наявність NAT/форвард-правил у активному бекенді.

7) IPv6 працює, а IPv4 ні (або навпаки)

Симптом: Контейнери дістають до IPv6-цілей, але не до IPv4, або навпаки.

Корінна причина: Окремі шляхи правил: ip6tables vs iptables (або таблиці inet в nft). Одна сторона налаштована, інша — ні.

Виправлення: Інспектуйте обидві сімейства адрес. В nft віддавайте перевагу table inet для filter-правил, коли це доречно, і впевніться, що NAT існує для потрібної адресної сім’ї.

Чеклісти / покроковий план

Чекліст A: Стабілізувати зламаний хост під час інциденту (15 хвилин, без геройств)

  1. Підтвердити бекенд: iptables --version, потім перевірити nft list ruleset, якщо бекенд — nft.
  2. Підтвердити форвардинг: sysctl net.ipv4.ip_forward і встановити 1, якщо потрібно.
  3. Підтвердити NAT: знайти MASQUERADE для підмережі Docker у активному бекенді.
  4. Підтвердити шлях FORWARD: впевнитися, що DOCKER-USER і accept-правила Docker присутні і викликаються.
  5. Перевірити, чи перезавантаження фаєрволу затирає правила. Якщо так — зупиніть кровотечу: тимчасово не проводьте reloads або перезапускайте Docker після reload як короткострокове рішення.
  6. Використовуйте лічильники, щоб підтвердити, що пакети потрапляють в NAT і forward-правила.

Чекліст B: Закріпити виправлення на постійно (те, що всі пропускають і потім шкодують)

  1. Оберіть власника політики: або Docker керує iptables правилами (за замовчуванням), або ваш фаєрвол керує ними і Docker обмежений. Не робіть «обох».
  2. Зафіксуйте бекенд iptables послідовно: використайте alternatives, щоб забезпечити однорідність по флоту.
  3. Зафіксуйте порядок сервісів: Docker має стартувати після того, як фаєрвол готовий, і перезавантаження фаєрволу не повинні знищувати ланцюги Docker.
  4. Поміщайте кастомну політику в DOCKER-USER: це підтримуваний і стабільний hook.
  5. Збережіть sysctl: forwarding і bridge sysctl мають бути встановлені через /etc/sysctl.d/.
  6. Додайте валідацію: перевірки після завантаження та після перезавантаження фаєрволу, які підтверджують наявність MASQUERADE і стрибків ланцюгів в активному бекенді.

Чекліст C: Чиста міграція з legacy iptables на nftables (без несподіваних простоїв)

  1. Зробіть інвентаризацію поточних правил iptables і визначте, які з них керуються Docker, а які — політикою сайту.
  2. Перенесіть політику сайту в DOCKER-USER, де можливо.
  3. Змініть інструменти інспекції в рунбуках з виводу iptables на nft (або впевніться, що використовується iptables-nft послідовно).
  4. Протестуйте поведінку перезавантаження фаєрволу у стейджингу: впевніться, що ланцюги Docker зберігаються і публічні порти переживають reload.
  5. Розгортайте на невеликій канарці хостів; валідуйте синтетичними перевірками egress/inbound контейнерів.
  6. Лише після цього переключайте флот.

Питання та відповіді

1) Чому Docker досі використовує iptables у 2026?

Бо інтерфейс ядра для фільтрації пакетів/NAT — netfilter, а iptables багато років був практичним інтерфейсом. Модель Docker побудована навколо вставляння ланцюгів і інкрементального керування правилами. nftables новіший і в багатьох відношеннях кращий, але «новіший» не означає, що весь екосистемний софт мігрував автоматично.

2) У чому різниця між iptables-nft та nftables?

iptables-nft — це команда iptables, що записує правила в nftables через шар перекладу. Нативний nftables використовує nft і власну мову правил та об’єкти. Операційно важливо: інструменти, що керують nftables нативно, можуть атомарно замінювати набори правил і випадково видалити правила, створені через iptables-nft, якщо вони цього явно не враховують.

3) Як зрозуміти, чи я дивлюсь на «правильний» набір правил?

Почніть з iptables --version. Якщо там написано (nf_tables), то nft list ruleset — авторитетний вигляд. Якщо бачите (legacy) — вивід iptables відображає активні правила. Не покладайтесь на м’язову пам’ять; перевіряйте.

4) Чи варто використовувати firewalld з Docker?

Можна, але потрібно налаштувати обдумано. Основна невдача — перезавантаження firewalld, яке затирає правила Docker або змінює поведінку форвардингу за замовчуванням. Якщо використовуєте firewalld — тестуйте поведінку reload, підтверджуйте збереження ланцюгів Docker і уникайте політик, що очищують/замінюють весь набір правил без урахування Docker.

5) Куди краще поміщати правила «заборонити контейнерам доступ до X»?

Використовуйте DOCKER-USER. Docker очікує це місце і не перезаписуватиме його. Не редагуйте довготривало Docker-ланцюги.

6) Чи безпечно перезапускати Docker, щоб «виправити мережу»?

Це тактичний крок, а не вирішення. Перезапуск Docker може тимчасово перепрограмувати правила, але якщо менеджер фаєрволу знову їх зітре, проблема повернеться — лише з додатковим розривом сервісу. Використовуйте перезапуски, щоб відновити сервіс, а потім виправте власність і порядок сервісів.

7) А як щодо rootless Docker?

Rootless змінює модель мережі; він часто використовує користувацьку мережу в просторі (наприклад, slirp4netns) замість програмування хостових iptables. Це може уникнути війни фаєрволів, але має інші компроміси щодо продуктивності та функціоналу, особливо для публікації портів і низькорівневих мережевих вимог.

8) Чому важлива політика FORWARD = DROP, якщо INPUT = ACCEPT?

Трафік контейнера, що форвардиться через хост, потрапляє в ланцюг FORWARD, а не INPUT. INPUT — для трафіку, призначеного самому хосту. Якщо FORWARD = DROP і у вас немає відповідних винятків, контейнери опиняться в своєрідній чемній в’язниці на своєму бріджі.

9) Чи можу я використовувати nftables для безпеки хоста і при цьому дозволити Docker керувати NAT?

Так, якщо робити це узгоджено. Дозвольте Docker програмувати правила через iptables-nft в nftables і переконайтесь, що ваше управління nftables не затирає ці правила. Поміщайте вашу політику в DOCKER-USER (через iptables) або створюйте nft-native правила, які інтегруються без очищення Docker-таблиць.

10) Мій дистрибутив оновив бекенд і тепер усе дивно. Що робити найбезпечніше?

Оберіть один бекенд і запровадьте його по всьому флоту. Під час стабілізації багато команд вибирають iptables-legacy, бо він відповідає старим припущенням. Довгостроково мігрувати на nftables-першим теж нормально — але робіть це з тестами, що покривають reloads, NAT, форвардинг і публічні порти.

Висновок: наступні кроки, які дійсно працюють

Збої мережі Docker, спричинені iptables/nftables, не містять таємниці. Це політика. Забагато акторів, недостатньо власності. Ваше завдання — припинити війну, зробивши чіткий вибір і забезпечивши його виконання.

  1. Визначте бекенд (nftables-перший або iptables-legacy) і зафіксуйте його через alternatives/конфігураційний менеджмент.
  2. Підтвердіть форвардинг і NAT за допомогою лічильників, а не інтуїції.
  3. Доведіть стійкість правил через перезавантаження фаєрволу і ребути. Якщо правила зникають — виправте порядок або припиніть повну заміну набору правил.
  4. Використовуйте DOCKER-USER як стабільний hook для політики. Нехай Docker тримає свої ланцюги Docker.
  5. Автоматизуйте пост-завантажувальну перевірку, яка валідує: бекенд, наявність MASQUERADE, стрибок FORWARD до DOCKER-USER і хоча б один синтетичний контейнер, що дістає зовнішню ціль.

Якщо ви зробите ці п’ять речей, ви не усунете всі мережеві інциденти — це Linux, а не казка — але ви виключите класи інцидентів, де ви відлагоджуєте невірну фаєрвол-універсу.

← Попередня
Proxmox «Too Many Open Files»: який ліміт підвищувати і для якої служби
Наступна →
MariaDB vs PostgreSQL: обмеження швидкості в базі даних — чому це пастка і що робити натомість

Залишити коментар