Разбираемся, что за зверь хранит весь ваш Kubernetes, учимся читать его ошибки и честно отвечаем на вопрос, можно ли ему доверять
Что это вообще
etcd — распределённое key‑value хранилище со строгой консистентностью. Если на пальцах: это место, где Kubernetes хранит авторитетное состояние кластера. Деплойменты, поды, секреты, конфигмапы — всё это записи в etcd. kube‑apiserver — по сути толстый REST‑фасад над ним, а scheduler, controller‑manager и kubelet не являются источниками истины для состояния kubernetes; они получают его через apiserver и имеют собственное локальное состояние
Отсюда первое следствие, которое стоит выжечь на подкорках:
Умер etcd == умерла память кластера. Поды продолжат бежать (kubelet автономен), но задеплоить, отскейлить или изменить что‑либо уже нельзя. Грубо говоря — экспонаты работают, трогать запрещено.
Raft на пальцах и магия чисел
etcd переживает падение узлов благодаря протоколу консенсуса Raft. Упрощённо он работает так:
-
Узлы выбирают лидера. Только лидер проводит записи через Raft; клиенту при этом необязательно знать, кто сейчас лидер. follower может автоматически перенаправить consensus‑запрос
-
Каждая запись это proposal: лидер пишет её в свой журнал (WAL) и рассылает фолловерам.
-
Когда кворум — большинство узлов (2 из 3, 3 из 5) — подтвердил запись на диске, она считается committed и применяется к состоянию.
-
Пропал лидер → новые выборы, счетчик
termувеличивается, жизнь продолжается.

Отсюда магия чисел: кластер из 3 узлов переживает потерю 1, из 5 потерю 2. А кластер из 2 узлов хуже, чем из одного: кворум = 2, потеря любого узла = потеря кворума. Вы заплатили за два сервера и построили распределённую единую точку отказа.
Как безопасно расширять кластер: Learner‑узлы
Классическая проблема: у вас 3 узла, вы хотите добавить 4й чтоб потом вывести старый. В момент добавления пустого узла кворум становится 3 из 4. Если пустой узел долго синхронизирует базу (а сеть или диск подтупливают) и в этот момент падает один из старых узлов то кластер встаёт колом. Вы потеряли кворум в попытке сделать безопаснее.
Решение ставшее индустриальным стандартом — Learner nodes. Добавляйте новый узел флагом --learner.

-
Ученик синхронизирует журнал (Raft log) от лидера.
-
Ученик не участвует в голосованиях и не влияет на расчет кворума (кворум остается 2 из 3).
-
Только когда метрика синхронизации догоняет лидера, ученик переводится в полноправные участники командой member promote.
Внутренности: WAL, bbolt, MVCC
Три кита, из которых растут 90% ошибок в логах:
WAL (write‑ahead log) — журнал в member/wal/, туда попадают proposals и состояние Raft, необходимое для восстановления. Это гарантия durability: что подтверждено — то переживёт ребут. Латентность fsync WAL — метрика № 1 здоровья etcd: etcd_disk_wal_fsync_duration_seconds, официальный ориентир — p99 ниже 10 мс.
bbolt — встраиваемая B+tree база (member/snap/db), собственно хранилище. Важная особенность: bbolt не отдаёт место ОС после удаления данных, файл только растёт.
MVCC (multi‑version concurrency control) — etcd не перезаписывает ключи, а хранит все версии, каждая с глобальным монотонным номером — revision. Когда вы делаете kubectl get pods --watch, вы буквально подписываетесь на поток ревизий. Старые версии нужно чистить операциями compaction (забыть старое) и defrag.
Над всем этим — квота --quota-backend-bytes, по умолчанию 2 GiB (рекомендованный потолок 8 GiB). Пробили квоту — etcd поднимает аларм NOSPACE и переводит в read‑only весь кластер.
Анатомия дисковой подсистемы: разделяй и властвуй
Чтобы понять, почему диски так важны, посмотрим на критический путь записи

Поскольку клиент ждет только WAL, нужно понимать профиль нагрузки на SSD:
-
WAL — преимущественно последовательная append‑запись. Важна низкая латентность и желательно наличие на SSD конденсаторов PLP (Power Loss Protection), чтобы контроллер моментально подтверждал
fsync. -
bbolt — случайное чтение/запись (Random I/O).
Если они лежат на одном диске, тяжелый defrag или снятие снапшота bbolt забивает очередь (Queue Depth) контроллера SSD. Последовательная запись в WAL встаёт в очередь за жирными блоками bbolt, latency WAL растёт, heartbeat начинает опаздывать, а при достаточной деградации возникают лишние выборы лидера
Best practice для highload: Разносить их физически. Флаг --wal-dir позволяет вынести журнал на отдельный сверхбыстрый NVMe‑накопитель, оставив /var/lib/etcd на диске попроще.
Часть 2. Где обычно болит
Конфигурация: типовые грабли
Квота на дефолте. 2 GiB на живом кластере с шумными CRD и операторами, пишущими статусы каждые 5 секунд, выедаются за недели. Дальше по учебнику: NOSPACE, read‑only.
Не задан --auto-compaction-retention. В самом etcd auto‑compaction по умолчанию отключён (0). Если история MVCC не компактируется, она постепенно съедает quota. В кубах способ и параметры compaction зависят от того, как развёрнут control plane, поэтому не стоит считать его магически настроенным просто потому, что это кубы.
Чётное число членов. Обсудили: минус доступность.
Ручные правки static pod‑а. /etc/kubernetes/manifests/etcd.yaml правится руками при каждом «а давайте подкрутим». Опечатка в имени флага — kubelet рестартует под в CrashLoopBackOff ‑→ у вас нет apiserver, чтобы это увидеть. Диагностика только через crictl и journalctl кубелета
История одного факапа: Как 1900 Evicted‑подов убили кластер
Подойдем к суровой практике. Как‑то раз я напоролась на кластер, вставший колом. Поды крутятся, приложения отдают трафик, но задеплоить ничего нового нельзя. kubectl get pods работает, а вот apply или delete зависают и отваливаются по таймауту.
kubbectl get po ‑A = кладбище 1900 подов в статусе Evicted.
Что произошло под капотом?
-
На одной из рабочих нод кончилось место на диске. Автономный
kubeletзапаниковал и начал спасать ноду, вытесняя поды. -
Контроллеры (ReplicaSet/Deployment) тут же пытались пересоздать эти поды, они снова падали на проблемные ноды и снова получали статус
Evicted. -
etcd: Каждое изменение статуса пода заставляет kubelet слать update в apiserver.
-
Apiserver покорно пишет это в etcd. Но мы же помним про MVCC! etcd не перезаписывает статус, он создает новую ревизию ключа. 1900 подов, которые агрессивно флапают= десятки тысяч новых ревизий за очень короткое время.
-
Файл базы bbolt стремительно раздувается
-
etcd понимает, что писать больше некуда и чтобы избежать коррапта поднимает тревогу NOSPACE
Попала я в классический капкан: не могу удалить поды через kubectl, потому что удаление в кубах аналогично операции записи (установка
deletionTimestamp), а база данных в режиме RO.
Как из этого выбираться?
Подключаемся напрямую к etcd (через crictl exec в под etcd или локально с сертификатами). Берем текущую ревизию кластера (etcdctl endpoint status).
-
etcdctl compact <revision>, приказываем забыть старые ревизии MVCC. -
etcdctl defrag= самая тяжелая, блокирующая операция. Возвращает пустые страницы изbboltобратно операционной системе. Файл базы физически уменьшается. -
etcdctl alarm disarm= снимаем тревогу NOSPACE.
Мораль: Именно поэтому auto‑compaction должен быть настроен всегда, дефолтную квоту для крупных кластеров стоит увеличивать до 8 GiB, а директорию /var/lib/etcd ОБЯЗАТЕЛЬНО выносить на отдельный раздел или диск, чтобы взбесившийся kubelet или переполненный /var/log на мастере не нагадили в панамку.
Учимся читать
|
Строка в логе |
Что означает |
Что делать |
|---|---|---|
|
|
Диск не тянет fsync. Корень большинства бед. |
Отдельный SSD/NVMe под WAL, найти соседей по IO. |
|
|
Лидер перегружен или сеть/диск деградировали. |
Смотреть fsync + сеть. |
|
|
Лидер не успел за 100 мс, обычно диск, не сеть. |
То же + поднять |
|
|
Proposal не собрал кворум за ожидаемое время |
Искать, какой узел/диск тормоз. |
|
|
Записи committed, но применяются медленно. |
Диск, defrag, размер значений, CPU starvation |
|
|
Квота пробита, кластер read‑only. |
compact, defrag, disarm. |
|
|
Значение больше |
Не хранить огромные configmap/блобы в etcd. |
Мониторинг жив/мёртв для etcd бесполезен, если apply‑петля захлёбывается. Караулить здоровье etcd можно хотя бы этой шестеркой метрик:
etcd_disk_wal_fsync_duration_seconds # p99 < 10msetcd_disk_backend_commit_duration_secondsetcd_server_proposals_failed_total # должно быть 0etcd_server_leader_changes_seen_total # рост = флаппинг лидераetcd_mvcc_db_total_size_in_bytesetcd_mvcc_db_total_size_in_use_in_bytes
Часть 3. А можно ли вообще доверять etcd?
Аргумент за: В анализе etcd 3.4.3 Jepsen пришёл к выводу, что KV‑операции выдержали проверку на strict serializability (строжайшая модель согласованности) под крашами процессов и сбоями сети. Это комплимент века. Библиотека etcd/raft стала индустриальным стандартом (используется в CockroachDB, TiKV).
Аргументы против и древние скелеты в шкафу:
-
В ранних версиях ветки 3.5 существовал баг, из‑за которого после OOMKill состояние backend могло расходиться с кластером, но Raft этого не замечал.
-
UPD: в etcd 3.6 появился отдельный Robustness Testing framework, который прогоняет кластер через различные нагрузки и отказовые сценарии с последующей проверкой linearizability. При этом встроенная corruption detection существует отдельно и не является включённой по умолчанию в качестве GA‑механизма: в актуальных версиях она управляется feature gates.
-
-
Локи = не совсем локи. Lease сам по себе не является гарантией взаимного исключения: клиент может считать lease действующим, тогда как сервер уже его отозвал. Встроенный lock использует проверки revision/version для защиты от этого сценария. Для внешних ресурсов нужен собственный механизм fencing/version validation.
-
Raft не защищает от полуживого железа. Частичный отказ свитча может вызвать выборочную видимость узлов и карусель перевыборов лидера. Формально база цела, фактически control plane лежит.
География: etcd в Multi‑AZ
Многие размазывают 3 узла etcd по трем разным зонам доступности (AZ). Грубо говоря, commit latency упирается в RTT между членами и latency записи на диск до достижения кворума. Multi‑AZ — норм сценарий. Multi‑Region технически возможен, но WAN RTT напрямую увеличивает commit latency и делает control plane значительно медленнее. Для etcd это обычно плохой компромисс
Часть 4. Чеклист паровозика, который выжил
-
Диски. Отдельное блочное устройство под etcd. Для highload — разнесение WAL (
--wal-dir) на сверхбыстрый NVMe с PLP, база (/var/lib/etcd) на отдельном SSD. В облаке избегайте дешевых сетевых дисков. -
--quota-backend-bytes=8589934592,--auto-compaction-mode=periodic,--auto-compaction-retention=1h(вне k8s обязательно). -
Расширение кластера только через Learner nodes. Сначала
--learner, потомmember promote. Нечетное число участников (3 или 5). -
Алерты на шестерку метрик + например, alert при
db_size / quota > 0.8+ свободное место на диске < 20%. -
etcdctl snapshot saveкроном + периодическая проверка раскатки бэкапа-
с версии 3.6
-
snapshot save —
etcdctl -
snapshot restore/status —
etcdutl
-
-
-
настройте очистку завершённых джобов (
ttlSecondsAfterFinished), ограничьте lifetime событий (--event-ttl) и не складывайте большие блобы в configmap.С etcd 3.6 не путайте инструменты:
etcdctlработает с живым кластером, аetcdutlпредназначен для offline‑операций над данными и snapshot restore
Вместо заключения
etcd — база,пережившая Jepsen, публично признала свои баги и внедрила CI‑проверки на их исключение в будущем. Можно доверять, но доверие это контрактное: с вас будут диски, мониторинг, апдейты, а если нарушите свою часть, то никакой консенсус не спасёт. Если же etcd для вас избыточен, то смотрите в сторону kine (k3s поверх PostgreSQL/SQLite).
На почитать
-
D. Ongaro, J. Ousterhout, “In Search of an Understandable Consensus Algorithm” — [Raft paper](https://raft.github.io/raft.pdf)
-
Jepsen: etcd 3.4.3 (2020) — [Jepsen: etcd 3.4.3](https://jepsen.io/analyses/etcd-3.4.3)
-
etcd: Data Inconsistency Postmortem — [v3.5 Data Inconsistency Postmortem](https://github.com/etcd‑io/etcd/blob/main/Documentation/postmortems/v3.5-data‑inconsistency.md)
-
Cloudflare, “A Byzantine failure in the real world” — [Cloudflare: A Byzantine failure in the real world](https://blog.cloudflare.com/a‑byzantine‑failure‑in‑the‑real‑world/)
-
M. Kleppmann, “How to do distributed locking” — [Martin Kleppmann: How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how‑to‑do‑distributed‑locking.html)
-
etcd: Learner Nodes Design — [Learner Nodes Design](https://etcd.io/docs/v3.7/learning/learner/)
-
etcd: Hardware Recommendations — [Hardware Recommendations](https://etcd.io/docs/v3.7/op‑guide/hardware/)
-
etcd: Tuning — [Tuning](https://etcd.io/docs/v3.7/tuning/)
-
etcd: Monitoring — [Monitoring etcd](https://etcd.io/docs/v3.7/op‑guide/monitoring/)
-
etcd: Maintenance — [Maintenance](https://etcd.io/docs/v3.7/op‑guide/maintenance/)
-
etcd: Disaster Recovery — [Disaster Recovery](https://etcd.io/docs/v3.7/op‑guide/recovery/)
-
etcd: Configuration Options — [Configuration Options](https://etcd.io/docs/v3.7/op‑guide/configuration/)
-
etcd: Persistent Storage Files — [Persistent Storage Files](https://etcd.io/docs/v3.7/learning/persistent‑storage‑files/)
-
etcd: v3.6 Release Notes — [Announcing etcd v3.6.0](https://etcd.io/blog/2025/announcing‑etcd-3.6/)
-
etcd: Autonomous Robustness Testing — [Autonomous Testing of etcd’s Robustness](https://etcd.io/blog/2025/autonomus_testing_with_antithesis/)
-
etcd: Network and Timeout Tuning — [Network and Timeout Tuning](https://etcd.io/docs/v3.7/tuning/)
ссылка на оригинал статьи https://habr.com/ru/articles/1072628/