Pull to refresh
80.32

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

Reading time 7 min
Views 45K

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 после всех этих манипуляций ;-)

Tags:
Hubs:
+11
Comments 6
Comments Comments 6

Articles

Information

Website
aenix.io
Registered
Employees
2–10 employees
Location
Чехия
Representative
Andrei Kvapil