tcpdump vidí SYN, ale služba timeoutuje: pasca listen backlogu
Toto je typ incidentu, ktorý ťa prinúti pochybovať o nástrojoch. Klienti timeoutovali. tcpdump na node ukazoval SYN pakety prichádzať na správny interface aj port. Niekedy sme videli aj odchádzajúci SYN-ACK. Ale service logy boli ticho a aplikácia sa správala, akoby sa nikto ani nepokúšal pripojiť.
Príčina nebola firewall, DNS ani “náhodná sieť”. Kernel robil presne to, čo má robiť, keď proces nestíha accept()ovať nové spojenia: prestane ich prijímať. A podľa jedného sysctl-u to z pohľadu klienta vyzerá buď ako nevysvetliteľný timeout (tichý drop), alebo ako rýchly RST.
Prostredie: Linux nody, Kubernetes, vysoký connection churn, CPU throttling / vyťažený event loop / pomalý accept loop
Vzor symptómov
Na tomto probléme je zradné, že každý signál vyzerá sám o sebe “OK”:
- Klienti: občasné timeouty pri connecte (alebo hneď na prvom requeste).
- tcpdump: SYN pakety prichádzajú na node/pod IP na správny port.
- Aplikácia: žiadne access logy, žiadne request logy, niekedy ani žiadna aktivita
accept().
Veľmi ľahko potom začneš riešiť nesprávne veci (conntrack, CNI, MTU, health-checky), lebo “pakety sem predsa chodia”.
Skrytý mechanizmus: nie jedna fronta, ale dve
Keď zavoláš listen(fd, backlog), Linux efektívne spravuje dve súvisiace fronty:
- SYN fronta (half-open spojenia): prišiel SYN, odišiel SYN-ACK, handshake ešte nie je komplet.
- Accept fronta (spojenia pripravené na
accept()): handshake je hotový, kernel čaká, kým si proces zavoláaccept()a začne čítať.
Ak sa accept fronta zaplní (CPU starvation, málo workerov, blokovanie v accept loope, náročné TLS handshaky v single-thread runtime), kernel musí rozhodnúť, čo spraví s ďalším pokusom o spojenie.
V praxi to znamená:
tcpdumpvidí SYN, lebo capture je skôr, než aplikácia uvidí socket.tcpdumpmôže vidieť aj SYN-ACK, lebo kernel dokáže odpovedať aj keď proces “nestíha”.- Aplikácia však nič nevidí, lebo nové spojenia sa už nedostanú do accept fronty.
Ako to dokázať (produkčne)
1) Skontroluj frontu na listen socket-e
Na node (alebo v pod netns) si pozri listener:
ss -ltnp 'sport = :443'
Pre listen socket je Recv-Q dobrá praktická aproximácia “koľko hotových spojení čaká na accept()”. Ak počas incidentu naráža na strop, máš tlak na accept frontu.
2) Pozri kernel countery, ktoré neklamú
Linux na to má explicitné štatistiky:
# TcpExt sú dva riadky: najprv názvy, potom hodnoty
grep -A1 '^TcpExt:' /proc/net/netstat | tail -n2
Hľadaj:
ListenOverflows— accept fronta overflowla (kernel už nevedel enqueue-núť ďalšie spojenie).ListenDrops— SYN-y na listen sockety boli dropnuté.SyncookiesSent/SyncookiesRecv— SYN cookies aktivita (signál tlaku na SYN frontu).
Ak ti počas incidentu rastie ListenOverflows, je to “smoking gun”.
3) Sysctly, ktoré rozhodujú “timeout vs reset”
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_syncookies
sysctl net.ipv4.tcp_abort_on_overflow
Kľúčový detail:
net.ipv4.tcp_abort_on_overflow=0(častý default): overflow sa často prejaví ako timeout.net.ipv4.tcp_abort_on_overflow=1: overflow sa častejšie prejaví ako rýchly RST (ľahšie sa to debugguje).
Repro lab: ako naschvál spraviť “tcpdump klame”
Tento failure mode vieš reprodukovať na hocijakom Linuxe.
Krok 1: Server, ktorý acceptuje veľmi pomaly (malý backlog)
python3 - <<'PY'
import socket, time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", 8080))
s.listen(1) # naschvál malé
print("listening on :8080")
while True:
conn, addr = s.accept()
time.sleep(5) # simulácia starvation / zaseknutého accept loopu
conn.close()
PY
Krok 2: Zaplň ho spojeniami, ktoré ostanú otvorené
# Vyžaduje bash /dev/tcp
for i in $(seq 1 2000); do (exec 3<>/dev/tcp/127.0.0.1/8080; sleep 60) & done
wait
Krok 3: Sleduj plnenie backlogu a overflows
ss -ltn 'sport = :8080'
grep -A1 '^TcpExt:' /proc/net/netstat | tail -n2
Krok 4 (voliteľné): Jeden sysctl zmení symptóm
# 0 -> viac “timeoutov”
sysctl -w net.ipv4.tcp_abort_on_overflow=0
# 1 -> viac “connection reset by peer”
sysctl -w net.ipv4.tcp_abort_on_overflow=1
Rovnaká príčina, iný “pocit” na klientskej strane.
Prečo je to v Kubernetes častejšie
V Kubernetes je jednoduchšie neúmyselne vytvoriť “accept starvation”:
- CPU limity/throttling: proces je runnable, ale nedostane CPU včas.
- Single-thread accept loop: jedna pomalá cesta blokuje prijímanie nových spojení.
- TLS handshake na hot path: veľa práce ešte pred tým, než sa niečo zaloguje.
- Burst traffic po rolloute: reconnect stormy zaplnia accept frontu.
Preto je “v dev-e to išlo” typická súčasť príbehu.
Fixy, ktoré reálne pomáhajú
Rieš to v tomto poradí (tuning sysctl ti nezachráni server, ktorý je jednoducho preťažený):
- Urob accept rýchly a nudný: accept loop musí vždy bežať; ťažkú prácu odovzdaj workerom.
- Nastav správnu concurrency: dosť workerov/threads aby si odčerpával accept frontu aj pri špičke.
- Bezpečne zväčši backlog: nastav rozumné
listen(backlog)a zvýšnet.core.somaxconnna nodoch, aby to nebolo capnuté. - Nadimenzuj SYN frontu: ak vidíš SYN cookies, pozri
net.ipv4.tcp_max_syn_backlog. - Preferuj rýchly fail: zváž
tcp_abort_on_overflow=1, aby klienti dostali okamžitý signál.
Monitoring checklist
- Alert na rast
ListenOverflows(trvalý rate je red flag). - Sleduj
ListenDropsaSyncookiesSentpočas incident okien. - Koreluj overflows s CPU throttlingom a reconnect špičkami.
- Pridaj canary, ktorý meria connect latenciu, nie len HTTP latenciu.
- Over backlog nastavenia po upgrade node image (defaulty sa menia).
Ak ti tento incident niečo pripomína, je to rovnaká lekcia ako pri iných “tcpdump vidí, ale aplikácia nie”: kernel vie zahodiť paket/spojenie skôr, než sa to dostane na miesto, ktoré bežne debuggujeme.
Súvisiace články
ingress-nginx reload búrky: prečo 502 špičky sedia s Ingress churnom
Reloady NGINX Ingressu vedia dropovať keep-alive a robiť 502 špičky pri častých zmenách. Runbook na dôkaz reloadu, zníženie churnu a hardening.
Redis AOF fsync latency špičky: keď sa durabilita stane tvojím p99
Redis AOF vie spraviť z durability p99 špičky: fsync tlak a BGREWRITEAOF fork CoW. Runbook na dôkaz, bezpečné mitigácie a guardrails.
TCP TIME_WAIT Vyčerpanie Portov: Keď Connection Pooling Nestačí
Služba sa nemôže pripojiť k databáze - 'cannot assign requested address'. Príčina: ephemeral porty vyčerpané tisíckami socketov v TIME_WAIT stave.
Pakety prichadzaju ale aplikacia timeoutuje: rp_filter pasca v Kubernetes
tcpdump ukazuje pakety ktore prichadzaju, ale aplikacia nic nevidi. Vinik: Linux reverse path filtering ticho zahadzuje pakety predtym nez dosiahnu iptables, sposobene asymetrickym routovanim.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.