Traffic Ide na Mŕtve Pody: Conntrack Zastaralé NAT Mapovanie
Tento bug bol ako duch: nove pody sa nepripojili, stare ano. “Každý deploy spôsobuje presne 2 minúty 503 chýb.” Pridali sme preStop hooks, zvýšili terminationGracePeriod, vyladili readiness probes—nič nepomohlo. Vinník bol conntrack držiaci NAT mapovanie na mŕtve pod IP.
Prostredie: Kubernetes 1.27, NodePort služby, vysoko zaťažené stateless API
Problém
Strašidelný Vzor
Každý jednotlivý deployment:
T+0:00 Nové pody ready, staré pody terminujú
T+0:00 Endpointy aktualizované (kube-proxy syncuje)
T+0:00 503 chyby začínajú
T+0:05 503 rate: 5%
T+0:30 503 rate: 3%
T+1:00 503 rate: 2%
T+2:00 503 rate: 0% (konečne!)
Vždy presne 2 minúty.
Rovnaký vzor zakaždým.
Žiadne výnimky.
Prečo Štandardné Fixe Nefungujú
# Skúšali sme všetko:
# Dlhší termination grace period - nepomohlo
terminationGracePeriodSeconds: 120
# preStop hook delay - nepomohlo
lifecycle:
preStop:
exec:
command: ["sleep", "30"]
# Agresívna readiness probe - nepomohla
readinessProbe:
periodSeconds: 1
failureThreshold: 1
# Problém nie je pod lifecycle
# Je to conntrack tabuľka nodu!
Príčina
Ako Conntrack Funguje
Normálny request flow s NodePort:
Klient → Node:30080 → iptables DNAT → Pod:8080
conntrack záznam vytvorený:
┌─────────────────────────────────────────────────────┐
│ tcp src=client:54321 dst=node:30080 │
│ src=pod:8080 dst=client:54321 [ASSURED] │
│ timeout=432000 (5 dní!) │
└─────────────────────────────────────────────────────┘
Tento záznam pamätá: "traffic z client:54321 ide na pod:8080"
Problém Počas Deploymentu
Časová os:
T+0:00 Staré pod IP: 10.1.1.100
Nové pod IP: 10.1.1.200
Kubernetes: "Endpoint 10.1.1.100 odstránený!"
kube-proxy: "iptables pravidlá aktualizované!"
Ale conntrack tabuľka stále má:
┌─────────────────────────────────────────┐
│ tcp src=client:54321 dst=node:30080 │
│ src=10.1.1.100:8080 dst=client │ ← Ukazuje na MŔTVY pod!
│ timeout=stale_zostava_cas │
└─────────────────────────────────────────┘
T+0:01 Klient pošle paket na rovnakom spojení
→ Conntrack: "Toto poznám! Pošli na 10.1.1.100"
→ Paket ide na mŕtvy pod
→ Connection reset / timeout
→ 503 chyba!
T+2:00 Conntrack záznamy konečne expirujú
Nové spojenia dostanú nový NAT na 10.1.1.200
Chyby prestanú
Prečo Je To Presne 2 Minúty
# Skontroluj conntrack timeout pre established TCP
cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
# 432000 (5 dní - nie je relevantné)
# 2-minútový vzor pochádza z:
# 1. Klient-side keepalive/retry nastavení
# 2. HTTP klient connection pool timeout
# 3. Load balancer health check intervalov
# Conntrack záznam sám osebe by mohol žiť dni
# Ale klient nakoniec vzdá a vytvorí nové spojenie
Diagnostika
Krok 1: Sleduj Conntrack Počas Deploy
# Pred deploy, poznač si client IP
CLIENT_IP="203.0.113.10"
# Sleduj conntrack záznamy pre toho klienta počas deploy
watch -n 0.5 "conntrack -L -s $CLIENT_IP 2>/dev/null | grep -E '(ESTABLISHED|TIME_WAIT)'"
# Uvidíš záznamy ukazujúce na staré pod IP
# aj po tom čo pod zmizol
Krok 2: Over Traffic na Mŕtvy Pod
# tcpdump na node počas deploy
tcpdump -i any host 10.1.1.100 # staré pod IP
# Uvidíš pakety posielané na staré IP
# po tom čo pod zmizol
Krok 3: Spočítaj Zastaralé Záznamy
#!/bin/bash
# count-stale-conntrack.sh
# Ziskaj aktuálne endpoint IP
VALID_IPS=$(kubectl get endpoints my-service -o jsonpath='{.subsets[*].addresses[*].ip}')
# Spočítaj conntrack záznamy ukazujúce na neplatné IP
conntrack -L 2>/dev/null | while read line; do
DST_IP=$(echo "$line" | grep -oP 'dst=\K[0-9.]+' | head -1)
if [[ ! " $VALID_IPS " =~ " $DST_IP " ]]; then
echo "STALE: $line"
fi
done | wc -l
Riešenie
Možnosť 1: Flush Conntrack Počas Deploy
# Pridaj do deploymentu ako preStop na STARÉ pody
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# Počkaj na propagáciu endpoint removal
sleep 5
# Ziskaj IP tohto podu
POD_IP=$(hostname -i)
# Flush conntrack záznamy ukazujúce na tento pod
# Vyžaduje NET_ADMIN capability
conntrack -D -d $POD_IP || true
# Udeľ NET_ADMIN capability
securityContext:
capabilities:
add: ["NET_ADMIN"]
Možnosť 2: Node-Level Conntrack Flush
# DaemonSet ktorý sleduje endpoint zmeny
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: conntrack-flusher
namespace: kube-system
spec:
selector:
matchLabels:
app: conntrack-flusher
template:
metadata:
labels:
app: conntrack-flusher
spec:
hostNetwork: true
serviceAccountName: conntrack-flusher
containers:
- name: flusher
image: alpine
securityContext:
privileged: true
command:
- /bin/sh
- -c
- |
apk add --no-cache conntrack-tools curl
while true; do
# Sleduj endpoint deletions cez API
# Flush conntrack keď sú pody odstránené
sleep 10
done
Možnosť 3: Použi Headless Service (Vyhni Sa NAT)
# Headless service = priame pod IP, žiadny NAT, žiadny conntrack problém
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None # Headless!
selector:
app: my-app
ports:
- port: 8080
# Klienti sa pripájajú priamo na pod IP
# Žiadny DNAT = žiadne zastaralé conntrack záznamy
# Ale vyžaduje client-side load balancing
Možnosť 4: Zníž Conntrack Timeouty
# Zníž established connection timeout (opatrne!)
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=120
# Zníž FIN_WAIT timeout
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
# Zníž TIME_WAIT timeout
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
# Toto ovplyvňuje VŠETKY spojenia, nie len zastaralé
# Môže rozbiť long-lived spojenia
Možnosť 5: Graceful Connection Draining
// V aplikácii: drain spojenia pred shutdown
func gracefulShutdown(srv *http.Server) {
// Signalizuj že sa vypíname
// Prestaň akceptovať nové spojenia na health endpointe
healthStatus.Store(false)
// Počkaj kým load balancer prestane posielať traffic
time.Sleep(10 * time.Second)
// Teraz gracefully shutdown existujúce spojenia
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx)
}
Monitoring
Prometheus Metriky
groups:
- name: conntrack
rules:
- alert: StaleConntrackEntries
expr: |
node_nf_conntrack_entries > 50000 AND
rate(node_nf_conntrack_entries[5m]) < 0
for: 5m
labels:
severity: warning
annotations:
summary: "Možné zastaralé conntrack záznamy na {{ $labels.instance }}"
- alert: DeploymentErrors
expr: |
sum(rate(http_requests_total{status=~"5.."}[1m])) BY (deployment) /
sum(rate(http_requests_total[1m])) BY (deployment) > 0.01
for: 2m
labels:
severity: warning
annotations:
summary: "Zvýšená 5xx miera počas deploymentu"
Checklist
## Conntrack Zastaralé NAT Mapovanie
### Symptómy
- [ ] Chyby trvajú presne 2+ minúty po deploy
- [ ] Rovnaký vzor každý deployment
- [ ] Dlhší preStop/terminationGrace nepomáha
- [ ] Používa NodePort alebo LoadBalancer service
### Diagnostika
- [ ] Sleduj conntrack počas deploy
- [ ] Porovnaj conntrack dst IP vs aktuálne endpointy
- [ ] tcpdump traffic na staré pod IP
### Riešenia
- [ ] Flush conntrack záznamy pre umierajúce pody
- [ ] Použi headless service (ak možné)
- [ ] Implementuj proper connection draining
- [ ] Zníž conntrack timeouty (opatrne)
### Prevencia
- [ ] Pridaj NET_ADMIN capability pre conntrack flush
- [ ] Implementuj graceful shutdown v app
- [ ] Zváž service mesh (rieši toto automaticky)
Záver
Tento problém je frustrujúci pretože:
- Štandardné pod lifecycle fixe nepomáhajú - problém je na node úrovni
- Vzor je strašidelne konzistentný - presne 2 minúty zakaždým
- Vyžaduje kernel-level pochopenie - conntrack nie je viditeľný pre K8s
- Fix vyžaduje elevated privileges - NET_ADMIN alebo privileged
Fundamentálny problém: Kubernetes endpoint updaty sa dejú na inej vrstve než Linux conntrack. Nekomunikujú spolu, takže zastaralé NAT mapovanie pretrvávajú.
Súvisiace články
- kube-proxy xtables Lock Contention - Ďalší kube-proxy problém
- Vyčerpanie Ephemeral Portov - Súvisiaci NAT problém
Súvisiace články
Vyčerpanie Ephemeral Portov: Node Ktorý 'Pokazí'
Jeden Kubernetes node začne zlyhávať pripojenia k externým službám zatiaľ čo pody vyzerajú zdravé. Skrytá príčina: sidecar proxy vyčerpávajú ephemeral porty krátkodobými spojeniami.
Ghost Pod: Prečo váš Service stále posiela traffic na mŕtve endpointy
Náhodné ECONNRESET na niektorých nodoch. Endpointy vyzerajú správne. Vinník: conntrack NAT záznamy držia dlhodobé spojenia pripnuté k podom, ktoré už neexistujú.
Kubernetes Ghost Connections: Zastarané Conntrack DNAT Záznamy
Service vracia zlé pod IP po škálovaní. Príčina: Linux conntrack drží DNAT záznamy dlhšie ako existujú pody, smeruje traffic na zmazané endpointy.
Kubernetes conntrack Vyčerpanie: Tichý Zabijak Paketov
Náhodné DNS timeouty, dropped spojenia, služby timeout-ujú. Vaša nf_conntrack tabuľka je plná. Ukážem ako diagnostikovať, monitorovať a opraviť tento K8s networking problém.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.