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

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

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


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

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

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

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

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

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

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

    # 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, которые как-то коммуницируют между собой.

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

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

    Для коммуникации им нужны TLS-сертификаты, которые в принципе можно вынести на отдельный уровень абстракции и полностью довериться вашему инструменту деплоя, будь-то kubeadm, kubespray или что либо ещё. В этой статье мы разберём kubeadm т.к. это наиболее стандартный инструмент для развёртывания Kubernetes, а также он часто используется в составе других решений.


    Допустим, у нас уже есть задеплоенный кластер. Начнём с самого интересного:

    rm -rf /etc/kubernetes/

    На мастерах данная директория содержит:

    • Набор сертификатов и CA для etcd (в /etc/kubernetes/pki/etcd)

    • Набор сертификатов и CA для Kubernetes (в /etc/kubernetes/pki)

    • Kubeconfig для cluster-admin, kube-controller-manager, kube-scheduler и kubelet (каждый из них также имеет закодированный в base64 CA-сертификат для нашего кластера /etc/kubernetes/*.conf)

    • Набор статик-манифестов для etcd, kube-apiserver, kube-scheduler и kube-controller-manager (в /etc/kubernetes/manifests)

    Предположим, что мы потеряли всё и сразу

    Чиним control-plane

    Чтобы не было недоразумений, давайте также убедимся что все наши control-plane поды также остановлены:

    crictl rm `crictl ps -aq`

    Примечание: kubeadm по умолчанию не перезаписывает уже существующие сертификаты и кубеконфиги, для того чтобы их перевыпустить их необходимо сначала удалить вручную.

    Давайте начнём с восстановления etcd, так как если у нас был кворум (3 и более мастер-нод) etcd-кластер не запустится без присутствия большинства из них.

    kubeadm init phase certs etcd-ca

    Команда выше сгенерит новый CA для нашего etcd-кластера. Так как все остальные сертификаты должны быть им подписаны, скопируем его вместе с приватным ключом на остальные мастер-ноды:

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

    Теперь перегенерим остальные etcd-сертификаты и static-манифесты для него на всех control-plane нодах:

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

    На этом этапе у нас уже должен подняться работоспособный etcd-кластер:

    # crictl ps
    CONTAINER ID        IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID
    ac82b4ed5d83a       0369cf4303ffd       2 seconds ago       Running             etcd                0                   bc8b4d568751b

    Теперь давайте проделаем тоже самое, но для для Kubernetes, на одной из master-нод выполним:

    kubeadm init phase certs all
    kubeadm init phase kubeconfig all
    kubeadm init phase control-plane all
    cp -f /etc/kubernetes/admin.conf ~/.kube/config

    Вышеописанные команды сгенерируют все SSL-сертификаты для нашего Kubernetes-кластера, а также статик под манифесты и кубеконфиги для сервисов Kubernetes.

    Если вы используете kubeadm для джойна кубелетов, вам также потребуется обновить конфиг cluster-info в kube-public неймспейсе т.к. он до сих пор содержит хэш вашего старого CA.

    kubeadm init phase bootstrap-token

    Так как все сертификаты на других инстансах также должны быть подписаны одним CA, скопируем его на остальные control-plane ноды, и повторим вышеописанные команды на каждой из них.

    /etc/kubernetes/pki/{ca,front-proxy-ca}.{key,crt}
    /etc/kubernetes/pki/sa.{key,pub}

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

    kubeadm init phase upload-certs --upload-certs

    Зашифрует и загрузит сертификаты в Kubernetes на 2 часа, таким образом вы сможете сделать реджойн мастеров следующим образом:

    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

    Стоит заметить, что в API Kubernetes есть ещё один конфиг, который хранит CA сертификат для front-proxy client, он используется для аутентификации запросов от apiserver в вебхуках и прочих aggregation layer сервисах. К счастью kube-apiserver обновляет его автоматически.

    Однако возможно вы захотите почистить его от старых сертификатов вручную:

    kubectl get cm -n kube-system extension-apiserver-authentication -o yaml

    В любом случае на данном этапе мы уже имеем полностью рабочий control-plane.

    Чиним воркеры

    Эта команда выведет список всех нод кластера, хотя сейчас все они будут в статусе NotReady:

    kubectl get node

    Это потому что они по прежнему используют старые сертификаты и с ожидают запросов apiserver, подписаных старым CA. Для того чтобы это исправить мы воспользуемся kubeadm, и сделаем реджойн нод в кластер.

    Когда как мастера имеют доступ к CA и могут быть присоеденены локально:

    systemctl stop kubelet
    rm -rf /var/lib/kubelet/pki/ /etc/kubernetes/kubelet.conf
    kubeadm init phase kubeconfig kubelet
    kubeadm init phase kubelet-start

    То для джойна воркеров мы сгенерируем новый токен:

    kubeadm token create --print-join-command

    и на каждом из них выполним:

    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

    Внимание, удалять директорию /etc/kubernetes/pki/ на мастерах не нужно, так как она уже содержит все необходимые сертификаты.

    Вышеописанная процедура переподключит все ваши kubelet'ы обратно к кластеру, при этом никак не повлияет на уже запущенные на них контейнеры. Однако если у вас в кластере много нод и вы сделаете это неодновременно, у вас может возникнуть ситуация когда controller-manager начнёт пересоздавать контейнеры с NotReady-нод и пытаться их запустить на живых нодах кластера.

    Чтобы это предотвратить мы можем временно остановить controller-manager, на мастерах:

    rm /etc/kubernetes/manifests/kube-controller-manager.yaml
    crictl rmp `crictl ps --name kube-controller-manager -q`

    Последняя команда нужна просто для того, чтобы удостовериться что под с controller-manager действительно не запущен. Как только все ноды кластера будут присоединены мы можем сгенерировать static-manifest для controller-manager обратно.

    Для этого на всех мастерах выполняем:

    kubeadm init phase control-plane controller-manager

    Учтите что делать это нужно на этапе когда вы уже сгенерировали join token, в противном случае операция подключения зависнет на попытке прочитать токен из cluster-info.

    В случае если kubelet настроен на получение сертификата подписанного вашим CA (опция serverTLSBootstrap: true), вам также потребуется заново подтвердить csr от ваших kubelet'ов:

    kubectl get csr
    kubectl certificate approve <csr>

    Чиним ServiceAccounts

    Есть ещё один момент. Так как мы потеряли /etc/kubernetes/pki/sa.key - это тот самый ключ которым были подписаны jwt-токены для всех наших ServiceAccounts, то мы должны пересоздать токены для каждого из них.

    Сделать это можно достаточно просто, удалив поле token изо всех секреты типа kubernetes.io/service-account-token:

    kubectl get secret --all-namespaces | awk '/kubernetes.io\/service-account-token/ { print "kubectl patch secret -n " $1 " " $2 " -p {\\\"data\\\":{\\\"token\\\":null}}"}' | sh -x

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

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

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

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

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

    Средняя зарплата в IT

    120 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 3 754 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

      +1

      Спасибо! Все как всегда по полочкам!

        0

        Подскажите, почему говорят не "управление" а "оркестрация". В чем разница?

          +2

          Хороший вопрос. Думаю разницы в терминах особой нет, но попробую выразить своё субъективное мнение на этот счёт.


          Формально управлением контейнеров занимается docker, containerd или другой CRI-демон. Kubernetes — грубо говоря, это плоскость управления для таких вот CRI, со своим API и шедуллером.


          То есть вы сообщаете Kubernetes что конкретно должно быть запущенно, с какими параметрами и в скольких экземплярах. Он находит свободные ноды и и даёт команду на запуск контейнеров на них. Чем не оркестрация?

            0

            Мне трудно сказать есть ли эта разница в русском языке.
            Но оркестрация в computing, если можно так сказать, это скорее мета-управление. Слово от "оркестр" где "орхестрированием" занимается дирижер, он не говорит как водить смычком или какие клавиши нажимать — это было бы уже управление более примитивными сущностями.
            Дирижер имеет дело с самодостаточными профессионалами, которые и сами по семе могут играть а в оркестре они формируются во что-то новое.
            Перенося аналогию на computing… у нас есть сервисы которы "умные" и в большей степени самодостаточны, вплодь до стандартных сервисов (авторизация, логирование, мониторинг, пользователи и т.д.). Они сами выполняют свои задачи, имеют свой лайфсайкл и т.д. — тут четкого опредиления нет, главное что ими не надо управлять из вне.
            Но! их можно "орхестритовать" в новые задачи (flows). Например самодостаточный и не нуждающийся в управлении сервис аудита может быть орхестрирован в различные бизнес-процессы.


            В ещё более узком понимании сейчас под оркестрацией на английских ресурсах все чаще подрузомевают автоматическую конфигурацию, развертывание и координацию аппликаций/сервисов. Например RedHat свою Ansible часто называет Orchestation tool, ну и kuberntes поэтому вполне можно так назвать и так и называют.

              0
              например RedHat свою Ansible часто называет Orchestation tool

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

                0

                Ну да.
                Просто я с универа это в контескте SOA помню ещё, тогда небыло облаков и DevOps, лет 15 назад и больше.
                И там архитектуру оркестрации противопостовляли хореографии (как и сейчас с микросервисами).
                Я помню про k8s изначально писали: "Оркестратор микросервисов", что удачно имхо, ну и видимо всем понравилось и теперь тянут на весь класс т.н. DevOps-инструментов.

              0

              Наверное примерно потому, что есть оркестр и есть дирижер, а не менеджер и команда :)
              Наверное намек на более высокий/возвышенный уровень аьстракции, как-то так

              +1

              На счет токенов сервис аккаунтов:


              1. Рестартить вам нужно будет и те поды, у которых дефолтный токен, иначе будет много 404 у API сервера да и просто дефолтный SA может использоваться. Если же поду не нужен доступ к API серверу, тогда нужно отключить автомонтирование дефолтного токена automountServiceAccountToken: false.
              2. Если вы потеряли только приватный ключ для подписи SA токенов, а сертификат у вас все еще есть, то можно сгенерить новую пару, сконфигурить новый приватный ключ controller-manager, добавить новый сертификат и оставить старый API серверу. Тогда ничего удалять не надо и ничего рестартить не надо.
              3. Пара сертификат-ключ который используется для SA один и тот же на всех инстансах API и controller-manager. Потому если потеряли на одном инстансе, то проще скопировать с других нод, тогда совсем ничего делать не надо.
                0

                Спасибо,


                Рестартить вам нужно будет и те поды, у которых дефолтный токен, иначе будет много 404 у API

                Но это при условии что данные поды ломятся в API, не так-ли?


                добавить новый сертификат и оставить старый API серверу

                А каким образом можно передать apiserver'у оба сертификата? — в смысле обычным бандлом?

                  0

                  На счёт 404 понял. Ошибка будет возникать при попытке кубелета смаунтить несуществующий секрет в под.


                  Кажется я нашёл более изящное решение: можно просто пропатчить все секреты, удалив из них .data.token и тогда controller-manager перегенирирует токены в секретах с тем же именем.


                  Статью обновил, огромное спасибо за наводку ;)

                    +1
                    А каким образом можно передать apiserver'у оба сертификата? — в смысле обычным бандлом?

                    нет, там просто можно два раза флаг с ключем/сертфикатом передать


                    --service-account-key-file old.crt
                    --service-account-key-file new.crt
                  +1
                  К сожалению далеко не все микросервисы умеют на лету перечитывать токен и скорее всего вам потребуется вручную перезапустить контейнеры, где они используются:

                  Тут не верное рассуждение, и наверное лучше поправить в статье. Когда генерируется новый токен для сервис акаунта то создаётся новый секрет, который должен быть примонтирован в под. Для пода эти поля являются неизменяемые. И только в момент создания пода, ServiceAccount Admission Controller прописывает секрет с токеном от ServiceAccount в под. Поэтому вам в любом случае необходимо удалить все поды, чтобы пересоздать их. Это всё верно если не используется фича BoundServiceAccountTokenVolume, и я не уверен, что её сейчас кто-то использует, т.к. она до сих пор в альфе.
                    0

                    Дельное замечание, спасибо, исправил!


                    PS: Использовал ServiceAccountTokenVolumeProjection для Konnectivity, очень удобно так как в этом случае токен перевыпускается и обновляются в поде автоматически.

                      0

                      UPD: Выше отписал что нашёл более изящное решение.
                      Имя секретов теперь не меняется, так что это утверждение вновь становится верным.

                        0
                        Да, согласен. Спасибо, что поделились.
                      0
                      В статье на схеме показан кластер с тремя control-plane нодами и зачем-то выполняется полная инициализация кластера, если на одном из мастеров все сломать, то можно просто его приджойнить заново к уже оставшимся в живых двум мастерам, просто сделав kubeadm reset для очистки остатков ноды и затем kubeadm join --control-plane …
                        0

                        Возможно я не совсем правильно выразился, но основная цель статьи была как раз в том чтобы разобраться как восстановить кластер при полной потере всех сертификатов и заменить их на новые.

                          0
                          В таком случае, все верно, за статью все равно спасибо, вы потратили время чтобы почитать документацию про фазы kuebadm init
                          0

                          А это разве починит сломанный етсд?

                            0
                            Насколько я знаю да починит, оно даже не должно сломаться, если конечно удаленная нода не была мастером, а так при добавлении ноды все должно восстановится и кворум = 3.

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

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