Все “працює”, поки не перестає. Малі ping’и проходять, перевірки здоров’я зелені, сторінки входу завантажуються, і раптом з’являється справжній запит: 2 МБ JSON POST, TLS-хендшейк із великим ланцюжком сертифікатів, gRPC-стрім, контейнер завантажує шар. Раптом: зависання, таймаути, загадкові повтори або класичне корпоративне повідомлення в Slack: «Мережа повільна?»
Якщо це відбувається тільки в Docker (або «тільки з контейнерів», або «тільки через VPN», або «тільки при проходженні через overlay»), швидше за все ви маєте справу з проблемою MTU/MSS. Помилки MTU — це ті збої, що змушують розумних людей сумніватися в реальності. Симптоми селективні, засоби спостереження за замовчуванням слабкі, а виправлення легко зробити неправильно.
MTU, MSS, фрагментація, PMTUD: що саме ламається
MTU в одному реченні
MTU (Maximum Transmission Unit) — це максимальний розмір IP-пакета (у байтах), який може пройти через посилання без фрагментації на цьому хопі.
А MSS — те, що люди забувають
TCP MSS (Maximum Segment Size) — максимально можливий TCP-пейлоад, який хост помістить у один TCP-сегмент; воно вираховується з MTU на шляху за вирахуванням заголовків IP і TCP. В Ethernet/IP/TCP без опцій MTU 1500 зазвичай дає MSS 1460 байт (1500 – 20 – 20).
MTU стосується пакетів «на дроті». MSS стосується того, як TCP розбиває дані на шматки. Якщо ви занижуєте MSS, можна уникнути фрагментації взагалі, гарантуючи, що пакети достатньо малі для найменшого MTU на шляху.
Чому малі запити працюють, а великі — ні
При невідповідності MTU ви можете отримати «селективний збій»:
- Малі пейлоади поміщаються в маленькі пакети; вони проходять.
- Великі пейлоади породжують пакети, що перевищують MTU на якомусь хопі; потрібна фрагментація або менший MSS.
- Якщо фрагментація заблокована або Path MTU Discovery (PMTUD) зламаний, ці великі пакети зникають у чорній дірі.
Фрагментація, DF і шаблон «чорної діри»
IPv4 може фрагментувати пакети в дорозі. Але сучасні стекі уникають цього. Вони встановлюють біт DF (Don’t Fragment) і покладаються на PMTUD: відправник надсилає пакети з DF і очікує, що мережа повідомить про придатний MTU, повернувши ICMP «Fragmentation needed», коли пакет занадто великий.
Коли ці ICMP-повідомлення фільтруються (правила безпеки, файрволи, «корисні» мережеві пристрої або неправильна політика), PMTUD мовчки ламається. Відправник продовжує повертати пакети, які ніколи не пройдуть. Для застосунку це виглядає як зависання: TCP-з’єднання встановлено, можливо, частина даних пройшла, а потім усе завмирає.
Цитата, варта стікера:
Переказана ідея: «Надія — не стратегія.»
— в SRE-кругах; ставтеся до MTU як до параметра дизайну, а не як до бажання.
Docker робить простіше створювати невідповідності MTU
Docker додає шари: мости, veth-пари, NAT, а іноді й оверлеї (VXLAN) або тунелі (WireGuard, IPsec, клієнт VPN). Кожен шар інкапсуляції «з’їдає» байти. Якщо з’їсти достатньо, ваше «1500» стає на практиці «1450» або «1412» чи «1376». Якщо тільки деякі вузли або маршрути мають цей оверхед — ласкаво просимо: у вас часткова чорна дірка.
Жарт №1: Помилки MTU — як офісні принтери: чекають, поки ви запізнитеся, а потім розвивають «характер».
Де Docker ховає проблеми MTU
Звична топологія
На типовому Linux-хості з дефолтною мостовою мережею Docker:
- Контейнер підключається через пару
vethдоdocker0(Linux bridge). - Хост маршрутизує/NAT-ує трафік через фізичну NIC типу
eth0абоens5. - Звідти трафік може пройти через VLAN-и, VPC-фабрики, VPN, проксі та інші чарівні корпоративні винаходи.
Контейнер бачить MTU інтерфейсу (зазвичай 1500). Міст бачить MTU (зазвичай 1500). NIC хоста може бути 1500. Але якщо реальний шлях включає інкапсуляцію (наприклад, VPN додає ~60–80 байт, VXLAN — ~50 байт, GRE — ~24 байти, плюс можливі додаткові заголовки), ефективний MTU менший. Якщо ніхто не повідомляє TCP-стеки, великі сегменти виявляться занадто великими.
Оверлеї додають спецій
Docker Swarm overlay і багато CNI-плагінів покладаються на VXLAN або подібну інкапсуляцію для побудови L2-подібних семантик через L3-мережі. VXLAN зазвичай додає приблизно 50 байт оверхеду (зовнішній Ethernet + зовнішній IP + UDP + заголовки VXLAN; точний оверхед залежить від середовища). Якщо підлегла мережа (underlay) — 1500, MTU оверлею має бути близько 1450.
Проблеми виникають, коли:
- Деякі вузли ставлять MTU оверлею 1450, інші лишають 1500.
- Підлегла мережа насправді не 1500 (в хмарних інфраструктурах буває інакше; VPN варіюються ще більше).
- ICMP фільтрується між вузлами, ламаючи PMTUD.
Чому ви відчуваєте це як «TLS ненадійний» або «POST зависає»
TLS може випадково тестувати MTU, бо сертифікати та хендшейк-фази можуть бути більшими, ніж ви думаєте. Так само gRPC-метадані, HTTP/2-фрейми та «маленький» JSON, який насправді величезний, якщо хтось вставив туди base64-дані.
Класичний симптом: TCP-хендшейк завершується, невеликі заголовки відповіді проходять, а потім з’єднання завмирає, коли перший великий сегмент із встановленим DF потрапляє на хоп із занадто малим MTU. Відбуваються повторні передачі. Зрештою — таймаут.
Цікаві факти та трохи історії
- Факт 1: MTU Ethernet 1500 байт став де-факто стандартом через ранні компроміси в проектуванні, а не тому, що 1500 — священне число.
- Факт 2: Path MTU Discovery з’явився, бо фрагментація дорога й крихка; вона перекладає роботу на кінцеві точки.
- Факт 3: PMTUD базується на ICMP. Блокування всього ICMP — як прибрати дорожні знаки і потім звинувачувати водіїв у тому, що вони загубилися.
- Факт 4: Оверхед інкапсуляції VXLAN зазвичай підсуне MTU оверлею до ~1450 на підлеглій мережі 1500; якщо ви залишаєте 1500, ви граєте в рулетку з фрагментацією або jumbo frames.
- Факт 5: Jumbo frames (MTU 9000) можуть знижувати навантаження на CPU в деяких робочих навантаженнях, але один хоп із підтримкою лише 1500 зіпсує вам день, якщо ви не впровадите обмеження чи сегментацію правильно.
- Факт 6: IPv6-роутери не фрагментують пакети в дорозі. Якщо ви ламаєте PMTUD в IPv6, ви не отримаєте «іноді». Ви отримаєте «не працює».
- Факт 7: TCP MSS узгоджується в обох напрямках під час SYN/SYN-ACK; middlebox-и можуть (і часто повинні) змінювати його на льоту для тунелів.
- Факт 8: Багато «раптом» API-таймаутів, які звинувачують сервери застосунків, насправді — буря повторних передач від чорних дір PMTUD.
- Факт 9: Дефолтні bridge та veth-пристрої Docker зазвичай успадковують MTU від хоста під час створення; зміна MTU на хості пізніше не завжди ретрофітує існуючі veth-пари.
Швидкий план діагностики
Це порядок дій, який найшвидше знаходить винуватця в продакшені з найменшою кількістю зайвих гілок.
1) Підтвердьте, що це залежить від розміру і від шляху
- З контейнера зробіть малий запит і великий до тієї самої цілі.
- Якщо мале працює, а велике зависає/таймаутиться, відразу підозрюйте MTU.
2) Знайдіть ефективний Path MTU за допомогою DF-пінгів
- Використайте
ping -M do -sдля бінарного пошуку максимального пейлоаду, який проходить. - Порівняйте результати з хоста і з контейнера. Різниці мають значення.
3) Перевірте значення MTU на всіх релевантних інтерфейсах
- Фізична NIC хоста, docker bridge, veth, оверлеї, VPN/тунельні інтерфейси.
- Якщо якийсь тунель має MTU 1420, а контейнери думають, що у них 1500 — ви, ймовірно, знайшли причину.
4) Перевірте, що PMTUD не блокується
- Перевірте правила файрволу на наявність ICMP типу 3 код 4 (IPv4) і ICMPv6 Packet Too Big (тип 2).
- Зробіть захоплення трафіку: повертаються ICMP-повідомлення помилок? Є повторні передачі?
5) Виберіть стратегію виправлення
- Найкраще: встановити коректний MTU наскрізь (underlay, overlay, контейнери).
- Практично: обмежити TCP MSS на вході/виході тунелю або моста.
- Уникайте: «просто знизити скрізь» без розуміння; ви сховаєте проблему і можете погіршити пропускну здатність.
Практичні завдання: команди, очікуваний вивід і рішення
Це задачі для продакшену: що запускати, що ви хочете побачити, що означає, якщо ні, і що робити далі. Запускайте їх на Linux Docker-хості, якщо не вказано інше.
Завдання 1: Відтворіть із середини контейнера малими й великими пейлоадами
cr0x@server:~$ docker run --rm curlimages/curl:8.6.0 curl -sS -o /dev/null -w "%{http_code}\n" http://10.20.30.40:8080/health
200
Значення: базова доступність є.
Рішення: тепер протестуйте великий запит, який породжує великі TCP-сегменти.
cr0x@server:~$ docker run --rm curlimages/curl:8.6.0 sh -lc 'dd if=/dev/zero bs=1k count=2048 2>/dev/null | curl -sS -m 10 -o /dev/null -w "%{http_code}\n" -X POST --data-binary @- http://10.20.30.40:8080/upload'
curl: (28) Operation timed out after 10000 milliseconds with 0 bytes received
Значення: є патерн помилки, залежний від розміру.
Рішення: припиніть звинувачувати застосунок, поки не доведете, що пакети можуть пройти шлях.
Завдання 2: Порівняйте поведінку хоста і контейнера
cr0x@server:~$ dd if=/dev/zero bs=1k count=2048 2>/dev/null | curl -sS -m 10 -o /dev/null -w "%{http_code}\n" -X POST --data-binary @- http://10.20.30.40:8080/upload
200
Значення: на хості шлях працює, але з контейнера — ні.
Рішення: фокусуйтеся на Docker bridge/veth/iptables і успадкуванні MTU, а не тільки на апстрімі.
Завдання 3: Перевірте MTU на NIC хоста і Docker bridge
cr0x@server:~$ ip -br link show dev eth0
eth0 UP 10.0.0.15/24 fe80::a00:27ff:feaa:bbbb/64 mtu 1500
cr0x@server:~$ ip -br link show dev docker0
docker0 UP 172.17.0.1/16 fe80::42:1cff:fe11:2222/64 mtu 1500
Значення: обидва 1500, що «нормально», але не обов’язково коректно для вашого реального шляху.
Рішення: визначте тунелі/оверлеї. Якщо вони є, 1500 може бути оманливим.
Завдання 4: Перевірте MTU всередині запущеного контейнера
cr0x@server:~$ docker run --rm --network bridge alpine:3.19 ip -br link show dev eth0
eth0@if8 UP 172.17.0.2/16 fe80::42:acff:fe11:0002/64 mtu 1500
Значення: контейнер думає, що MTU — 1500.
Рішення: якщо шлях містить інкапсуляцію (VPN/VXLAN), ймовірно, потрібен менший MTU у контейнері або обмеження MSS.
Завдання 5: Пошукайте тунельні інтерфейси та їх MTU
cr0x@server:~$ ip -br link | egrep 'wg0|tun0|tap|vxlan|geneve|gre' || true
wg0 UP 10.8.0.2/24 fe80::aaaa:bbbb:cccc:dddd/64 mtu 1420
Значення: WireGuard MTU 1420 — сильна підказка. Якщо трафік контейнера виходить через wg0, пакети розміру 1500 не влізуть.
Рішення: або знизьте MTU контейнера/моста до найменшого MTU виходу, або застосуйте обмеження MSS на трафік, що виходить через wg0.
Завдання 6: Визначте ефективний Path MTU за допомогою DF-пінгу (на хості)
cr0x@server:~$ ping -c 2 -M do -s 1472 10.20.30.40
PING 10.20.30.40 (10.20.30.40) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1420
ping: local error: message too long, mtu=1420
--- 10.20.30.40 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 0ms
Значення: хост вже знає MTU 1420 на відповідному маршруті/інтерфейсі (ймовірно, трафік йде через wg0).
Рішення: тепер протестуйте менші розміри, щоб знайти максимум, що проходить; підтвердіть те саме з контейнера.
Завдання 7: Бінарний пошук робочого DF-пінг розміру (на хості)
cr0x@server:~$ ping -c 2 -M do -s 1392 10.20.30.40
PING 10.20.30.40 (10.20.30.40) 1392(1420) bytes of data.
1400 bytes from 10.20.30.40: icmp_seq=1 ttl=62 time=12.3 ms
1400 bytes from 10.20.30.40: icmp_seq=2 ttl=62 time=12.1 ms
--- 10.20.30.40 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 12.051/12.173/12.296/0.122 ms
Значення: Path MTU близько 1420 (1392 пейлоаду + 28 ICMP/IP заголовків = 1420).
Рішення: налаштуйте MTU/MSS так, щоб TCP-пейлоади вміщувалися в ~1420 (практично: MSS близько 1380 залежно від заголовків/опцій).
Завдання 8: Виконайте DF-пінг із середини контейнера (щоб виявити невідповідність)
cr0x@server:~$ docker run --rm alpine:3.19 sh -lc 'ping -c 2 -M do -s 1472 10.20.30.40'
PING 10.20.30.40 (10.20.30.40): 1472 data bytes
ping: sendto: Message too long
ping: sendto: Message too long
--- 10.20.30.40 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
Значення: контейнер не може відправити DF-пакети такого розміру; або він бачить менший MTU на виході, або стек відкидає через виявлений MTU.
Рішення: перевірте маршрутизацію з неймспейсу контейнера і підтвердіть, через який інтерфейс виходить трафік на хості.
Завдання 9: Ідентифікуйте veth контейнера та перевірте його MTU на хості
cr0x@server:~$ cid=$(docker run -d alpine:3.19 sleep 300); echo "$cid"
b2e1e3d4c5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
cr0x@server:~$ pid=$(docker inspect -f '{{.State.Pid}}' "$cid"); echo "$pid"
24188
cr0x@server:~$ nsenter -t "$pid" -n ip -br link show dev eth0
eth0@if10 UP 172.17.0.2/16 fe80::42:acff:fe11:0002/64 mtu 1500
cr0x@server:~$ ifindex=$(nsenter -t "$pid" -n cat /sys/class/net/eth0/iflink); echo "$ifindex"
10
cr0x@server:~$ ip -br link | awk '$1 ~ /^veth/ {print}'
veth1a2b3c4d@if9 UP mtu 1500
Значення: veth має MTU 1500. Якщо трафік виходить через тунель з MTU 1420, пакети можуть бути занадто великими, якщо PMTUD не працює.
Рішення: вирішіть, знижувати MTU на bridge/veth або застосувати обмеження MSS.
cr0x@server:~$ docker rm -f "$cid" >/dev/null
Завдання 10: Перевірте, чи не відкидається ICMP «Fragmentation needed»
cr0x@server:~$ sudo iptables -S | egrep 'icmp|RELATED|ESTABLISHED'
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
Значення: ICMP дозволений на INPUT тут; хороший знак для PMTUD.
Рішення: також перевірте FORWARD (Docker використовує форвардинг) і будь-які ланцюги менеджера файрволу.
cr0x@server:~$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Значення: FORWARD з дефолтним DROP — нормально, якщо є винятки, але ICMP-помилки відносяться до «RELATED» і мають проходити. Якщо у вас немає правила RELATED,ESTABLISHED, PMTUD ламається.
Рішення: переконайтеся, що RELATED,ESTABLISHED присутнє для відповідного напрямку, або явно дозвольте типи ICMP.
Завдання 11: Спостерігайте повторні передачі та MSS за допомогою tcpdump
cr0x@server:~$ sudo tcpdump -ni any 'host 10.20.30.40 and tcp' -c 10
tcpdump: data link type LINUX_SLL2
12:01:10.100001 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 111 ecr 0,nop,wscale 7], length 0
12:01:10.102300 IP 10.20.30.40.8080 > 172.17.0.2.51234: Flags [S.], seq 2222222, ack 1234567891, win 65160, options [mss 1460,sackOK,TS val 222 ecr 111,nop,wscale 7], length 0
12:01:10.103000 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [.], ack 1, win 502, options [nop,nop,TS val 112 ecr 222], length 0
12:01:11.105500 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [P.], seq 1:1461, ack 1, win 502, options [nop,nop,TS val 113 ecr 222], length 1460
12:01:12.108000 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [P.], seq 1:1461, ack 1, win 502, options [nop,nop,TS val 114 ecr 222], length 1460
Значення: MSS — 1460. Дані сегменти по 1460 байт повторно передаються, що свідчить про те, що вони не проходять. Якщо Path MTU близько 1420, ці сегменти будуть занадто великими після інкапсуляції.
Рішення: обмежте MSS вниз (наприклад, до 1360–1380) або знизьте MTU у мережі контейнера, щоб MSS узгоджувався менший.
Завдання 12: Перевірте MTU маршруту і політику маршрутизації (поширено для VPN-клієнтів)
cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 dev wg0 src 10.8.0.2 uid 1000
cache
Значення: трафік виходить через wg0. Це інтерфейс із меншим MTU.
Рішення: застосуйте обмеження MSS на egress wg0 (або ingress) для форварденого трафіку контейнерів, або встановіть MTU Docker-мережі так, щоб він поміщався у wg0.
Завдання 13: Перевірте конфігурацію MTU демона Docker (якщо є)
cr0x@server:~$ cat /etc/docker/daemon.json
{
"log-driver": "journald"
}
Значення: перебаченої заміни MTU немає.
Рішення: якщо потрібно стабільне виправлення через перезавантаження та рестарти контейнерів, явно налаштуйте MTU демона Docker (або налаштуйте MTU у вашому CNI/оверлеї).
Завдання 14: Перевірте sysctl, що впливають на PMTUD
cr0x@server:~$ sysctl net.ipv4.ip_no_pmtu_disc net.ipv4.tcp_mtu_probing
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.tcp_mtu_probing = 0
Значення: PMTUD увімкнено (добре), але TCP MTU probing вимкнено (за замовчуванням).
Рішення: не «виправляйте» MTU увімкненням probing глобально, поки не розумієте додаткові наслідки; надавайте перевагу коректному MTU/MSS.
Виправлення, що працюють: вирівнювання MTU та обмеження MSS
Стратегія виправлення A: Вирівняйте MTU в underlay, overlay і інтерфейсах контейнерів
Це чисте рішення: пакети за розміром правильні, PMTUD — страховка, а ваші дампи трафіку стануть нудними. Потрібно більше думки спершу, але потім менше магії.
1) Якщо ви використовуєте тунель, прийміть його оверхед
Якщо egress іде через wg0 з MTU 1420, ви можете встановити MTU мосту Docker на 1420 (або трохи менше, щоб врахувати додатковий оверхед залежно від шляху). Ідея: переконатися, що MTU інтерфейсу контейнера не більше за найменший ефективний MTU на шляху.
2) Налаштуйте MTU демона Docker (bridge networks)
Сконфігуруйте Docker так, щоб мережі створювалися з конкретним MTU. Приклад:
cr0x@server:~$ sudo sh -lc 'cat > /etc/docker/daemon.json <<EOF
{
"mtu": 1420,
"log-driver": "journald"
}
EOF'
cr0x@server:~$ sudo systemctl restart docker
Значення: нові інтерфейси контейнерів, створені Docker, повинні використовувати MTU 1420.
Рішення: перезапустіть або пересоздайте уражені контейнери (існуючі veth-пари можуть зберегти старий MTU). Плануйте контрольований rollout.
3) Для мереж, створених користувачем, встановіть MTU явно
cr0x@server:~$ docker network create --driver bridge --opt com.docker.network.driver.mtu=1420 appnet
a1b2c3d4e5f6g7h8i9j0
Значення: ця мережа використовуватиме MTU 1420 для свого bridge/veth.
Рішення: підключайте робочі навантаження, які проходять через тунель, до цієї мережі; тримайте «локальні» мережі на 1500, якщо доречно.
4) Оверлеї: обчислюйте MTU, а не вгадуйте
Для VXLAN-оверлеїв на підлеглій мережі 1500 типовим є налаштування MTU 1450. Якщо підлегла сама інкапсулюється (VPN), ваш оверлейний MTU має бути ще меншим. Стек VXLAN-over-WireGuard може швидко стати тісним.
Коректний підхід — емпіричний: виміряйте ефективний path MTU між вузлами (DF-ping), потім відніміть оверхед інкапсуляції, який ви додаєте. Або, якщо простіше: встановлюйте MTU оверлею на основі найменшого підлеглого MTU, який у вас фактично є.
Стратегія виправлення B: Обмеження TCP MSS (швидко, практично, трохи огидно)
Обмеження MSS — як турнікет полевого медика: зупиняє кровотечу, навіть якщо анатомію ще не виправили. Особливо корисно, коли path MTU стабільний, але кінцеві точки думають інакше, або коли ви не можете швидко змінити MTU на всіх контейнерах/мережах.
Що воно робить: перезаписує MSS у SYN-пакетах, щоб кінцеві точки ніколи не відправляли TCP-сегменти занадто великі для шляху. Це не виправляє UDP-протоколи напряму і не виправляє IPv6, якщо ви не робите аналогічні правила в ip6tables/nft для v6.
1) Обмежте MSS для форварденого трафіку контейнерів, що виходить через тунель
Якщо контейнери за NAT на хості і егансять через wg0, обмежте на шляху FORWARD.
cr0x@server:~$ sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Значення: ядро підлаштовує MSS згідно з виявленим PMTU для того маршруту.
Рішення: якщо PMTUD зламаний (ICMP заблокований), режим clamp-to-pmtu може не збігтися; тоді використовуйте фіксований MSS.
2) Обмежте до фіксованого MSS, коли PMTUD ненадійний
cr0x@server:~$ sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
Значення: MSS встановиться в 1360 для цих потоків, що вміщується в ~1420 MTU із запасом для заголовків/опцій.
Рішення: вибирайте MSS консервативно. Надто низьке зменшить пропускну здатність; надто високе — не вирішить проблему.
3) Перевірте, що обмеження MSS активне
cr0x@server:~$ sudo iptables -t mangle -S FORWARD | grep TCPMSS
-A FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
Значення: правило присутнє.
Рішення: знову запустіть tcpdump і підтвердіть, що SYN рекламує MSS 1360.
4) Підтвердіть у tcpdump, що MSS змінився
cr0x@server:~$ sudo tcpdump -ni wg0 'tcp[tcpflags] & tcp-syn != 0 and host 10.20.30.40' -c 2
12:05:44.000001 IP 10.8.0.2.53312 > 10.20.30.40.8080: Flags [S], seq 1, win 64240, options [mss 1360,sackOK,TS val 333 ecr 0,nop,wscale 7], length 0
12:05:44.002000 IP 10.20.30.40.8080 > 10.8.0.2.53312: Flags [S.], seq 2, ack 2, win 65160, options [mss 1360,sackOK,TS val 444 ecr 333,nop,wscale 7], length 0
Значення: MSS тепер 1360; ви більше не повинні надсилати занадто великі сегменти.
Рішення: повторіть тест із великим POST. Якщо він пройде — у вас миттєва міра пом’якшення.
Стратегія виправлення C: Перестаньте лама́ти PMTUD (команда безпеки переживе)
Якщо ви керуєте файрволами, дозволіть ICMP, потрібний для PMTUD:
- IPv4: ICMP тип 3 код 4 («Fragmentation needed»).
- IPv6: ICMPv6 тип 2 («Packet Too Big»).
Також дозволяйте RELATED conntrack-трафік для форвардених потоків. Хости Docker форвардять трафік; ставтеся до них як до роутерів.
Жарт №2: Блокування ICMP заради «покращення безпеки» — як прибрати лампочку «масло», бо вона відволікає.
Три корпоративні міні-історії (усі в дусі правди)
Інцидент 1: Неправильне припущення («MTU завжди 1500»)
Команда розгорнула нову контейнеризовану службу інґесту. У стенді все працювало, а в продакшені почало падати лише для певних клієнтів. Менші пейлоади були в порядку. Великі завантаження зависали й таймаутилися через кілька хвилин. Логи застосунку були марні: запит дійшов, а потім — нічого. Метрики балансувальника показували з’єднання, що зависли в стані «active».
На чергуванні спершу підозрювали повільний апстрім. Це логічно: «таймаути» в розподілених системах зазвичай — проблеми залежностей. Але знімки пакетів показали повторні передачі повноцінних TCP-сегментів. SYN/SYN-ACK виглядали нормально. Перший шматок тіла запиту зник у порожнечі.
Припущення було простим: «мережевий MTU — 1500». На NIC хоста це було так. Але на шляху до певних клієнтських мереж — ні, бо трафік хапався через IPsec-тунель, яким керувала інша команда. Той тунель мав ефективний MTU ближче до 1400, і ICMP фільтрувався «заради безпеки».
Виправлення було двоетапним: дозволити PMTUD-пов’язані ICMP через тунель і обмежити MSS на краю тунелю. Після цього великі завантаження одразу стали звичайними. Ретроспектива була суворною, але корисною: MTU — це не властивість одного інтерфейсу. Це властивість шляху, і шляхи змінюються.
Інцидент 2: Оптимізація, що повернулась боком («увімкнути jumbo frames скрізь»)
Ще одна організація хотіла кращої пропускної здатності для передач між контейнерами. Хтось запропонував увімкнути jumbo frames (MTU 9000) у дата-мережі. Це було продано як низькоризикове підвищення продуктивності: менше пакетів, менше CPU, вища пропускна спроможність. Вони протестували між двома хостами — було швидше. Усі аплодували і змерджили зміни.
За тиждень почалися дивні явища. Деякі служби були в порядку. Інші мали періодичні проблеми: скидання gRPC-стрімів, випадкові помилки TLS, «випадкові» таймаути при завантаженні образів. Найдивніше: графіки втрат пакетів були чисті. Затримка — нормальна. Проблеми були лише на певних шляхах.
Проблема була не в jumbo сама по собі. Проблема була в частковому розгортанні і в одному хопі з підтримкою лише 1500: кластер файрволів, який не підтримував jumbo frames на одному інтерфейсі. Деякий трафік йшов через шлях із jumbo, частина — ні, в залежності від маршрутизації і failover.
Тепер мережа мала розділений MTU. Хости щасливо випускали кадри 9000. Коли вони потрапляли на 1500-only хоп, покладалися на PMTUD. ICMP «Packet Too Big» був обмежений за швидкістю на файрволі і іноді відкидався. Результат — чорні діри, що виникали і зникали залежно від навантаження та подій failover.
Вони в кінці кінців стандартизували MTU до 1500 на тому сегменті і використали налаштування LRO/GRO плюс паралелізм на рівні застосунку для продуктивності. Jumbo frames можуть бути чудовими, але єдиний безпечний jumbo MTU — той, який послідовно підтримується end-to-end.
Інцидент 3: Нудна практика, що врятувала день (стандартні MTU-тести при rollout)
Команда платформи тримала базовий скрипт «готовності вузла» для кожного нового ноду. Це було неістотно. Він запускав кілька перевірок: DNS, синхронізація часу, місце на диску і — тихо — пару PMTU-тестів до ключових цілей (control plane, registry, шлюзи mesh). Також перевіряв, що мережі контейнерів мають очікувані значення MTU.
Під час міграції підмножина вузлів була розміщена в іншій підмережі, яка маршрутизувала через VPN-апарат. Навантаження, що пішло на ті ноди, відразу показало підвищений рівень помилок, але скрипт готовності спіймав це до того, як відбувся великий інцидент. PMTU-перевірка провалила DF-пінги на 1500 і вказала на менший MTU.
Оскільки проблему виявили рано, виправлення було хірургічним: команда встановила MTU Docker-мережі на тих вузлах так, щоб відповідати VPN-шляху, і додала обмеження MSS на egress VPN. Також вони задокументували відмінність підмережі, щоб мережі пізніше могли прибрати непотрібний тунель.
Нічого героїчного в цьому не було. Ось у чому суть. Рутинні MTU-перевірки перетворили багатоденне розслідування на 30-хвилинну зміну. Нудне — це фіча в операціях.
Поширені помилки: симптом → головна причина → виправлення
1) Симптом: «Малі запити працюють; великі POST’и висять із контейнерів»
Головна причина: невідповідність MTU плюс зламаний PMTUD (ICMP заблокований) або не врахований оверхед тунелю.
Виправлення: виміряйте path MTU DF-ping; встановіть MTU мережі Docker відповідно або обмежте TCP MSS на інтерфейсі egress.
2) Симптом: «Працює на хості, падає в контейнері»
Головна причина: шлях мережі контейнера відрізняється (NAT/forwarding правила, інша таблиця маршрутизації, policy routing, оверлей).
Виправлення: перевірте ip route get на хості для цілі; tcpdump на docker0 і інтерфейсі egress; обмежте MSS для форварденого трафіку або вирівняйте MTU.
3) Симптом: «TLS-handshake іноді падає; curl іноді зависає після CONNECT»
Головна причина: великі TLS-хендшейк-записи перевищують path MTU; повторні передачі; ICMP заблокований або обмежений по швидкості.
Виправлення: обмежте MSS; переконайтеся, що ICMP PTB/frag-needed дозволено; перевірте в tcpdump, що MSS знижено.
4) Симптом: «Оверлей мережі падає для великих пакетів; ping між вузлами працює»
Головна причина: MTU оверлею не зменшено, щоб врахувати оверхед інкапсуляції (VXLAN/Geneve).
Виправлення: встановіть MTU оверлею/CNI (часто ~1450 для VXLAN на підлеглій 1500), забезпечте послідовну конфігурацію на всіх вузлах.
5) Симптом: «Тільки IPv6: деякі напрямки недоступні, дивні затримки»
Головна причина: ICMPv6 Packet Too Big блокується; IPv6-роутери не фрагментують, тому PMTUD обов’язковий.
Виправлення: дозволіть ICMPv6 тип 2; при потребі обмежте MSS для IPv6 TCP; не ставтеся до ICMPv6 як до необов’язкового.
6) Симптом: «Після зміни MTU хоста старі контейнери все ще падають»
Головна причина: існуючі veth-пари зберегли старий MTU; Docker не завжди ретрофітує живі мережі.
Виправлення: пересоздайте мережі/контейнери; встановіть MTU в daemon/user-network, щоб нові приєднання були коректними.
7) Симптом: «Тільки трафік через VPN ламається; внутрішній трафік в порядку»
Головна причина: MTU інтерфейсу VPN менший за LAN; MTU контейнера/моста занадто великий; PMTUD зламаний через VPN.
Виправлення: обмежте MSS на egress VPN; опційно використайте окрему Docker-мережу з меншим MTU для робочих навантажень, що йдуть через VPN.
8) Симптом: «UDP-служба (DNS, QUIC, syslog) втрачає великі повідомлення»
Головна причина: обмеження MSS не допомагає UDP; фрагментація може бути заблокована; UDP-пейлоад перевищує path MTU.
Виправлення: зменшіть розмір повідомлень на рівні застосунку; використовуйте TCP, якщо можливо; вирівняйте MTU і дозвольте фрагментацію/ICMP за потреби.
Контрольні списки / покроковий план
Чекліст A: Швидке стримування (15–30 хвилин)
- Відтворіть помилку великим запитом з контейнера.
- Запустіть DF-ping з хоста до цілі і знайдіть максимальний робочий розмір.
- Ідентифікуйте інтерфейс виходу для цієї цілі (
ip route get). - Застосуйте обмеження MSS на інтерфейсі egress для форварденого трафіку:
- Переважно
--clamp-mss-to-pmtu, якщо PMTUD працює. - Використовуйте
--set-mss, якщо PMTUD зламаний або непослідовний.
- Переважно
- Переконайтеся в tcpdump, що SYN MSS зменшено.
- Повторіть тест великого запиту. Підтвердіть успіх.
Чекліст B: Коректне виправлення (той самий день, менше адреналіну)
- Зробіть інвентар всіх шарів інкапсуляції на шляху (VPN, оверлей, хмарна інфраструктура, балансувальники).
- Уніфікуйте MTU підлеглих лінків, які мають бути «однією мережею».
- Встановіть MTU оверлею явно (VXLAN/Geneve/IPIP) і забезпечте його консистентність на вузлах.
- Встановіть MTU демона Docker або MTU для конкретних мереж bridge, які мають проходити через обмежені шляхи.
- Переконайтеся, що ICMP, потрібний для PMTUD, дозволений (IPv4 frag-needed, IPv6 packet-too-big), включно з FORWARD шляхами.
- Задокументуйте очікувані значення MTU і додайте автоматичний PMTU-тест у готовність вузла.
Чекліст C: Валідація після виправлення (не пропустіть)
- Захопіть короткий tcpdump під час великої передачі; підтвердьте відсутність повторних передач великих сегментів.
- Перевірте рівень помилок застосунку; переконайтеся, що конкретний симптом зник (а не «виглядає краще»).
- Переконайтеся, що продуктивність прийнятна; занадто малий MSS може знизити пропускну здатність на шляхах з високою BDP.
- Перезавантажте один вузол (або рестартуйте Docker) у вікні техобслуговування, щоб впевнитися, що конфігурація зберігається.
Поширені запитання
1) Чому ping’и проходять, а HTTP-завантаження падають?
За замовчуванням ping використовує малі пейлоади, які поміщаються майже під будь-який MTU. Великі HTTP-завантаження породжують великі TCP-сегменти, які перевищують path MTU і зникають, коли PMTUD/ICMP зламані.
2) Це баг Docker?
Зазвичай ні. Docker лише посилює проблему: він додає додаткові хопи (мост, veth, NAT) і полегшує випадкове відправлення трафіку через тунелі/оверлеї з меншим MTU, ніж думає контейнер.
3) Чи просто встановити MTU 1400 всюди і забути?
Лише якщо вам подобається постійний податок на продуктивність і таємничі регресії пізніше. Виміряйте найменший реальний path MTU, який треба підтримувати, потім налаштуйте MTU відповідно для кожної мережі. Використовуйте MSS clamping як цілеспрямовану міру, а не як постійний стан.
4) Що краще: знизити MTU чи обмежити MSS?
Зниження MTU — чистіше і працює для TCP та UDP. Обмеження MSS швидше розгорнути і часто достатнє для TCP, але не вирішує проблем з UDP-пейлоадами.
5) Чому блокування ICMP ламає TCP? TCP ж не ICMP.
PMTUD використовує ICMP як сигнальну площину, щоб сказати «ваш пакет занадто великий». Без цього зворотного зв’язку TCP продовжує надсилати занадто великі DF-пакети і вічно їх повторює. Дана «підказка» ніколи не приходить.
6) Чи змінює Kubernetes щось у цьому?
Концепції ідентичні; площа поверхні збільшується. CNI-плагіни, інкапсуляція між вузлами та service mesh додають оверхед і складність. Kubernetes просто дає вашій помилці MTU більше місць, де ховатися.
7) Що з IPv6?
IPv6 ще більше залежить від PMTUD: роутери не фрагментують. Якщо ICMPv6 Packet Too Big блокується, ви отримаєте жорстку помилку на шляхах з меншим MTU. Ставтеся до ICMPv6 як до необхідної інфраструктури.
8) Чи вирішать це sysctl для TCP MTU probing?
Іноді, але це останній засіб. MTU probing може маскувати зламані мережі, адаптуючись після втрат, але це не заміна правильного MTU і працюючого ICMP. У продакшені надавайте перевагу детерміністичним виправленням.
9) Як підібрати фіксований MSS?
Почніть із виміряного path MTU. MSS повинен бути MTU мінус заголовки. Для IPv4 TCP без опцій це зазвичай MTU-40, але опції (timestamps тощо) ефективно зменшують його. Саме тому значення на кшталт 1360 для шляху 1420 поширені: консервативні, безпечні, не максимальні.
10) Чому це падає лише для деяких напрямків?
Бо MTU — властивість шляху, і маршрути різні. Одна ціль може залишатися в LAN на 1500; інша перетинає тунель на 1420; третя проходить через неправильно налаштований файрвол, що відкидає ICMP. Той самий код — різна фізика.
Висновок: практичні наступні кроки
Коли великі запити падають із контейнерів і все інше здається «в порядку», підозрюйте MTU/MSS перш за все. Це не марновірство; це відпрацьована в реальних умовах причина з послідовними відбитками: зависання залежно від розміру, повторні передачі і шлях, що мовчки не може нести те, що ви надсилаєте.
Зробіть наступне:
- Відтворіть з великим пейлоадом з контейнера і з хоста. Підтвердьте форму бага.
- Виміряйте path MTU DF-ping’ами до реальної цілі.
- Знайдіть фактичний інтерфейс egress і будь-які тунелі/оверлеї на шляху.
- Негайно пом’якшіть проблему обмеженням MSS, якщо потрібен uptime прямо зараз.
- Виправте правильно: вирівняйте MTU між Docker-мережами, оверлеями й підлеглими мережами, і дозвольте PMTUD-потрібний ICMP.
- Зробіть це нудним назавжди: додайте PMTU-перевірки в готовність вузла і стандартизуйте налаштування MTU як код.