Часті зміни сокетів: коли платформи перетворюються на пастки оновлень

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

Ви хотіли оновити базовий образ, поміняти сертифікат або перейти на свіжішу мінорну версію Kubernetes. Замість цього отримали пейджер.
Графіки виглядали нормально минулого тижня. Тепер усе «здорове», але повільне. Хвилі латентності. Кількість повторних SYN зростає.
І ваше «просте оновлення» перетворюється на референдум щодо платформи.

Часті зміни сокетів — це одна з тих проблем, що здаються мережею, поки ви не зрозумієте: це проблема бюджету розподілених систем:
дескриптори файлів, ефемерні порти, записи NAT/conntrack, CPU для рукостискань TLS і час у чергах, про які ви не знали.
Платформи стають пастками оновлень, коли нова версія змінює «економіку» з’єднань — трохи інші налаштування за замовчуванням, нові сайдкари, нові перевірки здоров’я,
інша поведінка балансувальника навантаження — і ваша система падає зі скелі.

Що таке часті зміни сокетів (і чому оновлення їх викликають)

Часті зміни сокетів — це швидкість, з якою ваша система створює й закриває мережеві з’єднання. У термінах TCP: відкриття з’єднання (SYN/SYN-ACK/ACK),
опційні TLS-рукостискання, стабільний обмін даними, потім закриття (FIN/ACK) і хвіст обліку (TIME_WAIT, старіння conntrack, NAT-мапінги).
У продуктивних систем «занадто багато churn» стосується не одного з’єднання, а сукупних витрат на їх запуск і завершення.

Пастка в тому, що churn часто залишається під порогом — поки оновлення не ссуне один параметр. Можливо, сайдкар-проксі змінив повторне використання з’єднань.
Можливо, перевірки здоров’я балансувальника стали агресивнішими. Можливо, у клієнтській бібліотеці змінилися налаштування keepalive за замовчуванням.
Можливо, кластер почав віддавати перевагу IPv6 і шлях через NAT став іншим. Кожна зміна виправдана. Разом вони множаться.

Якщо взяти один висновок з цієї статті, запам’ятайте його: життєвий цикл з’єднання — це вимір ємності.
Не лише пропускна здатність. Не лише QPS. Не лише CPU. Якщо ваш роадмап вважає з’єднання безкоштовними, ви навмисне будуєте пастку оновлень.

Одна цитата, яка варта постера на стіні, бо тут пасує болісно точно:

«Сподівання — не стратегія.» — Gene Kranz

Часті зміни сокетів проти «мережа повільна»

Традиційна діагностика «мережа повільна» фокусується на пропускній здатності та втраті пакетів. Відмови через churn часто виглядають інакше:
короткоживучі з’єднання підсилюють хвіст латентності, створюють сплески повторних передач, перевантажують conntrack і спалюють CPU у ядрі та TLS-бібліотеках.
У вас може бути вдосталь пропускної здатності, і все одно буде падіння, бо вузьке місце — це робота на з’єднання та стан на з’єднання.

Система з низьким churn терпить джиттер. Система з високим churn перетворює незначний джиттер на гуркіт: повтори створюють більше з’єднань; більше з’єднань підвищують черги;
черги збільшують таймаути; таймаути збільшують повтори. Ви вже знаєте цикл.

Як оновлення стають пастками: приховані множники

Оновлення змінюють налаштування за замовчуванням. Налаштування за замовчуванням змінюють поведінку. Поведінка змінює кардинальність. Кардинальність змінює стан. Стан змінює латентність.
Оце й увесь фільм.

Множник №1: повторне використання з’єднань тихо зникає

HTTP/1.1 keepalive міг бути ввімкнений «деінде» і вимкнутися «деінде ще». Або проксі починає закривати простаєві з’єднання агресивніше.
Або нова клієнтська бібліотека переходить від глобальних пулів з’єднань до пулів на хост/маршрут з меншими лімітами.

Наслідком churn буде: більше SYN на запит, більше сокетів у TIME_WAIT, більше TLS-рукостискань
і більше споживання ефемерних портів — особливо за NAT-шлюзами або при вузловому SNAT.

Множник №2: сайдкари і сітки додають станових посередників

Сервісна сітка — це не «тільки латентність». Часто це ще один хоп з’єднання, додаткові політики рукостискань і додаткове буферування.
Якщо mesh термінує TLS, це може заохотити частіше виконання рукостискань; якщо він повторно ініціює upstream-з’єднання,
він змінює місце створення сокетів і, отже, місце споживання ефемерних портів і записів conntrack.

Іноді mesh працює чудово. Пастка виникає, коли ви оновлюєте mesh і його значення за замовчуванням змінюються — idle timeout, circuit-breaking, retries.
Ви не міняли додаток. Але ви змінили спосіб, у який додаток розмовляє зі світом.

Множник №3: перевірки здоров’я та проби стають фабрикою сокетів

Один readiness probe на под не здається багато, поки ви не помножите це на кількість подів, вузлів, кластерів і балансувальників навантаження.
Якщо проби використовують HTTP без keepalive (або ціль закриває з’єднання), вони створюють churn.

Оновлення часто змінюють поведінку проб: частіші інтервали, інші ендпоінти, більша паралельність, більше шарів (ingress → service → pod).
У масштабі «тільки проба» стає фоновим DDoS з вашого власного хазяйства.

Множник №4: NAT і conntrack стають справжньою «базою даних»

У контейнерних платформах пакети часто проходять через NAT: pod-to-service, node-to-external, external-to-nodeport. NAT потребує стану.
Linux відстежує цей стан у conntrack. Коли conntrack заповнюється, нові з’єднання падають у способи, що виглядають випадково й жорстоко.

Пастки оновлення відбуваються, коли навіть невелике збільшення швидкості з’єднань: записи conntrack живуть достатньо довго, щоб таблиця заповнилася.
Тепер ваша система залежить від плану ємності хеш-таблиці ядра. Вітаємо, ви керуєте stateful firewall як key-value сховищем.

Множник №5: зміни політики TLS та крипто налаштувань перетворюють CPU на вузьке місце

Швидші шифри й resumption допомагають, але найбільший важіль — як часто ви робите рукостискання.
Оновлення, яке підвищує кількість рукостискань у 3×, може виглядати як «CPU став повільніший», бо гарячий шлях змінився.
Це особливо приємно, коли оновлення також активує суворіші набори шифрів або вимикає старі режими resumption.

Множник №6: «безпечні» зміни таймаутів створюють масові перепідключення

Змініть idle timeout з 120 секунд на 30 — і ви не просто змінюєте число. Ви змінюєте синхронізацію.
Клієнти тепер перепідключаються частіше, і вони перепідключаються хвилями. Коли весь флот отримує однакове значення за замовчуванням, ви створюєте періодичні бурі.

Жарт №1: Якщо хочете побачити «виникаючу поведінку», встановіть однаковий таймаут на всіх клієнтах і спостерігайте, як вони синхронізуються як нервові метрономи.

Факти й контекст: чому це повторюється

Трохи історії допомагає, бо часті зміни сокетів — не новина. Ми просто постійно її відкриваємо наново з красивішим YAML.

  • TIME_WAIT існує, щоб захищати від запізнілих пакетів, а не щоб псувати вам день. Це захисна функція, яка під час churn стає лімітом масштабування.
  • Ефемерні порти обмежені. У Linux типовий діапазон ephemeral — близько 28k портів; це небагато при високому churn за NAT.
  • Conntrack за дизайном stateful. Таблиця має відстежувати потоки для NAT і firewall; вона може стати жорсткою межею на швидкість з’єднань.
  • HTTP/2 зменшив кількість з’єднань за рахунок мультиплексування стрімів, але приніс інші режими відмов (head-of-line blocking на TCP-шарі, поведінка проксі).
  • Балансувальники навантаження мають власне відстеження з’єднань. Навіть якщо ваші сервери в порядку, L4-балансувальник може першим вичерпати ресурси на потік.
  • Kubernetes популяризував liveness/readiness probes. Чудова ідея, але вона нормалізувала часті фон-запити, що в масштабі стають churn.
  • Service meshes відродили управління з’єднаннями на кожному хопі. «Ще один проксі» іноді того вартує, але це змінює місце життя сокетів.
  • Налаштування TCP keepalive за замовчуванням консервативні і часто не релевантні для рівня додатків; люди копіюють sysctl без вимірів.
  • Повтори множаться. Одне змінення політики retry може подвоїти або потроїти швидкість з’єднань при часткових відмовах.

Режими відмов: що ламається першим

Часті зміни сокетів зазвичай не призводять до чистого «out of memory». Вони ламаються боковим шляхом.
Ось поширені точки перелому, приблизно в тому порядку, в якому ви їх зустрічаєте.

1) Вичерпання ефемерних портів (зазвичай на клієнтах або вузлах NAT)

Симптоми: клієнти отримують таймаути підключення; логи показують «cannot assign requested address»; NAT-шлюзи відкидають нові з’єднання; кількість повторів зростає.
Ви можете бачити це лише на частині вузлів, бо використання портів локальне для IP-адреси джерела.

2) Вичерпання таблиці conntrack (зазвичай на вузлах, фаєрволах, NAT-шлюзах)

Симптоми: випадкові обриви з’єднань, нові з’єднання не проходять, логи ядра про заповнену таблицю conntrack, зростають дропи пакетів при нормальному CPU.
Ви можете «вирішити» це тимчасово, збільшивши розмір таблиці — це як купити більший кошик для витоку.

3) Переповнення backlog прослуховування та тиск черги accept

Симптоми: повтори SYN, клієнти бачать спорадичні таймаути, сервер виглядає недовантаженим, але ви падаєте в accept-черзі.
Часто при збільшенні швидкості з’єднань без підвищення accept-ємності або налаштування backlog.

4) Ліміти дескрипторів файлів і обмеження на процес

Симптоми: «too many open files», загадкові відмови accept або деградація продуктивності при наближенні до лімітів.
Churn це підсилює, бо більше сокетів у транзитних станах.

5) Насичення CPU у ядрі + TLS + шарі проксі

Симптоми: високий системний CPU, зростання переключень контексту, CPU для TLS-рукостискань домінує, процеси проксі спайкуються.
Додаток може бути не вузьким місцем; проблема в інфраструктурі.

6) Побічні ефекти зберігання та логування (так, насправді)

Високий churn часто збільшує обсяг логів (логи з’єднань, логи помилок, повтори). Це може створити зворотний тиск на диски, заповнити томи
і спричинити вторинні відмови. Тут інженер зберігання даних у мені покашлює.

Жарт №2: Нічого так не говорить «надійна розподілена система», як знищення продакшену через те, що логи помилок з’єднань заповнили диск.

Швидкий план діагностики

Коли час підтискає, вам нема коли милуватися складністю. Потрібна послідовність, що швидко знаходить вузьке місце.
Це порядок, який я використовую в реальних інцидентах, бо він рано відокремлює «проблему швидкості/стану з’єднань» від «пропускної здатності».

Спочатку: доведіть, що це churn (а не пропускна здатність)

  • Перевірте швидкість створення нових з’єднань проти швидкості запитів. Якщо з’єднань на запит стало більше — знайшли запах.
  • Подивіться на повторні передачі SYN/SYN-ACK. Проблеми churn проявляються як нестабільність рукостискань.
  • Порівняйте кількість TIME_WAIT і ESTABLISHED з часом. Churn підвищує TIME_WAIT; витоки — ESTABLISHED.

По-друге: знайдіть, де закінчується стан

  • На клієнтах/NAT-вузлах: використання ефемерних портів, накопичення TIME_WAIT, поведінка SNAT.
  • На вузлах/фаєрволах: використання conntrack і дропи.
  • На серверах: backlog прослуховування, черга accept, ліміти fd.

По-третє: ідентифікуйте тригер, який ввів оновлення

  • Зміни таймаутів: idle timeouts, keepalive, lifetimes проксі-з’єднань.
  • Нові проби: liveness/readiness, health checks балансувальників, телеметрія mesh.
  • Зміни політики повторів: оновлення клієнтської бібліотеки, дефолтні retries сайдкарів, налаштування circuit breaker.
  • Політика TLS: нові набори шифрів, зміни resumption, зміни ланцюга сертифікатів.

По-четверте: стабілізуйте

  • Зменшіть створення з’єднань: увімкніть keepalive/пулі, зменшіть retries, підвищіть повторне використання.
  • Збільште запас ресурсу: розширте діапазон ephemeral, розмір conntrack, ліміти fd (як тимчасовий захід, не як стиль життя).
  • Сповільніть фоновий churn: проби й перевірки здоров’я.

Практичні завдання: команди, виводи, рішення (12+)

Це практичні завдання, які можна виконати на Linux-вузлах (bare metal або VM) й інтерпретувати швидко.
Кожне завдання містить: команду, приклад виводу, що це означає, і яке рішення прийняти.
Підлаштуйте шляхи й імена інтерфейсів під ваше середовище.

Завдання 1: Порахуйте сокети за станом (сервер або клієнт)

cr0x@server:~$ ss -ant | awk 'NR>1 {s[$1]++} END{for (k in s) printf "%s %d\n", k, s[k]}' | sort -k2 -n
ESTAB 842
TIME-WAIT 19234
SYN-RECV 12
FIN-WAIT-1 3
FIN-WAIT-2 7

Що це означає: TIME-WAIT, що переважає над ESTAB, зазвичай вказує на короткоживучі з’єднання і churn. SYN-RECV натякає на тиск на backlog/accept.

Рішення: Якщо TIME-WAIT величезний і зростає, пріоритет — keepalive/пули і перевірка використання ефемерних портів. Якщо SYN-RECV підвищено, перевірте backlog і чергу accept.

Завдання 2: Знайдіть топ віддалених пірів, що створюють churn

cr0x@server:~$ ss -ant state time-wait | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
15422 10.42.18.91
 2981 10.42.22.14
 1710 10.42.19.37

Що це означає: Один або кілька пірів генерують більшість churn.

Рішення: Ідіть до тих клієнтів/сайдкарів і перевірте їхні налаштування keepalive, пулів і retry. Рідко це лише серверна проблема.

Завдання 3: Перевірте діапазон ефемерних портів

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

Що це означає: Цей діапазон дає приблизно 28k портів на джерело IP. За NAT це може бути весь ваш бюджет одночасних виходів.

Рішення: Якщо ви вичерпуєте порти, розширте діапазон (обережно) і, що важливіше, зменште churn через повторне використання.

Завдання 4: Виявити тиск на ефемерні порти (з боку клієнта)

cr0x@server:~$ ss -ant sport ge 32768 sport le 60999 | wc -l
29844

Що це означає: Ви близькі до загального діапазону ефемерних портів; колізії й помилки підключення стають ймовірними.

Рішення: Екстрено: зменшіть створення нових з’єднань (тротлінг, вимкнення агресивних повторів) і розширте діапазон портів. Довгостроково: keepalive/пули і менше NAT-колодязів.

Завдання 5: Перевірте помилки «cannot assign requested address»

cr0x@server:~$ sudo journalctl -k -n 50 | egrep -i "assign requested address|tcp:|conntrack"
Jan 13 08:41:22 node-17 kernel: TCP: request_sock_TCP: Possible SYN flooding on port 443. Sending cookies.
Jan 13 08:41:27 node-17 kernel: nf_conntrack: table full, dropping packet

Що це означає: SYN cookies вказують на тиск на backlog; дропи conntrack — на вичерпання стану.

Рішення: Якщо conntrack заповнений, потрібно негайне полегшення: зменшити нові з’єднання, збільшити max conntrack і знайти джерело churn. Якщо SYN cookies з’являються при нормальному навантаженні — налаштуйте backlog і accept-ємність.

Завдання 6: Виміряти використання conntrack (вузол або шлюз)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 247981
net.netfilter.nf_conntrack_max = 262144

Що це означає: Ви на ~95% використання. Під сплеском ви будете скидати нові потоки.

Рішення: Короткостроково: збільште max, якщо дозволяє пам’ять. Реальне виправлення: зменшити створення потоків (reuse, зменшення проб/повторів, налаштування таймаутів).

Завдання 7: Проінспектувати топ-говорців conntrack (потребує conntrack tool)

cr0x@server:~$ sudo conntrack -S
entries 247981
searched 0
found 0
new 98214
invalid 73
ignore 0
delete 97511
delete_list 97511
insert 98214
insert_failed 331
drop 1289
early_drop 0
icmp_error 0
expect_new 0
expect_create 0
expect_delete 0
search_restart 0

Що це означає: Високий показник «new» і ненульові insert_failed/drop вказують, що churn перевищує можливості.

Рішення: Стабілізувати, зменшивши створення з’єднань негайно; потім переглянути розмір conntrack і таймаути.

Завдання 8: Перевірити backlog прослуховування та поведінку accept-черги

cr0x@server:~$ ss -lnt sport = :443
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 4096   4096   0.0.0.0:443       0.0.0.0:*

Що це означає: Якщо Recv-Q росте в напрямку Send-Q під навантаженням, accept відстає, backlog заповнюється і SYNи можуть повторюватись.

Рішення: Збільшіть accept-ємність сервера (воркери/потоки), налаштуйте backlog (somaxconn, tcp_max_syn_backlog) і зменшіть швидкість створення з’єднань (keepalive, пули).

Завдання 9: Перевірити sysctl, що стосуються backlog

cr0x@server:~$ sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_syncookies
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_syncookies = 1

Що це означає: Ці обмеження впливають на те, наскільки добре ви поглинаєте сплески нових з’єднань. Увімкнені syncookies — гарна річ; їх використання — попереджувальний знак.

Рішення: Якщо ви втрачаєте під час сплесків підключень, підвищте backlogs і виправте джерело churn. Налаштування backlog — не заміна повторному використанню з’єднань.

Завдання 10: Подивіться тиск TIME_WAIT і таймери

cr0x@server:~$ cat /proc/net/sockstat
sockets: used 28112
TCP: inuse 901
orphan 0
tw 19234
alloc 1234
mem 211
UDP: inuse 58
RAW: inuse 0
FRAG: inuse 0 memory 0

Що це означає: «tw» — це TIME_WAIT. Високі значення сильно корелюють з churn і тиском на ефемерні порти (особливо на клієнтах).

Рішення: Розглядайте високий TIME_WAIT як симптом. Спочатку виправте повторне використання з’єднань і поведінку клієнтів; sysctl-хакі — пізніше і з розумінням компромісів.

Завдання 11: Перевірити ліміти дескрипторів файлів і їх використання

cr0x@server:~$ ulimit -n
1024
cr0x@server:~$ pidof nginx
2174
cr0x@server:~$ sudo ls /proc/2174/fd | wc -l
987

Що це означає: Ви близькі до пер- процесного ліміту fd. Під churn сплески можуть перевести вас за межу, і відмови виглядатимуть довільно.

Рішення: Підвищте ліміти (unit systemd, security limits), але також зменшіть churn, щоб не просто піднімати стелю над вогнем.

Завдання 12: Перевірити CPU-гарячі точки рукостискань TLS (швидкий сигнал від проксі)

cr0x@server:~$ sudo perf top -p 2174 -n 5
Samples: 5K of event 'cycles', 4000 Hz, Event count (approx.): 123456789
Overhead  Shared Object       Symbol
  18.21%  libcrypto.so.3      [.] EVP_PKEY_verify
  12.05%  libssl.so.3         [.] tls13_change_cipher_state
   8.44%  nginx               [.] ngx_http_ssl_handshake

Що це означає: Ваш CPU платить за TLS на кожне з’єднання. Це посилюється з ростом churn або неефективним resumption.

Рішення: Збільшіть повторне використання з’єднань, підтвердьте resumption і розгляньте offload/термінацію лише після зупинки джерела churn.

Завдання 13: Перевірити поведінку keepalive з боку клієнта

cr0x@server:~$ curl -s -o /dev/null -w "remote_ip=%{remote_ip} time_connect=%{time_connect} time_appconnect=%{time_appconnect} num_connects=%{num_connects}\n" https://api.internal.example
remote_ip=10.42.9.12 time_connect=0.003 time_appconnect=0.021 num_connects=1

Що це означає: Для одного запиту num_connects=1 — нормально. Хитрість — зробити сплеск і подивитися, чи переиспользуються з’єднання.

Рішення: Якщо повторні виклики завжди створюють нові з’єднання, виправте клієнтські пула, keepalive проксі або поведінку upstream, що закриває з’єднання.

Завдання 14: Спостерігати повторні передачі і TCP-проблеми

cr0x@server:~$ netstat -s | egrep -i "retransmit|listen|SYNs to LISTEN"
    1287 segments retransmitted
    94 SYNs to LISTEN sockets ignored

Що це означає: Повтори і проігноровані SYN вказують на стрес рукостискань — часто через переповнення backlog або дропи conntrack/NAT.

Рішення: Корелюйте з SYN-RECV і метриками backlog; зменшіть створення нових з’єднань і налаштуйте backlog за потреби.

Завдання 15: Доведіть, що проби створюють churn

cr0x@server:~$ sudo tcpdump -ni any 'tcp dst port 8080 and (tcp[tcpflags] & tcp-syn != 0)' -c 10
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
08:44:01.112233 eth0  IP 10.42.18.91.51234 > 10.42.9.12.8080: Flags [S], seq 123456, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
08:44:01.112310 eth0  IP 10.42.18.91.51235 > 10.42.9.12.8080: Flags [S], seq 123457, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0

Що це означає: Часті SYN на порт проби вказують, що проби відкривають нові TCP-з’єднання замість повторного використання.

Рішення: Зменшіть частоту проб, забезпечте keepalive де можливо або перейдіть на exec-проби для внутрішньоподових перевірок, коли це доречно.

Три корпоративні міні-історії з шахт churn

Міні-історія 1: Інцидент через хибне припущення

Середня компанія мігрувала з самописного розгортання на VM до Kubernetes. Вони зробили відповідально: staged rollout,
уважні SLO і план відкату. Оновлення було «лише» переміщення контролера ingress на нову мажорну версію.
Воно навіть пройшло навантажувальні тести. Звісно.

Хибне припущення: «HTTP keepalive увімкнений за замовчуванням, тож кількість з’єднань не зміниться». На старому ingress upstream keepalive до подів
було сконфігуровано явно роками раніше інженером, що вже пішов. На новому ingress ім’я ключа конфігурації змінилося, і їхні chart-значення
перестали застосовуватись мовчки. Зовнішні клієнти все ще тримали keepalive до ingress, тому графіки зі сторони клієнтів виглядали нормально.

Внутрішньо в кластері кожен запит став новим upstream TCP-з’єднанням від ingress до пода. Це змістило churn до вузлового SNAT,
бо IP подів маршрутизувалися через iptables із conntrack-станом. Нові з’єднання сплеснули. TIME_WAIT на вузлах ingress виріс.
Лічильник conntrack повільно піднімався, поки не досягнув максимуму. Далі почалося: випадкові 502 і upstream timeout-и, але лише на окремих вузлах.

Реакція на інцидент спочатку ганялася за затримками в додатку й запитами до БД, бо цьому навчені всі. Один скептичний мережевий інженер, який запустив ss на вузлах ingress,
помітив TIME_WAIT, що виглядав як телефонний довідник. Вони відновили upstream keepalive, тимчасово зменшили агресивність проб — і відмова швидко закінчилась.

Висновок, який вони записали в runbook, був прямим: вважайте, що keepalive вимкнено, поки не доведете, що увімкнено.
І для оновлень: дивіться diff ефективної конфігурації, а не лише Helm values. Платформа зробила саме те, що їй наказали. Люди наказали неправильно.

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

Інша компанія хотіла швидших розгортань. Вони втомились від зливу з’єднань під час деплоїв, тож скоротили idle timeouts
на внутрішніх L7-проксі. Ідея була в тому, що з’єднання очистяться швидше, поди завершуватимуться швидше, і deploy-и прискоряться.
Виглядало розумно у презентації.

Побічний ефект був тонким. Багато внутрішніх клієнтів використовували бібліотеки, що встановлюють з’єднання тільки при потребі і покладаються на idle keepalive,
щоб уникати повторних витрат рукостискань. Коли проксі почав закривати з’єднання агресивно, клієнти почали частіше перепідключатися.
За стабільного трафіку це перетворилось на фон постійних нових з’єднань. При часткових відмовах виникали бурі перепідключень.

CPU на вузлах проксі піднявся, головним чином в TLS-рукостисканнях. Проксі автошкалився, що трохи допомогло, але також змінило розподіл джерельних IP.
Це викликало іншу вузьку точку: зовнішній NAT-шлюз почав бачити більшу частоту створення потоків за секунду. Conntrack на шлюзі заповнився.
Відмови стали «випадковими», бо різні source IP досягали вичерпання в різний час.

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

Урок: ставтесь до idle timeout як до параметра стійкості, а не зручності. Менший таймаут не означає менше роботи.
Часто це означає ту саму роботу, але частіше і в найгірший момент.

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

Фінансова компанія мала політику, що дратувала розробників: кожне оновлення платформи вимагало перевірки «бюджету з’єднань».
Не рев’ю продуктивності. Перевірку бюджету з’єднань. Люди тихо її кепкували. Іноді голосно.

Їхня SRE-команда відстежувала три співвідношення з часом: з’єднань на запит на краю, з’єднань на запит між рівнями,
і нових з’єднань за секунду на вузлах NAT. Вони зберігали це поруч зі звичними метриками латентності і помилок. Кожна велика зміна — новий проксі,
новий mesh, нова клієнтська бібліотека — мала показати, що ці співвідношення не стрибнули.

Під час оновлення кластера вони одразу помітили: нові з’єднання в секунду з одного неймспейсу подвоїлися,
але швидкість запитів залишилась незмінною. Вони призупинили rollout. Виявилось, що оновлення рантайму мови змінило поведінку DNS за замовчуванням,
що призвело до частішої ресолвінга DNS і перевстановлення з’єднань при ротації ендпоінтів. Додаток ще «працював», але генерував churn.

Оскільки вони виявили проблему рано, ремедіація була невеликою: налаштувати кешування DNS у рантаймі і переконатися, що пула з’єднань не ключуються надто вузько.
Жодного інциденту. Жодної війни. Просто тікет, виправлений до того, як це стало проблемою.

Мораль нудна і тому правдива: трендуйте правильні співвідношення, і оновлення перестануть бути сюрпризами.
Вам не потрібне пророцтво. Потрібна панель, яка ставить з’єднання в статус першокласного ресурсу.

Рішення в архітектурі, що назавжди зменшують churn

Ви можете налаштовувати sysctl поки не посинієте. Якщо платформа продовжує звичкою створювати нові з’єднання, ви й далі платитимете.
Тривкі виправлення — це архітектурні й поведінкові зміни.

1) Віддавайте перевагу мультиплексуванню на рівні протоколу, коли це підходить

HTTP/2 або HTTP/3 можуть зменшити кількість з’єднань через мультиплексування стрімів. Але не сприймайте це як універсальне ліки.
Якщо ваші проксі даунгрейдять або посередники термінують і повторно ініціюють з’єднання, вигода може зникнути не наскрізь.
Але коли ви контролюєте і клієнт, і сервер — мультиплексування одне з найчистіших способів зменшити churn.

2) Робіть пула з’єднань явними і спостережуваними

Більшість «таємного churn» походить від невидимих пулів: дефолтний розмір пулу 2 тут, пул на хост там, пул за кожну DNS-відповідь ще десь.
Зробіть розміри пулів конфігурованими. Експортуйте метрики: активні з’єднання, простаючі з’єднання, очікувані захоплення, швидкість створення з’єднань.

3) Узгоджуйте таймаути між шарами, але не синхронізуйте їх

Таймаути мають бути послідовні: client idle timeout < proxy idle timeout < server idle timeout — поширений шаблон.
Але не встановлюйте їх однаковими по всьому флоту. Додавайте jitter. Розтягніть rollout. Уникайте періодичних бур перепідключень.

4) Розглядайте retries як бюджетований ресурс

Retries — це не «безкоштовна надійність». Вони множать навантаження і створюють більше з’єднань при часткових відмовах.
Бюджетуйте retries на запит, обережно використовуйте hedging і віддавайте перевагу швидким відмовам з backoff, коли система нездорова.

5) Уникайте непотрібних NAT-хопів

Кожний NAT-рубіж — це stateful вузьке місце. Зменшуйте їх, коли можливо: прямий маршрутизація, менше еґрес-чокпоінтів, краща топологія.
Якщо NAT необхідний — відміряйте conntrack і моніторьте його, як базу даних. Бо функціонально це база даних.

6) Встановіть обмеження на проби та перевірки здоров’я

Проби повинні бути дешевими, кешованими й максимально локальними. Якщо ваша проба заходить у повний стек запиту з аутентифікацією, TLS і викликами БД,
ви побудували підсилювач відмов. Використовуйте окремі легкі ендпоінти. Уникайте інтервалів проб, що масштабуються лінійно з кількістю подів так, як ви не можете собі дозволити.

7) Моделюйте життєвий цикл з’єднання в плануванні ємності

Планування ємності зазвичай моделює QPS і розміри payload. Додайте до ваших таблиць:
нові з’єднання за секунду, середній час життя з’єднання, вплив TIME_WAIT, CPU на TLS-рукостискання, стан conntrack на потік.
Якщо ви не можете надійно виміряти це — це ознака, що ваша платформа вже пастка оновлень.

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

Цей розділ навмисно конкретний. Загальні поради дешеві; інциденти в продакшені — ні.

1) Симптом: спорадичні connect() таймаути з боку клієнтів

Первинна причина: вичерпання ефемерних портів на клієнтських вузлах або SNAT; надто багато сокетів у TIME_WAIT.

Виправлення: зменшити створення з’єднань (keepalive, пули), розширити діапазон ефемерних портів, розподілити еґрес між більшою кількістю source IP, і зменшити шторм повторів.

2) Симптом: випадкові обриви, «connection reset by peer», 5xx на краю

Первинна причина: таблиця conntrack повна на вузлах або firewall/NAT-шлюзах; збільшення insert_failed/drop.

Виправлення: підвищити nf_conntrack_max як тимчасовий захід, налаштувати таймаути conntrack для вашого трафіку, зменшити створення потоків і прибрати непотрібні NAT-рубежі.

3) Симптом: повтори SYN, сплески в SYN-RECV, клієнти бачать затримку рукостискань

Первинна причина: переповнення backlog прослуховування або черга accept не очищається (недостатньо потоків/воркерів); somaxconn занизький.

Виправлення: збільште accept-ємність сервера, налаштуйте backlog sysctl і зменшіть швидкість створення з’єднань через keepalive і повторне використання.

4) Симптом: спайки CPU під час сплесків трафіку; проксі вузли автошкаляться

Первинна причина: підвищений рівень TLS-рукостискань від churn; сесійне resumption неефективне; агресивні idle таймаути.

Виправлення: відновіть keepalive, забезпечте resumption, уникайте надто коротких idle timeout і перевірте, що проміжні проксі не змушують перепідключення.

5) Симптом: лише деякі вузли падають; «працює на вузлі A, падає на вузлі B»

Первинна причина: локальне вичерпання ресурсів (порти, conntrack, ліміти fd) варіюється по вузлах через нерівномірний трафік або розміщення подів.

Виправлення: підтвердіть перекіс, перерозподіліть навантаження, виправте ліміти на вузлах і усуньте генератор churn замість полювання на невдачливі вузли.

6) Симптом: оновлення успішні в staging, але падають у production

Первинна причина: staging не має реальної кардинальності з’єднань: менше клієнтів, менше NAT-шарів, менше проб, інший балансувальник налаштувань.

Виправлення: проводьте staging з реалістичними шаблонами з’єднань; реплеюйте швидкість створення з’єднань; відстежуйте співвідношення з’єднань на запит; канарте з сигналами бюджету з’єднань.

7) Симптом: диски заповнюються під час мережевого інциденту

Первинна причина: підсилення логів через помилки з’єднань/повтори; увімкнене детальне логування під час інциденту; сайдкари записують багато.

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

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

Чек-лист: перед оновленням (уникнути пастки)

  1. Визначте бюджет з’єднань: припустимі нові з’єднання/сек на рівень, макс TIME_WAIT на вузол, максимальна зайнятість conntrack.
  2. Зробіть знімок поточних налаштувань: keepalive, таймаути проксі, політики повторів, інтервали проб, розміри conntrack.
  3. Зробіть diff ефективної конфігурації: зрендерені файли конфіга і аргументи запущених процесів, а не лише Helm values чи IaC-шаблони.
  4. Додайте canary-алерти на churn: нові з’єднання/сек, повтори SYN, зайнятість conntrack, лічильники TIME_WAIT.
  5. Запустіть навантажувальний тест, орієнтований на з’єднання: не лише QPS; включіть реалістичну паралельність клієнтів і повтори.

Чек-лист: під час rollout (виявити рано)

  1. Канарьте один зріз: одна AZ, один node pool або один namespace; тримайте форму трафіку порівнянною.
  2. Стежте співвідношення: з’єднань на запит на кожному хопі; якщо воно росте — зупиняйтеся.
  3. Стежте за таблицями стану: conntrack_count/max, TIME_WAIT, використання fd на проксі.
  4. Корелюйте з політиками: retries, таймаути, частота проб, налаштування TLS.

Чек-лист: якщо вже вогонь (спочатку стабілізуйте)

  1. Зупиніть кровотечу: вимкніть або зменшіть retries, що створюють шторм; збільшіть backoff.
  2. Зменшіть джерела churn: знизьте частоту проб; тимчасово вимкніть несуттєві скрепи/телеметрію, що відкриває нові з’єднання.
  3. Збільшіть запас: підвищте max conntrack і ліміти fd там, де безпечно; розширте діапазон ефемерних портів при потребі.
  4. Відкатуйте селективно: поверніть компонент, що змінив поведінку з’єднань (проксі/mesh/клієнтська бібліотека), а не обов’язково всю платформу.
  5. Після інциденту: напишіть «тест регресії з’єднань» і додайте його до критеріїв релізу.

Питання й відповіді (FAQ)

1) Чи завжди часті зміни сокетів — погано?

Ні. Деякі навантаження природно короткоживучі (серверлес-подібні виклики, карманні воркери). Churn стає проблемою, коли він перевищує ємність
stateful-компонентів: conntrack, ефемерні порти, черги backlog, CPU на TLS або дескриптори файлів. Мета — контрольований churn, а не нульовий churn.

2) Чому оновлення викликають churn, якщо ми не міняли код додатку?

Бо ваш код — не єдина річ, що створює сокети. Проксі, сайдкари, балансувальники навантаження, перевірки здоров’я і клієнтські бібліотеки
можуть змінювати налаштування під час оновлень. «Оновлення платформи» часто є прихованим оновленням управління з’єднаннями.

3) Чи можна просто підвищити nf_conntrack_max і забути?

Підвищуйте його, коли треба зупинити інцидент. Але вважайте це турнікетом. Якщо ви не зменшите створення потоків, ви заповните більшу таблицю теж,
і можете обміняти дропи з’єднань на тиск пам’яті і CPU. Виправляйте джерело churn.

4) Чи TIME_WAIT — це баг, який треба відключити?

Ні. TIME_WAIT захищає від затриманих пакетів, що можуть зіпсувати нові з’єднання. Можна настроїти його вплив, але «відключення TIME_WAIT»
зазвичай або неможливе, або погана ідея. Замість цього зменшіть короткоживучі з’єднання.

5) Чи keepalive завжди допомагає?

Keepalive допомагає, коли воно сприяє повторному використанню і зменшенню рукостискань. Воно може нашкодити, якщо ви тримаєте забагато простаючих з’єднань і вичерпуєте ліміти fd
або пам’ять сервера. Правильна практика — пулі правильного розміру, розумні idle timeout і спостережуваність поведінки пулу.

6) Чому це частіше помітно в Kubernetes?

Kubernetes заохочує патерни, що підвищують кардинальність з’єднань: багато малих подів, часті проби, service NAT і багато шарів проксі.
Жоден з цих елементів сам по собі не є неправильним. Разом вони роблять стан з’єднань ключовим виміром масштабування.

7) Як зрозуміти, чи mesh винен?

Виміряйте швидкість створення з’єднань на сайдкарах і порівняйте з швидкістю запитів. Якщо mesh додає нові upstream-з’єднання на запит,
або змінює idle timeout, ви побачите TIME_WAIT і зростання CPU рукостискань спочатку на шарі проксі.

8) Яка найкраща метрика для алерта?

Якщо можна вибрати лише одну: нові з’єднання в секунду на вузол (або на екземпляр проксі) у парі зі швидкістю запитів.
Алератити на зміну співвідношення. Абсолютні лічильники змінюються; співвідношення виявляє регресії.

9) Чи може зберігання справді мати значення в інциденті socket churn?

Так. Інциденти churn часто збільшують обсяг логів і помилкової телеметрії. Якщо логи лежать на обмежених томах, ви можете отримати каскад: диск заповнений,
повільні записі і завислі процеси. Ваш мережевий інцидент перетворюється на інцидент зберігання, бо система намагається сказати, що щось не в порядку.

10) Як організаційно уникнути пасток оновлень?

Зробіть «поведінку з’єднань» критерієм релізу: бюджет, дашборди і канарні ворота. Вимагайте від команд документувати очікувані зміни в
keepalive, таймаутах, retries, пробах і політиках TLS. Якщо це не задокументовано — ви знайдете це о 2:00 ранку.

Висновок: практичні наступні кроки

Часті зміни сокетів — це не екзотична помилка. Це те, що відбувається, коли ви вважаєте з’єднання безкоштовними, а оновлення — ізольованими подіями.
Платформи стають пастками оновлень, коли малі зміни за замовчуванням множаться й ведуть до вичерпання стану через NAT, conntrack, проксі і ядра.

Наступні кроки, які ви можете зробити цього тижня:

  1. Додайте дашборд по з’єднаннях: TIME_WAIT, ESTABLISHED, нові з’єднання/сек, повтори SYN, зайнятість conntrack.
  2. Виберіть один сервіс і виміряйте з’єднань на запит між рівнями. Відстежуйте під час деплоїв.
  3. Аудитуйте keepalive і idle таймаути у клієнтських бібліотеках, проксі і балансувальниках. Робіть їх явними налаштуваннями, а не фольклором.
  4. Заблокуйте оновлення канарним бюджетом: якщо співвідношення змінюється — призупиняйте rollout.
  5. Виправте найгіршого генератора churn: зазвичай це дефолт проксі, проба або політика retry — не ядро.

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

← Попередня
Ubuntu 24.04: UFW + Docker — ізолюйте контейнери без пошкодження Compose (випадок №40)
Наступна →
Злам через тестовий сервер: класичне корпоративне фіаско

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