Späť na blog

Gossip Protocol Ghost Nodes: IP Reuse Strašiaci Váš Cluster

Ghost nody su zvlastny druh strasidelnosti, hlavne ked sa recykluju IP. “Nový pod naplánovaný, ale existujúce pody odmietajú s ním komunikovať.” Príčina: nový pod dostal IP ktorú používal predchádzajúci pod, a gossip protokol si stále pamätá túto IP ako “failed” alebo patriacu inému nodu.

Prostredie: Consul/Serf/Memberlist gossip, Kubernetes s agresívnym pod recyclingom, NAT alebo IP address pool reuse

Problém

Incident Odmietnutého Nodu

Časová os:

T+0s    Pod A (10.0.1.50) sa pripája ku clusteru, gossip zdravý
T+60s   Pod A padne natvrdo (bez graceful leave)
T+61s   Gossip protokol označí 10.0.1.50 ako "failed"
T+62s   Ostatné nody pridajú 10.0.1.50 do suspicion listu
T+65s   IP Pod A sa vracia do Kubernetes IP poolu

T+120s  Pod B štartuje, dostáva IP 10.0.1.50 (rovnaká IP!)
T+121s  Pod B sa pokúša pripojiť ku gossip clusteru
T+122s  Existujúce nody: "10.0.1.50? To je ten mŕtvy node!"
        Odmietajú join pokusy Pod B alebo smerujú traffic
        na cached stav pre "starý" 10.0.1.50

Výsledok: Pod B je izolovaný napriek tomu že je zdravý

Symptómy

# Logy nového podu ukazujú:
# "Failed to join cluster: membership rejected"
# "No response from seed nodes"
# "Connection refused by peer"

# Logy existujúcich podov ukazujú:
# "Received message from failed node 10.0.1.50"
# "Ignoring join from node with conflicting incarnation"
# "Suspect node attempting to rejoin"

# Consul/Serf špecifické:
consul members
# Ukazuje OBA starý (failed) aj nový node s rovnakou IP!
# node-abc-old    10.0.1.50:8301    failed
# node-xyz-new    10.0.1.50:8301    alive   (ale nedostáva traffic)

Príčina

Gossip Protocol State Machine

Gossip node lifecycle:
┌─────────────────────────────────────────────────────────────┐
│ ALIVE → SUSPECT → DEAD → (odstránený po timeoutu)          │
│                                                             │
│ Problém: IP reuse nastáva PREDTÝM než je dead node         │
│          odstránený                                         │
│                                                             │
│ T+0:    Node A (10.0.1.50) = ALIVE, incarnation=1          │
│ T+60:   Node A = SUSPECT                                    │
│ T+90:   Node A = DEAD (stále trackovaný 5 minút!)          │
│ T+120:  Node B (10.0.1.50) = ??? konflikt!                 │
│                                                             │
│ Gossip vidí: Rovnaká IP, iné meno nodu, nižšie             │
│              incarnation číslo → musí byť staré/stale      │
└─────────────────────────────────────────────────────────────┘

Konflikty Incarnation Numbers

// Gossip protokoly používajú incarnation numbers na detekciu
// ktorá informácia o node je novšia

type Node struct {
    Name        string
    Addr        net.IP
    Incarnation uint32  // Monotónne rastúce
}

// Keď nový node B sa pripojí s rovnakou IP ako mŕtvy node A:
// Node A mal incarnation=5 keď zomrel
// Node B štartuje čerstvý s incarnation=1

// Ostatné nody myslia: "incarnation 1 < incarnation 5"
// "Toto musí byť stale/stará informácia, ignoruj"

// Ešte horšie: ak nody cachovali A's stav s vysokým incarnation,
// budú odmietať B's legitímne správy ako "zastarané"

Kubernetes IP Pool Dynamika

# Kubernetes IP alokácia je agresívna v reuse

# Malé IP ranges = rýchlejší reuse
spec:
  podCIDR: 10.0.1.0/28  # Len 14 použiteľných IP!

# Rýchly pod churn = častý IP recycling
# Predstav si: 100 podov, 14 IP, pody reštartujú každých 10 minút
# IP kolízia je GARANTOVANÁ

# CNI pluginy sa líšia v reuse správaní:
# - Calico: Používa IPAM s dlhšími hold časmi
# - Flannel: Agresívnejší reuse
# - AWS VPC CNI: Limitovaný ENI/IP kvótami

Diagnostika

Skontroluj Duplicitné Node Záznamy

# Consul
consul members -detailed | grep -E "(failed|left)" | awk '{print $2}' | sort | uniq -d

# Serf
serf members | awk '{print $2}' | cut -d: -f1 | sort | uniq -d

# Memberlist (cez aplikáciu)
curl localhost:7946/debug/members | jq '.[] | .Addr' | sort | uniq -d

Porovnaj Node Start Časy

# Ak dva záznamy zdieľajú IP, skontroluj ich join časy
consul members -detailed | grep "10.0.1.50"
# Mal by ukazovať jeden záznam; viac = ghost node problém

# Skontroluj skutočný start čas podu
kubectl get pod -o jsonpath='{.status.startTime}' pod-name

# Porovnaj s gossip's zaznamenaným join časom
# Ak gossip si myslí že node sa pripojil PRED štartom podu = stale záznam

Riešenie

Možnosť 1: Graceful Leave pri Pod Shutdown

# Kubernetes: Pridaj preStop hook pre graceful gossip leave
spec:
  containers:
  - name: app
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sh
          - -c
          - |
            # Povedz gossipu aby graceful odišiel
            curl -X POST localhost:8500/v1/agent/leave
            # Alebo pre memberlist-based:
            kill -SIGTERM 1
            sleep 5  # Daj čas na propagáciu leave
// Aplikačný kód: Spracuj shutdown gracefully
func main() {
    // ... setup memberlist ...

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)

    <-sigCh
    log.Println("Vypínam, opúšťam cluster...")

    // Graceful leave broadcastuje všetkým nodom
    if err := memberlist.Leave(10 * time.Second); err != nil {
        log.Printf("Nepodarilo sa čisto odísť: %v", err)
    }

    memberlist.Shutdown()
}

Možnosť 2: Unikátny Node Identifier (Nie IP-Based)

// PRED: Node identita viazaná na IP
config.Name = fmt.Sprintf("node-%s", ip)

// PO: Použi unikátny identifikátor ktorý prežije IP zmeny
config.Name = fmt.Sprintf("node-%s-%d", hostname, time.Now().UnixNano())
// Alebo použi pod UID:
config.Name = os.Getenv("POD_UID")  // Nastavené cez downward API

// Takto nový node s rovnakou IP má inú identitu
// Gossip ho vidí ako úplne nový node, nie konfliktný
# Kubernetes: Injektuj pod UID ako node name
env:
  - name: POD_UID
    valueFrom:
      fieldRef:
        fieldPath: metadata.uid
  - name: GOSSIP_NODE_NAME
    value: "$(POD_UID)"

Možnosť 3: Rýchlejší Dead Node Pruning

// Zníž čas ako dlho dead nody zostávajú v membership
config := memberlist.DefaultConfig()

// Default: Dead nody zostávajú 30 sekúnd
// Zníž pre fast-recycling prostredia:
config.GossipToTheDeadTime = 5 * time.Second

// Rýchlejšia failure detekcia (tradeoff: viac false positives)
config.ProbeInterval = 500 * time.Millisecond
config.ProbeTimeout = 200 * time.Millisecond
config.SuspicionMult = 2  // Default je 4

Možnosť 4: IP Lease Rozšírenie

# Calico: Predĺž IP hold time po zmazaní podu
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
  name: default-pool
spec:
  cidr: 10.0.0.0/16
  # Drž IP rezervované dlhšie po uvoľnení
  # Dáva gossipu čas na prune dead záznamov

Monitoring

groups:
  - name: gossip-health
    rules:
      - alert: GossipDuplicateNodes
        expr: |
          count by (ip) (gossip_member_info) > 1
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Viaceré gossip nody zdieľajú IP {{ $labels.ip }}"

      - alert: GossipNodeRejections
        expr: |
          rate(gossip_join_rejections_total[5m]) > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Gossip cluster odmieta join pokusy"

Checklist

## Gossip Ghost Nodes

### Symptómy
- [ ] Nové pody sa nemôžu pripojiť k existujúcemu clusteru
- [ ] Gossip ukazuje viacero nodov s rovnakou IP
- [ ] "Conflicting incarnation" chyby v logoch
- [ ] Sieť funguje ale gossip membership zlyháva

### Diagnostika
- [ ] Skontroluj duplicitné IP záznamy v membership
- [ ] Porovnaj pod start time s gossip join time
- [ ] Zapni gossip debug logging
- [ ] Skontroluj IP pool size vs pod churn rate

### Riešenia
- [ ] Implementuj graceful leave pri shutdown
- [ ] Použi unikátny node ID (pod UID) nie IP-based name
- [ ] Zníž dead node retention time
- [ ] Predĺž IP lease/hold time
- [ ] Monitoruj duplicitné membership záznamy

Záver

Lekcia: gossip protokoly predpokladajú že node identita je stabilná, ale Kubernetes IP alokácia toto negarantuje. “Ghost” z mŕtveho podu môže strašiť jeho IP adresu minúty po smrti.

Kľúčové princípy:

  1. Node identita by nemala byť IP-based v dynamických prostrediach
  2. Graceful leave je kritický - povedz clusteru že umieraš
  3. Dead node pruning musí byť rýchlejší než IP reuse
  4. Monitoruj membership anomálie

Súvisiace články

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Gossip Protocol Ghost Nodes: IP Reuse Strašiaci Váš Cluster". https://www.michal-drozd.com/sk/blog/gossip-ghost-nodes-ip-reuse/ (Publikované 10. februára 2025).