Резервное копирование etcd

Автоматическое резервное копирование

Deckhouse создаёт CronJob kube-system/d8-etcd-backup-*, который срабатывает в 00:00 по UTC+0. Резервная копия данных etcd сохраняется в архив /var/lib/etcd/etcd-backup.tar.gz на всех master-узлах.

Резервное копирование вручную с помощью Deckhouse CLI

В кластерах Deckhouse v1.65 и выше, резервную копию данных etcd можно создать одной командой d8 backup etcd:

d8 backup etcd --kubeconfig $KUBECONFIG ./etcd.db

Резервное копирование вручную с помощью etcdctl

Не рекомендуется использовать в версиях Deckhouse 1.65 и выше.

В кластерах Deckhouse версии v1.64 и ниже запустите следующий скрипт на любом master-узле от пользователя root:

#!/usr/bin/env bash
set -e

pod=etcd-`hostname`
d8 k -n kube-system exec "$pod" -- /usr/bin/etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/ca.crt --key /etc/kubernetes/pki/etcd/ca.key --endpoints https://127.0.0.1:2379/ snapshot save /var/lib/etcd/${pod##*/}.snapshot && \
mv /var/lib/etcd/"${pod##*/}.snapshot" etcd-backup.snapshot && \
cp -r /etc/kubernetes/ ./ && \
tar -cvzf kube-backup.tar.gz ./etcd-backup.snapshot ./kubernetes/
rm -r ./kubernetes ./etcd-backup.snapshot

В текущей директории будет создан файл kube-backup.tar.gz со снимком базы etcd одного из узлов etcd-кластера. Из полученного снимка можно будет восстановить состояние кластера etcd.

Также рекомендуется сделать бэкап директории /etc/kubernetes, в которой находятся:

Мы рекомендуем хранить резервные копии снимков состояния кластера etcd, а также бэкап директории /etc/kubernetes/ в зашифрованном виде вне кластера Deckhouse. Для этого вы можете использовать сторонние инструменты резервного копирования файлов, например Restic, Borg, Duplicity и т.д.

Полное восстановление состояния кластера из резервной копии etcd

Далее будут описаны шаги по восстановлению кластера до предыдущего состояния из резервной копии при полной потере данных.

Восстановление кластера с одним master-узлом

Для корректного восстановления кластера с одним master-узлом выполните следующие шаги:

  1. Загрузите утилиту etcdctl на сервер (желательно чтобы её версия была такая же, как и версия etcd в кластере).

     wget "https://github.com/etcd-io/etcd/releases/download/v3.5.4/etcd-v3.5.4-linux-amd64.tar.gz"
     tar -xzvf etcd-v3.5.4-linux-amd64.tar.gz && mv etcd-v3.5.4-linux-amd64/etcdctl /usr/local/bin/etcdctl
    

    Проверить версию etcd в кластере можно выполнив следующую команду:

    d8 k -n kube-system exec -ti etcd-$(hostname) -- etcdctl version
    
  2. Остановите etcd.

    Etcd запущен в виде статического пода, поэтому достаточно переместить файл манифеста:

    mv /etc/kubernetes/manifests/etcd.yaml ~/etcd.yaml
    
  3. Сохраните текущие данные etcd.

    cp -r /var/lib/etcd/member/ /var/lib/deckhouse-etcd-backup
    
  4. Очистите директорию etcd.

    rm -rf /var/lib/etcd/member/
    
  5. Поместите резервную копию etcd в файл ~/etcd-backup.snapshot.

  6. Восстановите базу данных etcd.

    ETCDCTL_API=3 etcdctl snapshot restore ~/etcd-backup.snapshot --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/ca.crt \
      --key /etc/kubernetes/pki/etcd/ca.key --endpoints https://127.0.0.1:2379/  --data-dir=/var/lib/etcd
    
  7. Запустите etcd.

    mv ~/etcd.yaml /etc/kubernetes/manifests/etcd.yaml
    

Восстановление мультимастерного кластера

Для корректного восстановления мультимастерного кластера выполните следующие шаги:

  1. Явно включите режим High Availability (HA) с помощью глобального параметра highAvailability. Это нужно, например, чтобы не потерять одну реплику Prometheus и её PVC, поскольку в режиме кластера с одним master-узлом HA отключен по умолчанию.

  2. Переведите кластер в режим с одним master-узлом, в соответствии с инструкцией для облачных кластеров или самостоятельно выведите статические master-узлы из кластера.

  3. На оставшемся единственном master-узле выполните шаги по восстановлению etcd из резервной копии в соответствии с инструкцией для кластера с одним master-узлом.

  4. Когда работа etcd будет восстановлена, удалите из кластера информацию об уже удаленных в п.1 master-узлах, воспользовавшись следующей командой (укажите название узла):

    d8 k delete node <MASTER_NODE_I>
    
  5. Перезапустите все узлы кластера.

  6. Дождитесь выполнения заданий из очереди Deckhouse:

    d8 k -n d8-system exec svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main
    
  7. Переведите кластер обратно в режим мультимастерного в соответствии с инструкцией для облачных кластеров или инструкцией для статических или гибридных кластеров.

Восстановление объекта Kubernetes из резервной копии etcd

Краткий сценарий восстановления отдельных объектов из резервной копии etcd:

  1. Получите резервную копию данных.
  2. Запустите временный экземпляр etcd.
  3. Наполните его данными из резервной копии.
  4. Получите описания нужных объектов с помощью утилиты etcdhelper.

Шаги по восстановлению объектов из резервной копии etcd

В примере:

  • etcd-snapshot.bin — файл с резервной копией данных etcd (snapshot);
  • infra-production — пространство имен, в котором нужно восстановить объекты.
  1. Запустите под с временным экземпляром etcd. Желательно, чтобы версия запускаемого экземпляра etcd совпадала с версией etcd, из которой создавалась резервная копия. Для простоты экземпляр запускается не локально, а в кластере, так как в кластере заведомо есть образ etcd.

    • Подготовьте файл etcd.pod.yaml с манифестом пода:

       cat <<EOF >etcd.pod.yaml 
       apiVersion: v1
       kind: Pod
       metadata:
         name: etcdrestore
         namespace: default
       spec:
         containers:
         - command:
           - /bin/sh
           - -c
           - "sleep 96h"
           image: IMAGE
           imagePullPolicy: IfNotPresent
           name: etcd
           volumeMounts:
           - name: etcddir
             mountPath: /default.etcd
         volumes:
         - name: etcddir
           emptyDir: {}
       EOF
      
    • Установите актуальное имя образа etcd:

       IMG=`kubectl -n kube-system get pod -l component=etcd -o jsonpath="{.items[0].spec.    containers[*].image}"`
       sed -i -e "s#IMAGE#$IMG#" etcd.pod.yaml
      
    • Создайте под:

      kubectl create -f etcd.pod.yaml
      
    • Скопируйте etcdhelper и снимок etcd в контейнер пода. etcdhelper можно собрать из исходного кода или скопировать из готового образа (например, из образа etcdhelper на Docker Hub).

      Пример:

      kubectl cp etcd-snapshot.bin default/etcdrestore:/tmp/etcd-snapshot.bin
      kubectl cp etcdhelper default/etcdrestore:/usr/bin/etcdhelper
      
    • В контейнере установите права на запуск etcdhelper, восстановите данные из резервной копии и запустите etcd.

      Пример:

      ~ # kubectl -n default exec -it etcdrestore -- sh
      / # chmod +x /usr/bin/etcdhelper
      / # etcdctl snapshot restore /tmp/etcd-snapshot.bin
      / # etcd &
      
    • Получите описания нужных объектов кластера, отфильтровав их с помощью grep.

      Пример:

      ~ # kubectl -n default exec -it etcdrestore -- sh
      / # mkdir /tmp/restored_yaml
      / # cd /tmp/restored_yaml
      /tmp/restored_yaml # for o in `etcdhelper -endpoint 127.0.0.1:2379 ls /registry/ | grep infra-production` ; do etcdhelper -endpoint 127.0.0.1:2379 get $o > `echo $o | sed -e "s#/registry/##g;s#/#_#g"`.yaml ; done
      

      Замена символов с помощью sed в примере позволяет сохранить описания объектов в файлы, именованные подобно структуре реестра etcd. Например: /registry/deployments/infra-production/supercronic.yamldeployments_infra-production_supercronic.yaml.

  2. Скопируйте полученные описания объектов из пода на master-узел командой:

    d8 k cp default/etcdrestore:/tmp/restored_yaml restored_yaml
    
  3. Удалите из полученных описаний объектов информацию о времени создания, UID, status и прочие оперативные данные, после чего восстановите объекты командой:

    d8 k create -f restored_yaml/deployments_infra-production_supercronic.yaml
    
  4. Под с временным экземпляром etcd можно удалить командой:

    d8 k delete -f etcd.pod.yaml
    

Как получить список узлов кластера etcd (вариант 1)

Используйте команду etcdctl member list.

Пример:

d8 k -n kube-system exec -ti $(d8 k -n kube-system get pod -l component=etcd,tier=control-plane -o name | head -n1) -- \
etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt \
--cert /etc/kubernetes/pki/etcd/ca.crt --key /etc/kubernetes/pki/etcd/ca.key \
--endpoints https://127.0.0.1:2379/ member list -w table

Внимание. Последний параметр в таблице вывода показывает, что узел кластера etcd находится в состоянии learner, а не в состоянии leader.

Как получить список узлов кластера etcd (вариант 2)

Используйте команду etcdctl endpoint status. Для этой команды, после флага --endpoints нужно подставить адрес каждого узла control-plane.

Значение true в пятом столбце вывода указывает на лидера.

Пример скрипта, который автоматически передает все адреса узлов control-plane:

MASTER_NODE_IPS=($(d8 k get nodes -l \
node-role.kubernetes.io/control-plane="" \
-o 'custom-columns=IP:.status.addresses[?(@.type=="InternalIP")].address' \
--no-headers))
unset ENDPOINTS_STRING
for master_node_ip in ${MASTER_NODE_IPS[@]}
do ENDPOINTS_STRING+="--endpoints https://${master_node_ip}:2379 "
done
d8 k -n kube-system exec -ti $(d8 k -n kube-system get pod \
-l component=etcd,tier=control-plane -o name | head -n1) \
-- etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt  --cert /etc/kubernetes/pki/etcd/ca.crt \
--key /etc/kubernetes/pki/etcd/ca.key \
$(echo -n $ENDPOINTS_STRING) endpoint status -w table

Как добавить master-узлы в облачном кластере

Далее описана конвертация кластера с одним master-узлом в мультимастерный кластер.

Перед добавлением узлов убедитесь в наличии необходимых квот.

Важно иметь нечетное количество master-узлов для обеспечения кворума.

  1. Сделайте резервную копию etcd и папки /etc/kubernetes.
  2. Скопируйте полученный архив за пределы кластера (например, на локальную машину).
  3. Убедитесь, что в кластере нет алертов, которые могут помешать обновлению.

    Чтобы получить список алертов в кластере, выполните команду:

     kubectl get clusteralerts
    

    Чтобы просмотреть конкретный алерт, выполните команду:

     kubectl get clusteralerts <ALERT_NAME> -o yaml
    
  4. Убедитесь, что очередь Deckhouse пуста.

    Чтобы просмотреть состояние всех очередей заданий Deckhouse, выполните команду:

     kubectl -n d8-system exec -it svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue list
    

    Пример вывода (очереди пусты):

     Summary:
     - 'main' queue: empty.
     - 88 other queues (0 active, 88 empty): 0 tasks.
     - no tasks to handle.
    

    Чтобы просмотреть состояние очереди заданий main Deckhouse, выполните команду:

     kubectl -n d8-system exec -it svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main
    

    Пример вывода (очередь main пуста):

     Queue 'main': length 0, status: 'waiting for task 0s'
    
  5. На локальной машине запустите контейнер установщика Deckhouse соответствующей редакции и версии (измените адрес container registry при необходимости):

    DH_VERSION=$(kubectl -n d8-system get deployment deckhouse -o jsonpath='{.metadata.annotations.core\.deckhouse\.io\/version}') \
    DH_EDITION=$(kubectl -n d8-system get deployment deckhouse -o jsonpath='{.metadata.annotations.core\.deckhouse\.io\/edition}' | tr '[:upper:]' '[:lower:]' ) \
    docker run --pull=always -it -v "$HOME/.ssh/:/tmp/.ssh/" \
      registry.deckhouse.io/deckhouse/${DH_EDITION}/install:${DH_VERSION} bash
    
  6. В контейнере с инсталлятором выполните следующую команду, чтобы проверить состояние перед началом работы:

    dhctl terraform check --ssh-agent-private-keys=/tmp/.ssh/<SSH_KEY_FILENAME> --ssh-user=<USERNAME> --ssh-host <MASTER-NODE-0-HOST>
    

    Ответ должен сообщить, что Terraform не нашел расхождений и изменений не требуется.

  7. В контейнере с инсталлятором выполните следующую команду и укажите требуемое количество master-узлов в параметре masterNodeGroup.replicas:

    dhctl config edit provider-cluster-configuration --ssh-agent-private-keys=/tmp/.ssh/<SSH_KEY_FILENAME> --ssh-user=<USERNAME> \
      --ssh-host <MASTER-NODE-0-HOST>
    

    Для Yandex Cloud, при использовании внешних адресов на master-узлах, количество элементов массива в параметре masterNodeGroup.instanceClass.externalIPAddresses должно равняться количеству master-узлов. При использовании значения Auto (автоматический заказ публичных IP-адресов), количество элементов в массиве все равно должно соответствовать количеству master-узлов.

    Например, при трех master-узлах (masterNodeGroup.replicas: 3) и автоматическом заказе адресов, параметр masterNodeGroup.instanceClass.externalIPAddresses будет выглядеть следующим образом:

    externalIPAddresses:
    - "Auto"
    - "Auto"
    - "Auto"
    
  8. В контейнере с инсталлятором выполните следующую команду для запуска масштабирования:

    dhctl converge --ssh-agent-private-keys=/tmp/.ssh/<SSH_KEY_FILENAME> --ssh-user=<USERNAME> --ssh-host <MASTER-NODE-0-HOST>
    
  9. Дождитесь появления необходимого количества master-узлов в статусе Ready и готовности всех экземпляров control-plane-manager:

    kubectl -n kube-system wait pod --timeout=10m --for=condition=ContainersReady -l app=d8-control-plane-manager
    

Как уменьшить число master-узлов в облачном кластере?

Описанные ниже шаги необходимо выполнять с первого по порядку master-узла кластера (master-0). Это связано с тем, что кластер всегда масштабируется по порядку: например, невозможно удалить узлы master-0 и master-1, оставив master-2.

  1. Сделайте резервную копию etcd и папки /etc/kubernetes.
  2. Скопируйте полученный архив за пределы кластера (например, на локальную машину).
  3. Убедитесь, что в кластере нет алертов, которые могут помешать обновлению master-узлов.

    Чтобы получить список алертов в кластере, выполните команду:

     kubectl get clusteralerts
    

    Чтобы просмотреть конкретный алерт, выполните команду:

     kubectl get clusteralerts <ALERT_NAME> -o yaml
    
  4. Убедитесь, что очередь Deckhouse пуста.

    Чтобы просмотреть состояние всех очередей заданий Deckhouse, выполните команду:

     kubectl -n d8-system exec -it svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue list
    

    Пример вывода (очереди пусты):

     Summary:
     - 'main' queue: empty.
     - 88 other queues (0 active, 88 empty): 0 tasks.
     - no tasks to handle.
    

    Чтобы просмотреть состояние очереди заданий main Deckhouse, выполните команду:

     kubectl -n d8-system exec -it svc/deckhouse-leader -c deckhouse -- deckhouse-controller queue main
    

    Пример вывода (очередь main пуста):

     Queue 'main': length 0, status: 'waiting for task 0s'
    
  5. На локальной машине запустите контейнер установщика Deckhouse соответствующей редакции и версии (измените адрес container registry при необходимости):

    DH_VERSION=$(kubectl -n d8-system get deployment deckhouse -o jsonpath='{.metadata.annotations.core\.deckhouse\.io\/version}') \
    DH_EDITION=$(kubectl -n d8-system get deployment deckhouse -o jsonpath='{.metadata.annotations.core\.deckhouse\.io\/edition}' | tr '[:upper:]' '[:lower:]' ) \
    docker run --pull=always -it -v "$HOME/.ssh/:/tmp/.ssh/" \
      registry.deckhouse.io/deckhouse/${DH_EDITION}/install:${DH_VERSION} bash
    
  6. В контейнере с инсталлятором выполните следующую команду и укажите 1 в параметре masterNodeGroup.replicas:

    dhctl config edit provider-cluster-configuration --ssh-agent-private-keys=/tmp/.ssh/<SSH_KEY_FILENAME> \
      --ssh-user=<USERNAME> --ssh-host <MASTER-NODE-0-HOST>
    

    Для Yandex Cloud при использовании внешних адресов на master-узлах количество элементов массива в параметре masterNodeGroup.instanceClass.externalIPAddresses должно равняться количеству master-узлов. При использовании значения Auto (автоматический заказ публичных IP-адресов) количество элементов в массиве все равно должно соответствовать количеству master-узлов.

    Например, при одном master-узле (masterNodeGroup.replicas: 1) и автоматическом заказе адресов параметр masterNodeGroup.instanceClass.externalIPAddresses будет выглядеть следующим образом:

    externalIPAddresses:
    - "Auto"
    
  7. Снимите следующие лейблы с удаляемых master-узлов:
    • node-role.kubernetes.io/control-plane
    • node-role.kubernetes.io/master
    • node.deckhouse.io/group

      Команда для снятия лейблов:

       kubectl label node <MASTER-NODE-N-NAME> node-role.kubernetes.io/control-plane- node-role.kubernetes.io/master- node.deckhouse.io/group-
      
  8. Убедитесь, что удаляемые master-узлы пропали из списка узлов кластера etcd:

    kubectl -n kube-system exec -ti $(kubectl -n kube-system get pod -l component=etcd,tier=control-plane -o name | head -n1) -- \
    etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt \
    --cert /etc/kubernetes/pki/etcd/ca.crt --key /etc/kubernetes/pki/etcd/ca.key \
    --endpoints https://127.0.0.1:2379/ member list -w table
    
  9. Выполните drain для удаляемых узлов:

    kubectl drain <MASTER-NODE-N-NAME> --ignore-daemonsets --delete-emptydir-data
    
  10. Выключите виртуальные машины, соответствующие удаляемым узлам, удалите инстансы соответствующих узлов из облака и подключенные к ним диски (kubernetes-data-master-<N>).

  11. Удалите в кластере поды, оставшиеся на удаленных узлах:

    kubectl delete pods --all-namespaces --field-selector spec.nodeName=<MASTER-NODE-N-NAME> --force
    
  12. Удалите в кластере объекты Node удаленных узлов:

    kubectl delete node <MASTER-NODE-N-NAME>
    
  13. В контейнере с инсталлятором выполните команду для запуска масштабирования:

    dhctl converge --ssh-agent-private-keys=/tmp/.ssh/<SSH_KEY_FILENAME> --ssh-user=<USERNAME> --ssh-host <MASTER-NODE-0-HOST>
    

Пересборка кластера etcd

Пересборка может потребоваться в случае, если etcd-кластер развалился, либо при миграции из мультимастерного кластера в кластер с одним master-узлом.

  1. Выберите узел, с которого начнётся восстановление кластера etcd. В случае миграции в кластер с одним master-узлом это узел, на котором должен остаться etcd.
  2. Остановите etcd на всех остальных узлах. Для этого удалите файл /etc/kubernetes/manifests/etcd.yaml.
  3. На оставшемся узле в манифесте /etc/kubernetes/manifests/etcd.yaml добавьте аргумент --force-new-cluster в поле spec.containers.command.
  4. После успешного запуска кластера удалите параметр --force-new-cluster.

Операция деструктивна, она полностью уничтожает консенсус и запускает etcd-кластер с состояния, которое сохранилось на выбранном узле. Любые pending-записи пропадут.

Устранение бесконечного рестарта

Данный вариант может понадобиться, если запуск с аргументом --force-new-cluster не восстанавливает работу etcd. Такое может случиться при неудачном converge master-узлов, когда новый master-узел был создан со старым диском etcd, поменял свой адрес из локальной сети, и другие master-узлы отсутствуют. Стоит использовать данный способ, если контейнер etcd в бесконечном рестарте, а в его логе ошибка: panic: unexpected removal of unknown remote peer.

  1. Установите утилиту etcdutl.
  2. С текущего локального снапшота базы etcd (/var/lib/etcd/member/snap/db) выполните создание нового снапшота:

    ./etcdutl snapshot restore /var/lib/etcd/member/snap/db --name <HOSTNAME> \
    --initial-cluster=HOSTNAME=https://<ADDRESS>:2380 --initial-advertise-peer-urls=https://ADDRESS:2380 \
    --skip-hash-check=true --data-dir /var/lib/etcdtest
    

    где:

    • <HOSTNAME> — название master-узла;
    • <ADDRESS> — адрес master-узла.
  3. Выполните команды для использования нового снапшота:

    cp -r /var/lib/etcd /tmp/etcd-backup
    rm -rf /var/lib/etcd
    mv /var/lib/etcdtest /var/lib/etcd
    
  4. Найдите контейнеры etcd и kube-apiserver:

    crictl ps -a --name "^etcd|^kube-apiserver"
    
  5. Удалите найденные контейнеры etcd и kube-apiserver:

    crictl rm <CONTAINER-ID>
    
  6. Перезапустите master-узел.