etcd 3.4 в Kubernetes 1.19: настраиваем под продакшн
etcd 3.4 стал дефолтным для Kubernetes 1.19. Разбираем выделенные SSD с latency <1ms, I/O scheduler и мониторинг wal_fsync_duration в Prometheus.
etcd 3.4 становится дефолтным для Kubernetes 1.19, что выводит тему настройки хранилища raft-лога под продакшн-нагрузку на первый план
Когда мы обновляли кластеры до Kubernetes 1.19, одним из негромких изменений был переход на etcd 3.4 как дефолтную версию. Само по себе это не событие - 3.4 вышел ещё в 2019-м, многие уже использовали. Но несколько инцидентов с нестабильным leader election на клиентских кластерах заставили нас заново пройтись по тому, как мы вообще готовим etcd к продакшн-нагрузке. Оказалось, что некоторые детали конфигурации мы принимали как данность без особых оснований.
Почему disk latency - это и есть leader election
etcd использует Raft - распределённый консенсус-протокол, где запись подтверждается только после того, как большинство участников записали лог. Критический путь выглядит так: лидер получает запись, пишет в WAL (write-ahead log) на диск, рассылает записи фолловерам, ждёт подтверждений, коммитит.
Важный момент: fsync WAL блокирует обработку следующих запросов. Если wal_fsync_duration начинает расти - лидер перестаёт отвечать на heartbeat в срок, и кластер запускает очередные выборы. Нам попадался кластер, где на разделённом SAN-дисковом пространстве с латентностью 3-5ms etcd переизбирал лидера несколько раз в час под нагрузкой - при внешне здоровом Kubernetes. Поды, которые в этот момент делали запросы через kubectl или через API-server, получали случайные таймауты. Диагностировали не сразу, потому что на обычных графиках в Grafana всё выглядело зелёным.
etcd разработчики сами пишут в документации: нужен диск с задержкой записи менее 10ms для нормальной работы, менее 1ms - для хорошей. Разница между «нормальной» и «хорошей» в продакшне ощущается вполне конкретно.
Что делаем с дисками
Выделенный SSD под etcd. Не делить диск с containerd, kubelet, системными логами, prometheus-agent и прочим, что периодически пишет крупными блоками. На shared-диске один dd if=/dev/zero способен создать вам внеплановый leader election. Отдельный NVMe под etcd данные и WAL - хороший минимум для серьёзного кластера.
WAL и данные на разных томах. Менее обязательно, чем выделенный диск вообще, но если диск один - хотя бы убедитесь что --data-dir и --wal-dir не конкурируют за I/O с чем-то ещё. etcd по умолчанию кладёт WAL в <data-dir>/member/wal. Если хотите разнести - указываете --wal-dir отдельно при запуске.
I/O scheduler. На modern NVMe-дисках scheduler none (он же noop в старых ядрах) предпочтительнее cfq или bfq. CFQ пытается быть справедливым между процессами, но для etcd это значит что он может придержать ваш fsync, пока не обслужит чью-то большую sequential read. Проверяем текущий scheduler:
cat /sys/block/nvme0n1/queue/scheduler
Меняем:
echo none > /sys/block/nvme0n1/queue/scheduler
Для persistence между перезагрузками - через udev rules или в /etc/rc.local, в зависимости от дистрибутива. На нескольких кластерах этот переход дал ощутимое снижение p99 latency по wal_fsync_duration.
--quota-backend-bytes. По умолчанию 2GB. При достижении квоты etcd переходит в maintenance mode и перестаёт принимать записи - кластер деградирует. Для кластеров с большим количеством CRD, секретов, configmap-ов смотрим реальный размер через etcdctl endpoint status и выставляем квоту с запасом. Максимум - 8GB, больше etcd официально не рекомендует.
Мониторинг: что смотреть в Prometheus
etcd 3.4 экспортирует метрики через /metrics endpoint. Для Kubernetes-кластеров он обычно доступен на порту 2381 (по умолчанию). Ключевые метрики:
etcd_disk_wal_fsync_duration_seconds - гистограмма времени fsync WAL. Смотрим p99. Если p99 > 10ms стабильно - проблема с диском или I/O contention. Если p99 кратковременно уходит за 50-100ms - ищем что в этот момент пишет на диск.
etcd_disk_backend_commit_duration_seconds - время коммита boltdb backend. p99 должен быть в разумных пределах, но WAL fsync критичнее для latency кластера.
etcd_server_leader_changes_seen_total - счётчик смены лидера. В нормальном кластере растёт только при рестартах нод. Если растёт в idle - что-то не так с latency или сетью между etcd-нодами.
etcd_network_peer_round_trip_time_seconds - RTT между участниками. Для хорошей работы Raft нужен RTT < 10ms между нодами. Если etcd-ноды размазаны по дата-центрам с RTT 30-50ms - лучший диск не спасёт.
Пример recording rule для алертинга:
- alert: EtcdHighWalFsyncDuration
expr: histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) > 0.01
for: 5m
labels:
severity: warning
annotations:
summary: "etcd WAL fsync p99 > 10ms на {{ $labels.instance }}"
Параметры heartbeat и election timeout
etcd 3.4 по умолчанию: --heartbeat-interval=100ms, --election-timeout=1000ms. Для кластеров на быстрых локальных дисках и низкой сетевой latency это нормально. Если диски медленнее или RTT между нодами выше - можно увеличить --election-timeout до 2000-5000ms, чтобы дать лидеру больше времени на медленный fsync без запуска выборов. Но это двухстороннее решение: более высокий timeout означает более долгое восстановление при реальном падении лидера. Увеличиваем осторожно, наблюдая метрики.
Где сейчас
После того как мы прошлись по этому чеклисту на клиентских кластерах под сопровождением - хаотичные timeouts ушли. Основным виновником был shared-диск на одном из кластеров и CFQ-scheduler на другом. Не rocket science, но нужно было потратить время чтобы это системно проверить, а не гасить симптомы рестартами etcd.
etcd - не та компонента, которую хочется трогать в панике посреди инцидента. Лучше один раз настроить правильно и мониторить wal_fsync_duration в фоне.
- Kubernetes 1.19 GA: обновляем продакшн с extensions/v1beta1 на Ingress v1 · 3 сентября 2020
- Grafana 7.2: library panels и трансформации без ETL · 19 октября 2020