
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
Для понимания, вышеописанная команда сделает следующее:
Добавит новый 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
Сгенерирует новый 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
после всех этих манипуляций ;-)