← Back to notes

Backup и Restore в Kubernetes:
от snapshot до доказанного RTO


25: Observability

«Backup job succeeded» ≠ «restore capability». Без проверки restore, без измеренного времени восстановления, без подтверждения читаемости данных — backup создаёт ложное чувство безопасности. kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#backing-up-an-etcd-cluster


Цепочка доказательств

Только прохождение всех пяти шагов позволяет утверждать: «Мы можем восстановить кластер».

Scope  Execution  Freshness  Restore Check  Recovery Claim
                                                   
                                                   └─ "Мы можем
  │         │           │             │                   восстановиться
  │         │           │             │                   за N минут"
                                   
                                   └─ Тестовый restore прошёл,
                                      данные читаемы
                      
                      └─ backup свежее RPO
           
           └─ backup job завершился успешно
  
  └─ Все необходимые компоненты включены в backup

Полный scope backup'а

Каждый элемент обязателен. Отсутствие любого из них делает restore невозможным или неполным.

1. etcd snapshot

Полный снимок состояния кластера — все объекты Kubernetes (Pod'ы, Service'ы, Deployment'ы, Secret'ы, ConfigMap'ы, RBAC, CRD и их instances).

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

Проверка целостности snapshot:

ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-20241215-120000.db --write-out=table

2. PKI / сертификаты (/etc/kubernetes/pki/)

CA-сертификат, сертификаты API server, kubelet, front-proxy, etcd peer/client сертификаты. Без них TLS handshake невозможен: компоненты кластера не смогут аутентифицироваться друг у друга, kubelet'ы не подключатся к API server.

tar -czf /backup/pki-$(date +%Y%m%d).tar.gz /etc/kubernetes/pki/

3. Encryption keys (EncryptionConfiguration)

Ключи, которыми зашифрованы Secret'ы в etcd. Путь — файл, указанный в --encryption-provider-config у API server (обычно /etc/kubernetes/enc/encryption-config.yaml).

Без encryption keys после restore etcd Secret'ы будут содержать зашифрованные данные, которые невозможно расшифровать. Все пароли, токены, TLS-сертификаты приложений из Secret'ов — потеряны. Это самая частая причина «успешного restore, который не работает».

4. Static manifests control plane (/etc/kubernetes/manifests/)

Манифесты kube-apiserver, kube-controller-manager, kube-scheduler, etcd как static Pod'ов. Без них kubelet не сможет запустить компоненты control plane.

5. kubeadm config / bootstrap artifacts

ClusterConfiguration, InitConfiguration, join tokens, bootstrap kubeconfig. Обычно хранится в ConfigMap kube-system/kubeadm-config:

kubectl get configmap kubeadm-config -n kube-system -o yaml > /backup/kubeadm-config.yaml

6. CNI и critical addon configs

Конфигурация сетевого плагина (Calico, Cilium, Flannel), CoreDNS, kube-proxy. После restore без CNI Pod'ы не получат сетевое подключение и DNS-резолюция не работает — кластер технически запущен, но неработоспособен.

7. Recovery runbook

Документированная пошаговая процедура восстановления. Без него bus factor = 1. Если единственный человек, знающий процедуру, недоступен во время инцидента — restore невозможен, даже если все backup'ы на месте.


Что НЕ входит в baseline scope


Связь с RPO и RTO

RPO определяет cadence backup'ов

RPO = 15 мин    backup каждые  15 мин    корректно
RPO = 15 мин    backup каждый час         НАРУШЕНИЕ BY DESIGN

Если RPO = 15 минут, а backup снимается раз в час — при аварии в 59-ю минуту после backup'а потеря данных = 59 минут. RPO нарушен в 4 раза, ещё до любой реальной аварии.

RTO определяет требования к скорости restore

RTO = 30 мин    измеренный restore < 30 мин        корректно
RTO = 30 мин    restore никогда не тестировался    бесполезно

Влияние на error budget

При SLO 99.95% месячный error budget = ~22 минуты. Один неудачный restore длительностью 45 минут исчерпывает бюджет двух месяцев целиком. Это превращает тестирование restore из «лучшей практики» в экономически обоснованное требование.


Recovery Evidence Maturity Model

Level 0  Нет backup'а
Level 1 │ Backup существует (job выполняется)
Level 2 │ Scope проверен (etcd + PKI + keys + manifests + CNI + runbook)
Level 3 │ Freshness мониторится (алерт при > RPO)
Level 4 │ Restore проверяется периодически          ← Baseline target
Level 5 │ RTO доказан, Secret'ы читаемы              Production target

Baseline target: Level 4. Периодическая (не реже раза в неделю) проверка restore в тестовом окружении.

Production target: Level 5. Измеренное время restore укладывается в RTO, данные (включая Secret'ы) читаемы.


Процедура restore из etcd snapshot

# 1. Подготовить чистую control plane ноду с kubeadm, kubelet, container runtime

# 2. Восстановить PKI
cp -r /backup/pki/ /etc/kubernetes/pki/
chmod -R 600 /etc/kubernetes/pki/

# 3. Восстановить encryption keys
cp /backup/encryption-config.yaml /etc/kubernetes/enc/encryption-config.yaml

# 4. Восстановить static manifests
cp /backup/manifests/*.yaml /etc/kubernetes/manifests/

# 5. Восстановить etcd из snapshot
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20241215-120000.db \
  --data-dir=/var/lib/etcd \
  --name=master-1 \
  --initial-cluster=master-1=https://192.168.1.10:2380 \
  --initial-advertise-peer-urls=https://192.168.1.10:2380

# Установить правильного владельца
chown -R etcd:etcd /var/lib/etcd

# 6. Запустить kubelet — static Pod'ы поднимут control plane
systemctl start kubelet

# 7. Проверить состояние
kubectl get nodes
kubectl get pods -A
kubectl get secrets -A  # Критично: проверить читаемость Secret'ов

# 8. Восстановить CNI
kubectl apply -f /backup/cni-config.yaml

# 9. Подключить worker-ноды (если нужны новые bootstrap tokens)
kubeadm token create --print-join-command

Проверка читаемости Secret'ов — обязательный шаг. Если encryption keys не были включены в backup или восстановлены некорректно, kubectl get secret <name> -o jsonpath='{.data}' вернёт нечитаемые данные.


Velero: backup данных приложений

Velero бэкапит PersistentVolume данные (через CSI snapshots) и Kubernetes-объекты. velero.io/docs/latest/

# Установка Velero (пример для AWS S3; см. актуальную версию плагина на github.com/vmware-tanzu/velero-plugin-for-aws)
velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.14.0 \
  --bucket my-velero-bucket \
  --backup-location-config region=us-east-1 \
  --snapshot-location-config region=us-east-1 \
  --use-node-agent

# Создать backup namespace с PV snapshots
velero backup create prod-backup \
  --include-namespaces production \
  --snapshot-volumes \
  --wait

# Проверить состояние backup
velero backup describe prod-backup

# Restore namespace
velero restore create --from-backup prod-backup \
  --include-namespaces production \
  --wait

# Проверить restore
velero restore describe <restore-name>

Velero и etcd snapshot — дополняющие, а не взаимозаменяемые инструменты. Velero не заменяет etcd snapshot для state control plane; etcd snapshot не покрывает данные в PersistentVolume.


Observability-сигналы для backup/restore

Пример CronJob для periodic restore check и обновления метрик:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: restore-check
  namespace: kube-system
spec:
  schedule: "0 2 * * *"  # Каждую ночь в 02:00
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: restore-checker
            image: bitnami/etcd:3.5
            command:
            - /bin/sh
            - -c
            - |
              START=$(date +%s)
              etcdctl snapshot restore /backup/latest.db \
                --data-dir=/tmp/etcd-test
              END=$(date +%s)
              DURATION=$((END - START))
              # Push metrics to Pushgateway
              echo "restore_check_duration_seconds $DURATION" | \
                curl --data-binary @- http://pushgateway:9091/metrics/job/restore-check
              echo "restore_check_last_success $(date +%s)" | \
                curl --data-binary @- http://pushgateway:9091/metrics/job/restore-check
          restartPolicy: OnFailure

Когда использовать

  • Production кластер с любыми stateful workloads — etcd snapshot обязателен. Без него кластер не восстановим.
  • RPO < 1 час — автоматизированный backup через CronJob или Velero schedule. Ручной backup не масштабируется.
  • Multi-master кластер — snapshot нужно снимать с одного члена etcd кластера; остальные члены не нужно snapshot'ировать отдельно (snapshot содержит полное состояние).
  • Managed Kubernetes (EKS, GKE, AKS) — провайдер управляет etcd backup'ом control plane. Но PersistentVolume данные приложений — всё равно ответственность команды. Velero или CSI snapshots обязательны.
  • Dev/staging кластеры — backup опционален, если кластер воспроизводим через IaC за < 30 минут.

Типичные ошибки

1. Encryption keys не включены в backup Самая частая причина «успешного restore, который не работает». После restore из etcd snapshot все Secret'ы содержат зашифрованные данные, которые невозможно расшифровать без ключей. Все application credentials потеряны. Решение: encryption keys — часть backup scope, хранить в отдельном безопасном месте (HSM, Vault).

2. Snapshot снимается с работающего etcd без --cacert/--cert/--key Команда etcdctl snapshot save без TLS-сертификатов работает только если etcd настроен без TLS (что само по себе проблема безопасности). В kubeadm-кластерах etcd всегда использует mTLS.

3. Restore никогда не тестировался Level 1 в maturity model. При реальной аварии обнаруживается corruption snapshot, несовместимость версий etcd, или отсутствие encryption keys. Правило: тестовый restore — еженедельно в изолированном окружении.

4. Velero backup без --snapshot-volumes Velero по умолчанию не делает снимки PersistentVolume. Backup содержит только Kubernetes-объекты (манифесты), но не данные баз данных. После restore PVC пустые.

5. Один backup destination без offsite копии Backup хранится на той же ноде, что и etcd. При отказе ноды теряются и данные, и backup. Правило: backup хранится минимум в двух местах, одно из которых offsite (S3, GCS, другой datacenter).

6. kubeadm-config ConfigMap не включён в backup После restore кластера без kubeadm-config невозможно корректно добавить ноды или провести upgrade. Решение: kubectl get cm kubeadm-config -n kube-system -o yaml — часть backup процедуры.


Альтернативы

Velero Backup и restore Kubernetes-объектов и PersistentVolume данных. Интегрируется с CSI snapshot API (k8s 1.20+). Поддерживает schedule, retention, namespace mapping при restore. Стандарт для application-level backup. Не заменяет etcd snapshot для control plane state. velero.io

etcdctl + CronJob Нативный подход без зависимостей. Минус: только etcd state, без PV данных. Плюс: минимум зависимостей, максимум контроля. Подходит для control plane backup.

CSI Volume Snapshots Стандартный Kubernetes API для снимков PersistentVolume. Работает на уровне storage driver. kubernetes.io/docs/concepts/storage/volume-snapshots/ Требует CSI driver с поддержкой снимков (большинство cloud CSI drivers поддерживают).

Kasten K10 (Veeam) Enterprise-grade backup для Kubernetes. Policy-based, с поддержкой compliance, encryption, multi-cloud. Дороже Velero, но значительно богаче по функциональности для enterprise требований.

Cluster Backup через GitOps (Flux/ArgoCD) Если весь state кластера задекларирован в Git (GitOps-подход), то «restore» — это пересоздание кластера и apply Git state. Не покрывает PV данные, но для stateless workloads может быть достаточно. etcd snapshot всё равно нужен для CRD instances и объектов, не управляемых через GitOps.


27: PDB и обновления

Backup и Restore в Kubernetes: от snapshot до доказанного RTO | Aleksandr Suprun