Vyčerpanie Ephemeral Portov: Node Ktorý 'Pokazí'
Ked som prvykrat videl “cannot assign requested address” v K8s, tipoval som DNS. “Jeden node náhodne ‘pokazí’—pripojenia k externým API zlyhávajú, ale len z toho nodu.” Pody vyzerali zdravé, CPU bolo v poriadku, pamäť tiež. Vinník: service mesh sidecary vytvárajúce tisíce krátkodobých spojení, vyčerpávajúce ephemeral port range.
Prostredie: Kubernetes 1.27, Istio service mesh, vysoko zaťažené API gateway pody
Problém
Symptómy
Čo sme pozorovali:
Node A (zdravý):
Pod 1 → External API → 200 OK
Pod 2 → External API → 200 OK
Node B (zlý):
Pod 3 → External API → connection refused / timeout
Pod 4 → External API → connection refused / timeout
Ale:
- Všetky pody ukazovali Ready
- Žiadny OOM, žiadne CPU throttling
- Rovnaký image, rovnaká konfigurácia
- Reštart podov dočasne pomohol
- Problém sa vrátil po ~30 minútach
Prečo Štandardný Debugging Toto Nezachytí
# Pod metriky vyzerajú dobre
kubectl top pod -n api-gateway
# Všetky pody: CPU 20%, Memory 40%
# Logy ukazujú connection failures ale nie root cause
kubectl logs api-gateway-xyz
# ERROR: connection refused to external-api.com:443
# Dokonca aj node metriky vyzerajú OK
kubectl top node problem-node
# CPU: 45%, Memory: 60%
# Problém je neviditeľný kým nekontrolujete ephemeral porty
Príčina
Problém Ephemeral Portov
Ako TCP spojenia fungujú:
┌─────────────┐ ┌─────────────┐
│ Klient │ │ Server │
│ │ Source Port │ │
│ │ (ephemeral) │ │
│ :34567 ───┼──────────────────▶ ├──── :443 │
│ :34568 ───┼──────────────────▶ ├──── :443 │
│ :34569 ───┼──────────────────▶ ├──── :443 │
└─────────────┘ └─────────────┘
Default ephemeral port range: 32768-60999 = ~28,000 portov
Keď je zapojený SNAT (NodePort, externý traffic):
Všetky pody na node zdieľajú ephemeral porty nodu!
┌──────────────────────────────────────────────────┐
│ Node │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Pod 1│ │Pod 2│ │Pod 3│ Všetky zdieľajú │
│ └──┬──┘ └──┬──┘ └──┬──┘ rovnaké porty │
│ │ │ │ pre SNAT │
│ └───────┴───────┴────▶ iptables MASQUERADE │
│ 32768-60999 │
└──────────────────────────────────────────────────┘
Service Mesh Amplifikácia
Bez meshu:
Pod → External API
= 1 spojenie per request
So sidecar proxy:
Pod → Envoy sidecar → External API
= 2 spojenia per request (interné + externé)
S agresívnymi retries:
Pod → Envoy (retry 3x) → External API
= Až 6 spojení per request
S vypnutým/pokazeným connection poolingom:
Každý request = nové TCP spojenie
Vysoké RPS = vyčerpanie portov za minúty
TIME_WAIT To Zhoršuje
# Skontroluj TIME_WAIT spojenia na node
ss -tan state time-wait | wc -l
# 25000 <-- Takmer na limite!
# TIME_WAIT trvá 60 sekúnd defaultne
# Ak vytváraš spojenia rýchlejšie než expirujú:
Rate: 500 nových spojení/sekundu
TIME_WAIT trvanie: 60 sekúnd
Steady state: 500 * 60 = 30,000 portov v TIME_WAIT
Dostupné porty: ~28,000
Výsledok: Vyčerpanie portov!
Diagnostika
Krok 1: Skontroluj Využitie Portov
# Na postihnutom node
# Spočítaj spojenia podľa stavu
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# Vzorový výstup z vyčerpaného nodu:
# 25432 TIME-WAIT
# 523 ESTABLISHED
# 156 FIN-WAIT-2
# 42 SYN-SENT
# Skontroluj spojenia na špecifickú destináciu
ss -tan state time-wait dst :443 | wc -l
Krok 2: Identifikuj Vinný Pod
# Skontroluj ktorý proces vlastní najviac spojení
# Použi conntrack na trasovanie NAT spojení
conntrack -L -d <external-api-ip> 2>/dev/null | \
awk '{print $5}' | sort | uniq -c | sort -rn | head -10
# Alebo skontroluj využitie source port range
ss -tan | awk -F: '/TIME-WAIT.*:443/ {print $2}' | \
cut -d' ' -f1 | sort -n | uniq -c | sort -rn
Krok 3: Over Port Range
# Skontroluj aktuálny ephemeral port range
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999 (default, cca 28,000 portov)
# Skontroluj net.ipv4.tcp_tw_reuse nastavenie
cat /proc/sys/net/ipv4/tcp_tw_reuse
# 0 (defaultne vypnuté)
Riešenie
Možnosť 1: Rozšír Port Range
# Na všetkých nodoch (cez DaemonSet alebo node config)
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# Perzistentne cez /etc/sysctl.d/
echo "net.ipv4.ip_local_port_range = 1024 65535" > /etc/sysctl.d/99-ephemeral-ports.conf
# DaemonSet na aplikovanie sysctl
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: sysctl-tuning
namespace: kube-system
spec:
selector:
matchLabels:
app: sysctl-tuning
template:
metadata:
labels:
app: sysctl-tuning
spec:
hostNetwork: true
hostPID: true
initContainers:
- name: sysctl
image: busybox
securityContext:
privileged: true
command:
- /bin/sh
- -c
- |
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fin_timeout=30
containers:
- name: pause
image: gcr.io/google_containers/pause:3.2
Možnosť 2: Zapni TCP Connection Reuse
# Povol znovupoužitie TIME_WAIT spojení pre nové odchádzajúce
sysctl -w net.ipv4.tcp_tw_reuse=1
# Zníž TIME_WAIT timeout (opatrne - môže rozbiť niektoré scenáre)
sysctl -w net.ipv4.tcp_fin_timeout=30
Možnosť 3: Oprav Aplikáciu
# Istio: Zapni connection pooling
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: external-api
spec:
host: external-api.com
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
connectTimeout: 10s
http:
h2UpgradePolicy: UPGRADE # Použi HTTP/2 multiplexing
maxRequestsPerConnection: 1000
// Go: Znovupoužívaj HTTP klienta s connection poolingom
var httpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
// Kritické: drž spojenia nažive
DisableKeepAlives: false,
},
Timeout: 30 * time.Second,
}
// NEROB toto:
// resp, err := http.Get(url) // Vytvára nového klienta zakaždým!
Možnosť 4: Použi HTTP/2 alebo gRPC
HTTP/1.1: 1 request per connection
HTTP/2: Viacero requestov per connection (multiplexing)
S HTTP/2:
1000 RPS = môže použiť len 10-50 spojení
vs HTTP/1.1 = 1000 spojení
Prepni na HTTP/2 pre externé API kde je to možné
Monitoring
Prometheus Metriky
groups:
- name: ephemeral-ports
rules:
- alert: EphemeralPortExhaustion
expr: |
node_sockstat_TCP_tw > 20000
for: 5m
labels:
severity: warning
annotations:
summary: "Vysoké TIME_WAIT spojenia na {{ $labels.instance }}"
description: "{{ $value }} spojení v TIME_WAIT, riziko vyčerpania portov"
- alert: HighConnectionRate
expr: |
rate(node_netstat_Tcp_PassiveOpens[5m]) +
rate(node_netstat_Tcp_ActiveOpens[5m]) > 1000
for: 5m
labels:
severity: info
annotations:
summary: "Vysoká TCP connection rate na {{ $labels.instance }}"
Rýchly Health Check Skript
#!/bin/bash
# port-exhaustion-check.sh
echo "=== Ephemeral Port Status ==="
# Aktuálny port range
echo "Port range: $(cat /proc/sys/net/ipv4/ip_local_port_range)"
RANGE=$(cat /proc/sys/net/ipv4/ip_local_port_range)
LOW=$(echo $RANGE | awk '{print $1}')
HIGH=$(echo $RANGE | awk '{print $2}')
TOTAL=$((HIGH - LOW))
echo "Celkom dostupných: $TOTAL"
# Porty v použití
TIME_WAIT=$(ss -tan state time-wait | wc -l)
ESTABLISHED=$(ss -tan state established | wc -l)
echo "TIME_WAIT: $TIME_WAIT"
echo "ESTABLISHED: $ESTABLISHED"
# Využitie
USED=$((TIME_WAIT + ESTABLISHED))
PERCENT=$((USED * 100 / TOTAL))
echo "Využitie: $PERCENT%"
if [ $PERCENT -gt 80 ]; then
echo "VAROVANIE: Riziko vyčerpania portov!"
fi
# Top destinácie v TIME_WAIT
echo -e "\n=== Top TIME_WAIT destinácie ==="
ss -tan state time-wait | awk '{print $4}' | sort | uniq -c | sort -rn | head -5
Checklist
## Vyčerpanie Ephemeral Portov
### Symptómy
- [ ] Jeden node zlyháva zatiaľ čo ostatné fungujú
- [ ] Connection refused na externé služby
- [ ] Pody vyzerajú zdravé (CPU/memory OK)
- [ ] Problém sa vracia po reštarte podov
### Diagnostika
- [ ] Skontroluj TIME_WAIT count: ss -tan state time-wait | wc -l
- [ ] Skontroluj port range: cat /proc/sys/net/ipv4/ip_local_port_range
- [ ] Identifikuj top connection destinations
- [ ] Hľadaj chýbajúci connection pooling
### Riešenia
- [ ] Rozšír port range (1024-65535)
- [ ] Zapni tcp_tw_reuse
- [ ] Zapni connection pooling v app/mesh
- [ ] Prepni na HTTP/2 kde je to možné
- [ ] Zníž connection-per-request vzory
Záver
Tento failure mode je obzvlášť záludný pretože:
- Pod metriky vyzerajú dobre - je to node-level zdroj
- Postihuje len SNAT traffic - interný traffic funguje
- Dočasné fixe (reštart) fungujú - skrývajú vzor
- Service mesh amplifikuje - viac spojení per request
Fix je zvyčajne kombinácia:
- System tuning (port range, tcp_tw_reuse)
- Aplikačné fixe (connection pooling, HTTP/2)
- Architektonické zmeny (zníž krátkodobé spojenia)
Súvisiace články
- Kubernetes Conntrack Vyčerpanie - Ďalší node-level resource limit
- gRPC Load Balancing v Kubernetes - Connection management patterns
Súvisiace články
Traffic Ide na Mŕtve Pody: Conntrack Zastaralé NAT Mapovanie
Deploy spôsobuje 503 presne 2 minúty. Problém: conntrack drží NAT mapovanie na staré pod IP aj po tom čo Kubernetes odstráni endpointy.
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.
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.