etcd 3.3 и backup-restore: проверили disaster recovery на реальном кластере Kubernetes
Настроили ежечасный CronJob для snapshot etcd в S3-совместимое хранилище и прогнали полный disaster-recovery сценарий - восстановление с нуля заняло около четырёх минут.
etcd 3.3 - улучшения производительности и механизм snapshot для Kubernetes
etcd - это та часть Kubernetes, о которой стараются не думать лишний раз. Работает в фоне, хранит всё состояние кластера, и при спокойной работе о нём никто не вспоминает. Мы решили, что такое отношение к компоненту, без которого кластер превращается в набор бесхозных контейнеров, - не очень хорошая идея. И взялись за нормальный backup-процесс.
Поводом стал не инцидент, что уже неплохо. В etcd 3.3 подтянули работу с snapshot-ами через etcdctl - команда etcdctl snapshot save стала более предсказуемой, появился snapshot status для проверки целостности. Мы как раз обновляли кластера под управлением managed-инфраструктуры и решили совместить обновление с нормально выстроенным DR-процессом.
Что именно настраивали
Задача звучала просто: делать snapshot etcd каждый час, складывать в S3-совместимое хранилище, и уметь из него восстановиться за разумное время.
Реализация - CronJob в Kubernetes, который запускается раз в час и делает примерно следующее:
- Запрашивает snapshot.
etcdctl snapshot saveс параметрами подключения к etcd - эндпойнт, TLS-сертификаты через projected volume из Secret. Snapshot пишется во временный файл внутри контейнера. - Верифицирует snapshot.
etcdctl snapshot statusс выводом в таблицу - смотрим что ревизия, количество ключей и размер файла вменяемые. Если статус не возвращает нормального ответа - Job завершается с ошибкой, Alertmanager видит failed CronJob. - Загружает в хранилище.
aws s3 cpв S3-совместимое хранилище. Имя файла включает временную метку в UTC. Lifecycle rule на бакете - удалять файлы старше семи дней.
CronJob запускается в отдельном namespace с минимальными правами - ему нужен доступ только к Secret с etcd TLS-сертификатами и endpoint-у etcd. Serviceaccount без лишних RBAC-прав.
Один момент, который сразу выяснился: etcd-эндпойнт нужно указывать конкретно - не через kube-apiserver, а прямо на etcd member. Иначе snapshot берётся через API, который делает это иначе и без гарантий консистентности. Казалось бы очевидно, но документация на это намекает не особо громко.
Disaster recovery: проверили по-настоящему
Настроить backup - это одно. Мы решили проверить, что восстановление вообще работает, пока мы это выбрали сами, а не когда придётся. Взяли отдельный тестовый кластер, намеренно уронили etcd - удалили data directory на всех членах кластера, после чего кластер ожидаемо перестал функционировать, kube-apiserver сыпал ошибками.
Восстановление по шагам:
Первое - восстановление каждого member-а из snapshot. etcdctl snapshot restore создаёт новый data directory. Для каждого member-а запускаем с его индивидуальными параметрами - --name, --initial-advertise-peer-urls, --initial-cluster. Это важный момент: восстановление делается на каждой ноде отдельно из одного и того же snapshot-файла, с разными параметрами идентификации. Не копируем data directory с одной ноды на другие.
Второе - запуск etcd. После восстановления запускаем etcd на каждой ноде, кластер формирует кворум, начинает выбирать лидера.
Третье - перезапуск kube-apiserver. После того как etcd доступен и отвечает, apiserver поднимается сам. Следом поднимаются controller-manager и scheduler. Kubelet на воркер-нодах пинает apiserver, тот отвечает - ноды начинают возвращаться в Ready.
Итоговое время от «etcd data directory удалён» до «кластер полностью функционирует, все поды запущены» - около четырёх минут. Это для трёх-нодового кластера с небольшим количеством объектов в etcd. Большая часть времени - ожидание пока поднимутся system-поды вроде CoreDNS и kube-proxy, а не само восстановление etcd.
Хочется оговориться: мы не восстанавливали с нуля виртуальные машины, они уже были живые. Реальный disaster требует сначала поднять инфраструктуру, потом восстанавливать etcd. Это отдельный сценарий, который мы ещё не прогоняли от и до - он включает Terraform и provisioning, что удлиняет процесс на неизвестное время.
Пара вещей, которые выяснились
Snapshot не включает секреты в открытом виде. Если в кластере включено encryption at rest для etcd (EncryptionConfiguration в apiserver), данные в etcd зашифрованы, и snapshot тоже зашифрован. При восстановлении нужно, чтобы ключи шифрования были доступны apiserver-у. Это значит, что ключи шифрования должны жить в отдельном, более защищённом месте, а не только в etcd. У нас они хранятся в Vault - это дополнительная зависимость при восстановлении, которую нужно учитывать.
etcd 3.3 чуть менее говорлив при restore. В предыдущих версиях snapshot restore выдавал вывод, который требовал интерпретации - не всегда было понятно, ошибка это или просто информационное сообщение. В 3.3 это немного причесали, команда явно завершается с нулевым кодом при успехе. Мелочь, но в стрессовой ситуации восстановления это приятно.
Мониторинг самого etcd. В процессе настройки посмотрели на метрики etcd - там есть etcd_mvcc_db_total_size_in_bytes, etcd_server_leader_changes_seen_total, etcd_disk_wal_fsync_duration_seconds. Добавили их в Prometheus и сделали алерты на аномальные значения. Частая смена лидера или растущий размер базы без compaction - это симптомы, которые лучше замечать заранее.
Backup-ротация и автоматическая верификация snapshot-а - это следующее что хочется добавить. Сейчас Job просто проверяет что snapshot сохранился, но не проверяет что из него реально можно восстановиться. Полная проверка потребует тестового окружения, в котором периодически прогоняется restore - это уже отдельная история.