Ломаем и чиним Kubernetes

bmsp06ik_pa4dlp57vyac8ozyck.png

Kubernetes отличная платформа как для оркестрации контейнеров так и для всего остального. За последнее время Kubernetes ушёл далеко вперёд как по части функциональности так и по вопросам безопасности и отказоустойчивости. Архитектура Kubernetes позволяет с лёгкостью переживать сбои различного характера и всегда оставаться на плаву.

Сегодня мы будем ломать кластер, удалять сертификаты, вживую реджойнить ноды и всё это, по возможности, без даунтайма для уже запущенных сервисов.

5i9tog3dbgiupgljl-zty_wmbpc.png

Итак приступим. Основной control-plane Kubernetes состоит всего из нескольких компонентов:

  • etcd — используется в качестве базы данных

  • kube-apiserver — API и сердце нашего кластера

  • kube-controller-manager — производит операции над Kubernetes-ресурсами

  • kube-scheduller — основной шедуллер

  • kubelet’ы — которые непосредственно и запускают контейнеры на хостах

Каждый из этих компонентов защищён набором TLS-сертификатов, клиентских и серверных, которые используются для аутентификации и авторизации компонентов между ссобой. Они не хранятся где-либо в базе данных Kuberentes, за исключением определенных случаев, а представлены в виде обычных файлов:

# tree /etc/kubernetes/pki/
/etc/kubernetes/pki/
├── apiserver.crt
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
├── apiserver.key
├── apiserver-kubelet-client.crt
├── apiserver-kubelet-client.key
├── ca.crt
├── ca.key
├── CTNCA.pem
├── etcd
│   ├── ca.crt
│   ├── ca.key
│   ├── healthcheck-client.crt
│   ├── healthcheck-client.key
│   ├── peer.crt
│   ├── peer.key
│   ├── server.crt
│   └── server.key
├── front-proxy-ca.crt
├── front-proxy-ca.key
├── front-proxy-client.crt
├── front-proxy-client.key
├── sa.key
└── sa.pub

Сами компоненты описаны и запускаются на мастерах как static pods из директории /etc/kubernetes/manifests/

На этом месте не будем останавливаться подробно, т.к. это тема для отдельной статьи. В данном случае нас в первую очередь интересует как из этого всего добра получить рабочий кластер. Но для начала давайте немного абстрагируемся, и представим что у нас есть вышеперечисленные компоненты Kubernetes, которые как-то коммуницируют между ссобой.

Основная схема выглядит примерно так:

(стрелочки указывают на связи клиент --> сервер)» />(стрелочки указывают на связи клиент --> сервер)<p>Для коммуникации им нужны TLS-сертификаты, которые в принципе можно вынести на отдельный уровень абстракции и полностью довериться вашему инструменту деплоя, будь-то <strong>kubeadm</strong>, <strong>kubespray</strong> или что либо ещё. В этой статье мы разберём <strong>kubeadm</strong> т.к. это наиболее стандартный инструмент для развёртывания Kubernetes, а также он часто используется в составе других решений.</p><p>Предположим, что у нас уже есть задеплоенный кластер. Начнём с самого интересного: </p><pre><code>rm -rf /etc/kubernetes/</code></pre><p>На мастерах данная директория содержит: </p><ul><li><p>Набор сертификатов и CA для etcd (в <code>/etc/kubernetes/pki/etcd</code>)</p></li><li><p>Набор сертификатов и CA для Kubernetes (в <code>/etc/kubernetes/pki</code>)</p></li><li><p>Kubeconfig для cluster-admin, kube-controller-manager, kube-scheduller и kubelet (каждый из них также имеет закодированный в base64 CA-сертификат для нашего кластера <code>/etc/kubernetes/*.conf</code>)</p></li><li><p>Набор статик-манифеств для etcd, kube-apiserver, kube-scheduller и kube-controller-manager (в <code>/etc/kubernetes/manifests</code>)</p></li></ul><h2>Чиним control-plane</h2><p>Чтобы не было недоразумений, давайте также убедимся что все наши control-plane поды также остановлены: </p><pre><code>crictl rm `crictl ps -aq`</code></pre><blockquote><p><strong>Примечание</strong>: kubeadm по умолчанию не перезаписывает уже существующие сертификаты и кубеконфиги, для того чтобы их перевыпустить их необходимо сначала удалить вручную.</p></blockquote><p>Давайте начнём с восстановления etcd, так как если у нас был кворум (3 и более мастер-нод) etcd-кластер не запустится без присутствия большинства из них.</p><pre><code>kubeadm init phase certs etcd-ca</code></pre><p>— сгенерит новый CA для нашего etcd-кластера. Так как все остальные сертификаты должны быть им подписанны, скопируем его вместе с приватным ключём на остальные мастер-ноды: </p><pre><code>/etc/kubernetes/pki/etcd/ca.{key,crt}</code></pre><p>Теперь перегенерим остальные etcd-сертификаты и static-манифесты для него на всех control-plane нодах: </p><pre><code>kubeadm init phase certs etcd-healthcheck-client
kubeadm init phase certs etcd-peer
kubeadm init phase certs etcd-server
kubeadm init phase etcd local</code></pre><p>На этом этапе у нас уже должен подняться работоспособный etcd-кластер: </p><pre><code># crictl ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
ac82b4ed5d83a       0369cf4303ffd       2 seconds ago       Running             etcd                0                   bc8b4d568751b</code></pre><p>Теперь давайте проделаем тоже самое, но для для Kubernetes, <strong>на одной</strong> из master-нод выполним: </p><pre><code>kubeadm init phase certs all
kubeadm init phase kubeconfig all
kubeadm init phase control-plane all
cp -f /etc/kubernetes/admin.conf ~/.kube/config</code></pre><p>Вышеописанные команды сгенирируют все SSL-сертификаты нашего Kubernetes-кластера.</p><p>Если вы используете kubeadm для джойна кубелетов, вам также потребуется обновить конфиг cluster-info в kube-public неймспейсе т.к. он до сих пор содержит хэш вашего старого CA.</p><pre><code>kubeadm init phase bootstrap-token</code></pre><p>Так как все сертификаты на других инстансах также должны быть подписаны одним CA, скопируем его <strong>на остальные control-plane ноды</strong>, и повторим вышеописанные команды <strong>на каждой из них</strong>.</p><pre><code>/etc/kubernetes/pki/{ca,front-proxy-ca}.{key,crt}
/etc/kubernetes/pki/sa.{key,pub}</code></pre><p>Кстати, в качестве альтернативы ручного копирования сертификатов теперь вы можете использовать интерфейс Kubernetes, например следующая команда: </p><pre><code>kubeadm init phase upload-certs --upload-certs</code></pre><p>Зашифрует и загрузит сертификаты в Kubernetes на 2 часа, таким образом вы сможете сделать реджойн мастеров следующим образом: </p><pre><code>kubeadm join phase control-plane-prepare all kubernetes-apiserver:6443 --control-plane --token cs0etm.ua7fbmwuf1jz946l     --discovery-token-ca-cert-hash sha256:555f6ececd4721fed0269d27a5c7f1c6d7ef4614157a18e56ed9a1fd031a3ab8 --certificate-key 385655ee0ab98d2441ba8038b4e8d03184df1806733eac131511891d1096be73
kubeadm join phase control-plane-join all</code></pre><p>Стоит заметить, что в API Kubernetes есть ещё один конфиг, который хранит CA сертификат для front-proxy client, он используется для аутентификации запросов от apiserver в вебхуках и прочих aggregation layer сервисах. К счастью kube-apiserver обновляет его автоматически.</p><p>Однако возможно вы захотите почистить его от старых сертификатов вручную: </p><pre><code>kubectl get cm -n kube-system extension-apiserver-authentication -o yaml</code></pre><p>В любом случае на данном этапе мы уже имеем полностью рабочий control-plane.</p><h2>Чиним воркеров</h2><p>Эта компанда выведет список всех нод кластера, хотя сейчас все они будут в статусе <code>NotReady</code>: </p><pre><code>kubectl get node</code></pre><p>Это потому что они по прежнему используют старые сертификаты и с ожидают запросов apiserver, подписанных старым CA. Для того чтобы это исправить мы воспользуемся kubeadm, и сделаем реджойн нод в кластер.</p><p>Когда как мастера имеют доступ к CA и могут быть присоеденены локально: </p><pre><code>systemctl stop kubelet
rm -rf /var/lib/kubelet/pki/ /etc/kubernetes/kubelet.conf
kubeadm init phase kubeconfig kubelet
kubeadm init phase kubelet-start</code></pre><p>То для джойна воркеров мы сгенерируем новый токен: </p><pre><code>kubeadm token create --print-join-command</code></pre><p>и на каждом из них выполним: </p><pre><code>systemctl stop kubelet
rm -rf /var/lib/kubelet/pki/ /etc/kubernetes/pki/ /etc/kubernetes/kubelet.conf 
kubeadm join phase kubelet-start kubernetes-apiserver:6443  --token cs0etm.ua7fbmwuf1jz946l     --discovery-token-ca-cert-hash sha256:555f6ececd4721fed0269d27a5c7f1c6d7ef4614157a18e56ed9a1fd031a3ab8</code></pre><blockquote><p>Внимание, удалять директорию <code>/etc/kubernetes/pki/</code> на мастерах не нужно, так как она уже содержит все необходимые сертификаты.</p></blockquote><p>Вышеописанная процедура переподключит все ваши kubelet’ы обратно к кластеру, при этом никак не повлияет на уже запущенные на них контейнеры. Однако если у вас в кластере много нод и вы сделаете это неодновременно, у вас может возникнуть ситуация когда <strong>controller-manager</strong> начнёт пересоздавать контейнеры с NotReady-нод и пытаться их запустить на живых нодах кластера.</p><p>Чтобы это предотвратить мы можем временно остановить controller-manager, на мастерах: </p><pre><code>rm /etc/kubernetes/manifests/kube-controller-manager.yaml
crictl rmp `crictl ps --name kube-controller-manager -q`</code></pre><p>Последняя команда нужна просто для того, чтобы удостовериться что под с controller-manager действительно не запущен. Как только все ноды кластера будут присоединены мы можем сгенерировать static-manifest для controller-manager обратно.</p><p>Для этого на всех мастерах выполняем: </p><pre><code>kubeadm init phase control-plane controller-manager</code></pre><blockquote><p>Учтите что делать это нужно на этапе когда вы уже сгенерировали join token, в противном случае операция подключения зависнет на попытке прочитать токен из cluser-info.</p></blockquote><p>В случае если kubelet настроен на получение сертификата подписанного вашим CA (опция <code>serverTLSBootstrap: true</code>), вам также потребуется заново подтвердить csr от ваших kubelet’ов: </p><pre><code>kubectl get csr
kubectl certificate approve <csr></code></pre><h2>Чиним ServiceAccounts</h2><p>Есть ещё один момент. Так как мы потеряли <code>/etc/kubernetes/pki/sa.key</code> — это тот самый ключ которм были подписаны jwt-токены для всех наших ServiceAccounts, то мы должны пересоздать токены для каждого из них.</p><p>Сделать это можно достаточно просто, удалив все секреты типа <code>kubernetes.io/service-account-token</code>: </p><pre><code>kubectl get secret --all-namespaces | awk '/kubernetes.io\/service-account-token/ { print

После чего kube-controller-manager автоматически сгенерирует новые, подписанные новым ключём.

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

kubectl get pod --field-selector 'spec.serviceAccountName!=default' --no-headers --all-namespaces | awk '{print "kubectl delete pod -n " $1 " " $2}'

Например эта команда выведет список команд для удаления всех подов использующих недефолтный serviceAccount. Рекомендую начать с неймспейса kube-system, т.к. там могут быть установлен kube-proxy и CNI-плагин, жизненно необходимые для настройки коммуникации ваших микросервисов.

На этом восстановление кластера можно считать оконченным. Спасибо за внимание! В следующей статье мы подробнее рассмотрим бэкап и восстановление etcd-кластера.

© Habrahabr.ru