
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 после всех этих манипуляций ;-) me
rotabor
«etcd — это быстрая, надёжная и устойчивая к сбоям key-value база данных. Она лежит в основе Kubernetes и является неотъемлемой частью его control-plane. Именно поэтому критически важно уметь бэкапить и восстанавливать работоспособность как отдельных нод, так и всего etcd-кластера.» просто поражает своей логикой. База настолько надёжна и устойчива к сбоям, что критически важно уметь её восстанавливать вручную!
kvaps Автор
Имелось ввиду скорее второе утверждение чем первое, что etcd лежит в основе Kubernetes.
Исправил пунктуацию, спасибо :)