Múlt hónapban hajnali 2-kor csörgött a telefonom, mert egy éles cluster API szervere elkezdett timeoutolni. A podok nem schedulálódtak, a kubectl csak lógott, a Slack csatorna pedig addigra teljesen elszabadult. Úgy fél óra múlva jutottam el oda, hogy megint az etcd volt a hibás.

Az etcd minden Kubernetes cluster közepén ott van, szóval ha neki rossz napja van, azt az egész rendszer megérzi. Pont ez benne a kellemetlen: az etcd hibái ritkán egyértelműek. Szinte soha nincs egy tiszta jelzés arról, hogy “na igen, ez most biztosan etcd”. Inkább olyan tüneteket látsz, mint a lassú API hívások, a késő scheduling vagy a furcsa timeoutok. Elég sok ilyen incidensen vagyok túl ahhoz, hogy legyen egy saját rövid ellenőrzőlistám, és mostanában az etcd-diagnosis sokat segít abban, hogy gyorsabban jussak a lényeghez.

Az első öt perc

Ha arra gyanakszom, hogy az etcd körül van a gond, mindig az alapokkal kezdek. Ez a három parancs gyorsan megmutatja, hogy a cluster egyáltalán mennyire van életben:

# Minden member egészséges?
etcdctl endpoint health --cluster -w table

# Member státusz, leader, raft index
etcdctl endpoint status --cluster -w table

# Memberek és peer URL-jeik
etcdctl member list -w table

Nekem ilyenkor három dolog számít: minden member healthy-e, van-e leader, és mozognak-e a raft indexek vagy csak állnak egy helyben. Ha az endpoint health egy konkrét membernél lóg vagy hibával tér vissza, általában ott érdemes tovább ásni.

Az elején ez többször megfogott: managed környezetben, vagy egyes disztróknál az etcdctl nincs fent a hoston. Ilyenkor egyszerűbb bemenni az etcd podba:

kubectl exec -n kube-system etcd-controlplane-0 -- etcdctl \
  --cacert /etc/kubernetes/pki/etcd/ca.crt \
  --cert /etc/kubernetes/pki/etcd/server.crt \
  --key /etc/kubernetes/pki/etcd/server.key \
  endpoint health --cluster

A két fő bűnös: diszk és tárhely

A tapasztalatom szerint az etcd incidensek nagyjából 80 százaléka két kategóriába esik.

Lassú diszkek

Az etcd nagyon érzékeny a diszk latenciára. Minden tranzakció bekerül a WAL-ba (write-ahead log), aztán jön az fsync. Ha ez a késleltetés tartósan 10 ms fölé kúszik, abból általában hamar probléma lesz.

Így ellenőrzöm:

# etcd metrikákból (ha van Prometheus)
etcd_disk_wal_fsync_duration_seconds_bucket

# Vagy gyors fio teszt az etcd adat könyvtáron
fio --name=etcd-test --filename=/var/lib/etcd/test \
  --rw=write --ioengine=sync --fdatasync=1 \
  --size=22m --bs=2300 --runtime=30

A fio teszt elég jól közelíti azt az írási mintát, amit az etcd használ. Ha a p99 latencia 10 ms felett van, akkor a háttértár túl lassú. Láttam már olyat, hogy valaki az etcd-t egy megosztott EBS volume-ra rakta, miközben ugyanaz a volume egy adatbázist is kiszolgált. Ez szinte biztosan rossz ötlet. Az etcd kapjon saját gyors SSD-t, felhőben lehetőleg provisioned IOPS-szal.

Database space exceeded

A másik klasszikus a rettegett mvcc: database space exceeded hiba. Az etcd alapértelmezett tárhelykvótája 2 GiB. Ha ezt eléred, a write-ok leállnak, a cluster pedig gyakorlatilag befagy.

Erre általában a compaction, majd a defragmentálás a megoldás:

# Aktuális revision lekérése
rev=$(etcdctl endpoint status -w json | jq '.[0].Status.header.revision')

# Compaction az aktuális revision előttig
etcdctl compact $rev

# Defragmentálás memberenként (egyszerre csak egyet!)
etcdctl defrag --endpoints=https://etcd-0:2379
etcdctl defrag --endpoints=https://etcd-1:2379
etcdctl defrag --endpoints=https://etcd-2:2379

# Alarm ellenőrzése
etcdctl alarm list

# Alarm törlése, ha a defrag felszabadított elég helyet
etcdctl alarm disarm

A sorrend itt számít. Először compact, aztán defrag. És az összes membert egyszerre semmiképp ne defragmentáld, mert a defrag rövid időre blokkolja az adott membert, és ezt nem akkor akarod megtapasztalni, amikor a quorum a tét.

Ezt is a nehezebb úton tanultam meg: attól, hogy van auto-compaction beállítva, még simán belefuthatsz a space limitbe, ha nincs rendszeres defragmentálás. A compaction csak megjelöli a felszabadítható helyet, de ténylegesen a defrag adja vissza. Nálam ezért most már CronJob futtatja.

Az etcd-diagnosis eszköz

Nemrég elkezdtem használni az etcd-diagnosis eszközt, és őszintén szólva korábban órákat spórolt volna meg nekem. Ahelyett, hogy egyesével nézném végig az összes fontos jelet, elég ezt lefuttatni:

etcd-diagnosis report --endpoints=https://etcd-0:2379,https://etcd-1:2379,https://etcd-2:2379 \
  --cacert /path/to/ca.crt \
  --cert /path/to/client.crt \
  --key /path/to/client.key

Egy olyan riportot generál, amiben benne van:

  • Cluster health és membership
  • Diszk I/O latencia (WAL fsync)
  • Hálózati latencia a memberek között
  • Erőforrásterhelés (memória, diszkhasználat)
  • Kulcsfontosságú etcd metrikák

A legjobb benne az, hogy ezt a riportot egyből oda lehet adni upstream karbantartóknak vagy a vendorodnak, és nem kell még öt kört futni azzal, hogy “ezt is át tudnád küldeni?”. Gyakorlatilag kész diagnosztikai csomag.

Megelőzés

Miután ezt párszor megszívtam, nálam ez az alap minden clusteren:

Monitoring alertek:

# Alert magas WAL fsync latenciánál
- alert: EtcdHighFsyncDuration
  expr: histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) > 0.01
  for: 5m
  labels:
    severity: warning

# Alert mielőtt elfogy a hely
- alert: EtcdDatabaseSizeNearQuota
  expr: etcd_mvcc_db_total_size_in_bytes / etcd_server_quota_backend_bytes > 0.8
  for: 10m
  labels:
    severity: warning

Ütemezett defragmentálás:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: etcd-defrag
  namespace: kube-system
spec:
  schedule: "0 3 * * 0"  # Hetente, vasárnap hajnali 3
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: defrag
            image: bitnami/etcd:3.5
            command:
            - /bin/sh
            - -c
            - |
              for ep in https://etcd-0:2379 https://etcd-1:2379 https://etcd-2:2379; do
                echo "Defragmentálás: $ep"
                etcdctl defrag --endpoints=$ep \
                  --cacert=/certs/ca.crt \
                  --cert=/certs/client.crt \
                  --key=/certs/client.key
                sleep 30
              done              

Dedikált tároló: az etcd saját volume-ot kap. Nincs megosztás.

Tanulságok

  1. A legtöbb etcd probléma valójában tárolóprobléma. Először mindig ezt nézd meg.
  2. Az apply request took too long szinte mindig fsync latenciát jelent.
  3. Az auto-compaction nem jelent auto-defragot. Mindkettő kell.
  4. Tartsd kicsiben az etcd adatbázist. Ha 1 GiB fölé nő, ott valami nem stimmel, gyakran elavult resource-ok vagy egy controller loop termeli feleslegesen az objektumokat.
  5. Három member általában az ideális. Öt több hibatűrést ad, de lassabbak lesznek az írások. Egyetlen memberrel pedig csak kéred a bajt.
  6. Rendszeresen mentsd az etcd-t: etcdctl snapshot save backup.db. És teszteld a visszaállítást.

Az etcd tipikusan olyan komponens, ami csendben teszi a dolgát, egészen addig, amíg egyszer csak nem. És amikor nem, akkor rengeteget számít, hogy kéznél legyen pár jól bevált parancs, és tudd, milyen jeleket kell nézni. Sokszor ezen múlik, hogy negyedóra alatt megvan a javítás, vagy három órán át megy a kapkodás.