etcd 3.4 в Kubernetes 1.19: настраиваем под production
etcd 3.4 стал дефолтным для Kubernetes 1.19. Разбираем выделенные SSD с latency <1ms, I/O scheduler и мониторинг wal_fsync_duration в Prometheus.
etcd 3.4 становится дефолтным для Kubernetes 1.19, что выводит тему настройки хранилища raft-лога под production нагрузку на первый план
Когда мы обновляли кластеры до Kubernetes 1.19, одним из негромких изменений был переход на etcd 3.4 как дефолтную версию. Само по себе это не событие - 3.4 вышел ещё в 2019-м, многие уже использовали. Но несколько инцидентов с нестабильным leader election на клиентских кластерах заставили нас заново пройтись по тому, как мы вообще готовим etcd к production нагрузке. Оказалось, что некоторые детали конфигурации мы принимали как данность без особых оснований.
Почему 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 - для хорошей. Разница между «нормальной» и «хорошей» в production ощущается вполне конкретно.
Что делаем с дисками
Выделенный 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 означает более долгое восстановление при реальном падении лидера. Увеличиваем осторожно, наблюдая метрики.
Где сейчас
После того как мы прошлись по этому чеклисту на клиентских кластерах под managed-сопровождением - хаотичные timeouts ушли. Основным виновником был shared-диск на одном из кластеров и CFQ-scheduler на другом. Не rocket science, но нужно было потратить время чтобы это системно проверить, а не гасить симптомы рестартами etcd.
etcd - не та компонента, которую хочется трогать в панике посреди инцидента. Лучше один раз настроить правильно и мониторить wal_fsync_duration в фоне.
- Kubernetes 1.19 GA: обновляем production с extensions/v1beta1 на Ingress v1 · 3 сентября 2020
- Grafana 7.2: library panels и трансформации без ETL · 19 октября 2020