Разбираемся, что за зверь хранит весь ваш 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 начинает опаздывать, а при достаточной деградации возникают лишние выборы лидера.
Никогда не запускайте
defragодновременно на всех мемберах кластера. Во время live defrag member блокирует обработку операций на время перестройки backend. Если одновременно положить на defrag весь кластер, можно временно потерять доступность control plane.Если
ETCD_ENDPOINTSсодержит все endpoints кластера, не передавайте его вслепую в команду defrag. Выбирайте один member, дождитесь его завершения и переходите к следующему.
Best practice для highload: Разносить их физически. Флаг --wal-dir позволяет вынести журнал на отдельный сверхбыстрый NVMe‑накопитель, оставив /var/lib/etcd на диске попроще.
Fio и проверка storage
Для etcd недостаточно посмотреть на паспортную скорость SSD. Красивые 5000 MB/s в fio --rw=read --bs=1M мало что говорят о том, насколько хорошо storage подходит для etcd.
Для WAL гораздо важнее latency синхронных записей. etcd последовательно пишет данные в WAL и периодически выполняет fdatasync, чтобы гарантировать durability. Поэтому storage с огромным throughput, но плохой latency на sync-write может оказаться для etcd гораздо хуже, чем менее быстрый, но предсказуемый SSD.
Для грубой проверки можно использовать fio.
Например:
[global] time_based=1 group_reporting=1 directory=/mnt/wal loops=1 ramp_time=5s [etcd] ioengine=sync fdatasync=1 direct=0 bs=2300 iodepth=1 rw=write nrfiles=1000 filesize=64MiB fallocate=truncate
Этот профиль пытается приблизить одну из важных характеристик WAL - последовательную запись с fdatasync и малой глубиной очереди. Это не полный эмулятор workload etcd.
Не используйте runtime= с заранее ограниченным набором файлов, если хотите измерить именно append-поведение. После исчерпания пространства тест начнёт повторно использовать уже записанные области, и профиль нагрузки перестанет соответствовать первоначальной модели.
Гонять такой тест нужно на тестовом сторе или на отдельном mount. Не надо запускать fio с таким workload прямо на /var/lib/etcd, пока там живой production etcd, иначе получится прекрасный эксперимент по проверке вашей способности объяснять инцидент руководству.
По поводу подобных тестов для bbolt - WAL и backend имеют разные I/O-профили, поэтому хороший результат synthetic WAL benchmark не означает автоматически хороший результат для bbolt backend.
Часть 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 < 10ms etcd_disk_backend_commit_duration_seconds etcd_server_proposals_failed_total # должно быть 0 etcd_server_leader_changes_seen_total # рост = флаппинг лидера etcd_mvcc_db_total_size_in_bytes etcd_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 —
etcdctlsnapshot 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/)

