Ломаем и чиним etcd-кластер

    etcd — это быстрая, надёжная и устойчивая к сбоям key-value база данных. Она лежит в основе Kubernetes и является неотъемлемой частью его control-plane, именно поэтому критически важно уметь бэкапить и восстанавливать работоспособность как отдельных нод, так и всего etcd-кластера.

    В предыдущей статье мы подробно рассмотрели перегенерацию SSL-сертификатов и static-манифестов для Kubernetes, а также вопросы связанные c восстановлением работоспособности Kubernetes. Эта статья будет посвящена целиком и полностью восстановлению etcd-кластера.


    Для начала я сразу должен сделать оговорку, что рассматривать мы будем лишь определённый кейс, когда etcd задеплоен и используется непосредственно в составе Kubernetes. Приведённые в статье примеры подразумевают что ваш etcd-кластер развёрнут с помощью static-манифестов и запускается внутри контейнеров.

    Для наглядности возьмём схему stacked control plane nodes из предыдущей статьи:

    (стрелочки указывают на связи клиент --> сервер)
    (стрелочки указывают на связи клиент --> сервер)

    Предложенные ниже команды можно выполнить и с помощью kubectl, но в данном случае мы постараемся абстрагироваться от плоскости управления Kubernetes и рассмотрим локальный вариант управления контейнеризированным etcd-кластером с помощью crictl.

    Данное умение также поможет вам починить etcd даже в случае неработающего Kubernetes API.

    Подготовка

    Поэтому, первое что мы сделаем, это зайдём по ssh на одну из master-нод и найдём наш etcd-контейнер:

    CONTAINER_ID=$(crictl ps -a --label io.kubernetes.container.name=etcd --label io.kubernetes.pod.namespace=kube-system | awk 'NR>1{r=$1} $0~/Running/{exit} END{print r}')

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

    alias etcdctl='crictl exec "$CONTAINER_ID" etcdctl --cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key --cacert /etc/kubernetes/pki/etcd/ca.crt'

    Приведённые выше команды являются временными, вам потребуется выполнять их каждый раз перед тем как начать работать с etcd. Конечно, для своего удобства вы можете добавить их в .bashrc. Однако это уже выходит за рамки этой статьи.

    Если что-то пошло не так и вы не можете сделать exec в запущенный контейнер, посмотрите логи etcd:

    crictl logs "$CONTAINER_ID"

    А также убедитесь в наличии static-манифеста и всех сертификатов в случае если контейнер даже не создался. Логи kubelet так же бывают весьма полезными.

    Проверка состояния кластера

    Здесь всё просто:

    # etcdctl member list -w table
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    |        ID        | STATUS  | NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    | 409dce3eb8a3c713 | started | node1 | https://10.20.30.101:2380 | https://10.20.30.101:2379 |      false |
    | 74a6552ccfc541e5 | started | node2 | https://10.20.30.102:2380 | https://10.20.30.102:2379 |      false |
    | d70c1c10cb4db26c | started | node3 | https://10.20.30.103:2380 | https://10.20.30.103:2379 |      false |
    +------------------+---------+-------+---------------------------+---------------------------+------------+

    Каждый инстанс etcd знает всё о каждом. Информация о members хранится внутри самого etcd и поэтому любое изменение в ней будет также обновленно и на остальных инстансах кластера.

    Важное замечание, команда member list отображает только статус конфигурации, но не статус конкретного инстанса. Чтобы проверить статусы инстансов есть команда endpoint status, но она требует явного указания всех эндпоинтов кластера для проверки.

    ENDPOINTS=$(etcdctl member list | grep -o '[^ ]\+:2379' | paste -s -d,)
    etcdctl endpoint status --endpoints=$ENDPOINTS -w table

    в случае если какой-то из эндпоинтов окажется недоступным вы увидите такую ошибку:

    Failed to get the status of endpoint https://10.20.30.103:2379 (context deadline exceeded)

    Удаление неисправной ноды

    Иногда случается так что какая-то из нод вышла из строя. И вам нужно восстановить работоспособность etcd-кластера, как быть?

    Первым делом нам нужно удалить failed member:

    etcdctl member remove d70c1c10cb4db26c

    Прежде чем продолжить, давайте убедимся, что на упавшей ноде под с etcd больше не запущен, а нода больше не содержит никаких данных:

    rm -rf /etc/kubernetes/manifests/etcd.yaml /var/lib/etcd/
    crictl rm "$CONTAINER_ID"

    Команды выше удалят static-pod для etcd и дирректорию с данными /var/lib/etcd на ноде.

    Разумеется в качестве альтернативы вы также можете воспользоваться командой kubeadm reset, которая удалит все Kubernetes-related ресурсы и сертификаты с вашей ноды.

    Добавление новой ноды

    Теперь у нас есть два пути:

    В первом случае мы можем просто добавить новую control-plane ноду используя стандартный kubeadm join механизм:

    kubeadm init phase upload-certs --upload-certs
    kubeadm token create --print-join-command --certificate-key <certificate_key>

    Вышеприведённые команды сгенерируют команду для джойна новой control-plane ноды в Kubernetes. Этот кейс довольно подробно описан в официальной документации Kubernetes и не нуждается в разъяснении.

    Этот вариант наиболее удобен тогда, когда вы деплоите новую ноду с нуля или после выполнения kubeadm reset

    Второй вариант более аккуратный, так как позволяет рассмотреть и выполнить изменения необходимые только для etcd не затрагивая при этом другие контейнеры на ноде.

    Для начала убедимся что наша нода имеет валидный CA-сертификат для etcd:

    /etc/kubernetes/pki/etcd/ca.{key,crt}

    В случае его отсутсвия скопируейте его с других нод вашего кластера. Теперь сгенерируем остальные сертификаты для нашей ноды:

    kubeadm init phase certs etcd-healthcheck-client
    kubeadm init phase certs etcd-peer
    kubeadm init phase certs etcd-server

    и выполним присоединение к кластеру:

    kubeadm join phase control-plane-join etcd --control-plane

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

    1. Добавит новый member в существующий etcd-кластер:

      etcdctl member add node3 --endpoints=https://10.20.30.101:2380,https://10.20.30.102:2379 --peer-urls=https://10.20.30.103:2380
    2. Сгенерирует новый static-manifest для etcd /etc/kubernetes/manifests/etcd.yaml с опциями:

      --initial-cluster-state=existing
      --initial-cluster=node1=https://10.20.30.101:2380,node2=https://10.20.30.102:2380,node3=https://10.20.30.103:2380

      эти опции позволят нашей ноде автоматически добавиться в существующий etcd-кластер.

    Создание снапшота etcd

    Теперь рассмотрим вариант создания и восстановления etcd из резервной копии.

    Создать бэкап можно довольно просто, выполнив на любой из нод:

    etcdctl snapshot save /var/lib/etcd/snap1.db

    Обратите внимание я намеренно использую /var/lib/etcd так как эта директория уже прокинута в etcd контейнер (смотрим в static-манифест /etc/kubernetes/manifests/etcd.yaml)

    После выполнения этой команды по указанному пути вы найдёте снапшот с вашими данными, давайте сохраним его в надёжном месте и рассмотрим процедуру восстановления из бэкапа

    Восстановление etcd из снапшота

    Здесь мы рассмотрим кейс когда всё пошло не так и нам потребовалось восстановить кластер из резервной копии.

    У нас есть снапшот snap1.db сделанный на предыдущем этапе. Теперь давайте полностью удалим static-pod для etcd и данные со всех наших нод:

    rm -rf /etc/kubernetes/manifests/etcd.yaml /var/lib/etcd/member/
    crictl rm "$CONTAINER_ID"

    Теперь у нас снова есть два пути:

    Вариант первый создать etcd-кластер из одной ноды и присоединить к нему остальные ноды, по описанной выше процедуре.

    kubeadm init phase etcd local

    эта команда сгенерирует статик-манифест для etcd c опциями:

    --initial-cluster-state=new
    --initial-cluster=node1=https://10.20.30.101:2380

    таким образом мы получим девственно чистый etcd на одной ноде.

    # etcdctl member list -w table
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    |        ID        | STATUS  | NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    | 1afbe05ae8b5fbbe | started | node1 | https://10.20.30.101:2380 | https://10.20.30.101:2379 |      false |
    +------------------+---------+-------+---------------------------+---------------------------+------------+

    Восстановим бэкап на первой ноде:

    etcdctl snapshot restore /var/lib/etcd/snap1.db \
      --data-dir=/var/lib/etcd/new
      --name=node1 \
      --initial-advertise-peer-urls=https://10.20.30.101:2380 \
      --initial-cluster=node1=https://10.20.30.101:2380
    
    mv /var/lib/etcd/member /var/lib/etcd/member.old
    mv /var/lib/etcd/new/member /var/lib/etcd/member
    crictl rm "$CONTAINER_ID"
    rm -rf /var/lib/etcd/member.old/ /var/lib/etcd/new/

    На остальных нодах выполним присоединение к кластеру:

    kubeadm join phase control-plane-join etcd --control-plane

    Вариант второй: восстановить бэкап сразу на всех нодах кластера. Для этого копируем файл снапшота на остальные ноды, и выполняем восстановление вышеописанным образом. В данном случае в опциях к etcdctl нам потребуется указать сразу все ноды нашего кластера, к примеру

    для node1:

    etcdctl snapshot restore /var/lib/etcd/snap1.db \
      --data-dir=/var/lib/etcd/new \
      --name=node1 \
      --initial-advertise-peer-urls=https://10.20.30.101:2380 \
      --initial-cluster=node1=https://10.20.30.101:2380,node2=https://10.20.30.102:2380,node3=https://10.20.30.103:2380

    для node2:

    etcdctl snapshot restore /var/lib/etcd/snap1.db \
      --data-dir=/var/lib/etcd/new \
      --name=node2 \
      --initial-advertise-peer-urls=https://10.20.30.102:2380 \
      --initial-cluster=node1=https://10.20.30.101:2380,node2=https://10.20.30.102:2380,node3=https://10.20.30.103:2380

    для node3:

    etcdctl snapshot restore /var/lib/etcd/snap1.db \
      --data-dir=/var/lib/etcd/new \
      --name=node3 \
      --initial-advertise-peer-urls=https://10.20.30.103:2380 \
      --initial-cluster=node1=https://10.20.30.101:2380,node2=https://10.20.30.102:2380,node3=https://10.20.30.103:2380

    Потеря кворума

    Иногда случается так что мы потеряли большинство нод из кластера и etcd перешёл в неработоспособное состояние. Удалить или добавить новых мемберов у вас не получится, как и создать снапшот.

    Выход из этой ситуации есть. Нужно отредактировать файл static-манифеста и добавить ключ --force-new-cluster к etcd:

    /etc/kubernetes/manifests/etcd.yaml

    после чего инстанс etcd перезапустится в кластере с единственным экземпляром:

    # etcdctl member list -w table
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    |        ID        | STATUS  | NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
    +------------------+---------+-------+---------------------------+---------------------------+------------+
    | 1afbe05ae8b5fbbe | started | node1 | https://10.20.30.101:2380 | https://10.20.30.101:2379 |      false |
    +------------------+---------+-------+---------------------------+---------------------------+------------+

    Теперь вам нужно очистить и добавить остальные ноды в кластер по описанной выше процедуре. Только не забудьте удалить ключ --force-new-cluster после всех этих манипуляций ;-)

    Комментарии 5

      +2
      «etcd — это быстрая, надёжная и устойчивая к сбоям key-value база данных. Она лежит в основе Kubernetes и является неотъемлемой частью его control-plane. Именно поэтому критически важно уметь бэкапить и восстанавливать работоспособность как отдельных нод, так и всего etcd-кластера.» просто поражает своей логикой. База настолько надёжна и устойчива к сбоям, что критически важно уметь её восстанавливать вручную!
        0

        Имелось ввиду скорее второе утверждение чем первое, что etcd лежит в основе Kubernetes.
        Исправил пунктуацию, спасибо :)

        0
        Что-то в итоге ничего и не сломали.
        Думал будет про failed proposals, частые re-elect, потерю кворума (и влияние на кубер в таком случае).
        Кстати, а что произойдет с кластером, если мы восстановим бекап etcd?
        Он резко постарается привести себя в то состояние, в котором был и начнет шедулить 100500 недостающих подов?
          +2

          Дельное замечание, про потерю кворума дополнил.


          Кстати, а что произойдет с кластером, если мы восстановим бекап etcd?

          Ничего не произойдёт. Kube-apiserver спроектирован так, чтобы по возможности не хранить никакого состояния, поэтому он просто подхватит изменения в etcd. Это работает даже без перезапуска apiserver.


          Он резко постарается привести себя в то состояние, в котором был и начнет шедулить 100500 недостающих подов?

          Это зависит не от apiserver, а скорее от controller-manager и scheduler. После того как вы востановите бэкап, все ваши ноды будут находиться в том же состоянии на момент которого был сделан бэкап. То есть если они были в Ready, то такими они и останутся до поры до времени. И только спустя 30 секунд (таймаут по умолчанию), если кубелеты не сообщили о своём состоянии аписерверу, controller-manager начнёт переводить их в NotReady и триггерить создание новых подов.
          Старые поды при этом останутся работать без изменений, то есть кубелеты самостоятельно не предпринимают никаких действий по удалению подов в случае невозможности связаться с apiserver.


          про failed proposals, частые re-elect

          Про это мне сказать пока что нечего, если кому есть чем дополнить статью, милости прошу в комментарии :)

          0

          Круто пишет автор.
          Немного вскипел мозг при прочтении, пришлось вернутся к прошлой статье. Непонимаю зачем так мудрено делать. Народ кто не шарит просто не поймёт, мне надо было мозг жёстко перевернуть, я не говорю что это не рабочий способ, и не говорю что долго. За статью спасибо, она врятле поможет, хотя было бы время как говорится.
          Помню лет 5 назад поднимал, где то уткнулся. Спасибо понял ошибку.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое