Азбука: FluxCD — перенастраиваем kubernetes с одного репозитория на другой

Очень удобным способом настройки кластеров kubernetes является использование GitOps подхода. Его суть заключается в том, что выделяется отдельный git репозиторий, который становится хранилищем всех манифестов, определяющих состояние кластера. Таким образом мы получаем единое хранилище истины и единую точку конфигурации для управления кластером. Одним из пионеров и проповедников этого подхода являлась компания WeaveWorks, в рамках которой было разработано множество интересных решений, среди которых есть и GitOps‑оператор FluxCD. Именно он и реализует цикл синхронизации манифестов из git репозитория с кластером.

Подробнее об этом инструменте можно почитать в различных статьях и послушать в следующих видео:

Можно посмотреть уже готовые к использованию примеры репозиториев:

И, конечно, есть официальная архитектурный гайд «Flux D1 Architectural Reference»

Сегодня же рассмотрим два реальных кейса

Кейс 1. Сломался GitLab.

В одной организации жил‑был GitLab. Жил он на домене gitlab.roga‑i-kopyta.com. Все было хорошо до этой «черной» пятницы, когда инженер Петя, заступив на свою смену, обнаружил, что GitLab вообще не подает признаки жизни. Не работает, и всё тут. Что только он ни делал с GitLab: и перезапускал, и пытался снять снимок виртуальной машины (а надо сказать, что GitLab был запущен в заморском облаке в виде VM), и подключал этот снимок к чистой виртуалке. Но GitLab всё это было как мертвому припарки.

В процессе этих мытарств обнаружилось, что бэкапы сделать забыли, а вот файловая система, к счастью,  была жива: все файлы были на месте и структура не была повреждена. Однако по какой‑то причине операционная система никак не грузилась с диска — видимо, или загрузчик сломался, или сама операционная система умерла.

Здесь стоит отметить, что и правда в прошлом при создании и обслуживании системы было допущено много ошибок. Необходимо делать бэкапы, а лучше всего — выносить данные приложений на отдельный диск, чтобы его можно было легко переподключать к другим виртуальным машинам. Плюс ко всему, так было бы проще делать снимки — без лишних файлов, которые не имеют отношение к делу. Но, как говорится, все мы задним умы крепки.

Наш Петя трудностей не боялся и был достаточно креативен. Поэтому он поднял новую виртуальную машину, а диск от старой виртуалки подключил к ней. После этого, вооружившись официальной докой, он установил чистую копию GitLab — той же версии, что была на безвременно почившей виртуальной машине. Далее он придумал хитрый план: он состоял в том, чтобы подменить точки монтирования на хост‑машине, в которую устанавливался свежий GitLab, на такие же, но со старого диска. Точечно. Сказано — сделано.

Для начала он подмонтировал старый диск:

# mount /dev/xvdbb14 /mnt

Дальше он остановил новый инстанс гитлаба и подменил каталоги с данными:

# mount --bind /mnt/opt/gitlab/ /opt/gitlab/
# mount --bind /mnt/var/opt/gitlab/ /var/opt/gitlab/

После этого он обратил внимание, что на каталогах побились разрешения доступов. Петя был опытным администратором и помнил, что ядро Linux работало не с именами пользователей, а с их целочисленными идентификаторами, а потому один пользователь на разных установках мог отображаться в разных id. Поэтому ему пришлось пройтись по файловой системе и исправить разрешения на правильные.

К сожалению, официальная документация GitLab не предусматривает и не описывает такой способ восстановления, поэтому работа Пети была похожа на выращивание кристалла из пересыщенного раствора дома в стакане. Интересно, что именно в такие моменты при решении инцидента инженер лучше узнает систему, которую должен поддерживать. Можно сказать, что он переходит на новый уровень.

Гэндальф тоже перешел на новый уровень после победы над монстром

Гэндальф тоже перешел на новый уровень после победы над монстром

Петя выдохнул, думая, что теперь‑то все готово. Он попробовал перезапустить GitLab и…ничего. Заглянув в логи, он понял, что забыл последний маленький штрих — перенести сертификаты letsencrypt, которые охраняли трафик от чужих глаз. После этого он снова перезапустил GitLab при помощи gitlab-ctl restart и…победа! Петя убедился, что репозитории можно скачать и загрузить изменения назад. Единственное, изменился отпечаток ssh‑узла, а потому git‑клиент при подключении по ssh выругался матом.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:HNl/PEU62MLuL3KRh8y8stDrJSta9aeRS8wg9zA0L3Y.
Please contact your system administrator.

Но это дело нехитрое. Командой ssh-keygen -R Петя удалил старый отпечаток и сделал рассылку на нашу компанию о том, что инцидент был решен.

Разработчики теперь могут работать, пайплайны пайплайнятся!

Внимательный читатель спросит —, а где же тут FluxCD? А вот где.

Петя решил отметить свою победу кружечкой крепкого чая и когда он вернулся к своему рабочему месту, чуть было не поперхнулся. FluxCD был настроен на отправку уведомлений в отдельный Slack канал и там была куча ошибок вида:

ssh: handshake failed: knownhosts: key mismatch

На счастье, FluxCD был достаточно умный, чтобы заметить подмену GitLab. А вдруг это были враги? И они только что подменили правильный инфраструктурный код на неведому зверюшку? А если бы код откатили? А в кластере хранятся данные, которые нельзя потерять? Петя подумал обо всех возможностях, но так как именно он восстанавливал GitLab, то он был уверен, что ничего плохого не случится. Оставалось только как‑то наладить обратно связь между FluxCD и GitLab, чтобы можно было дальше управлять кластером через репозиторий. Петя никогда не сталкивался именно с такой проблемой. Первое, что он вспомнил, что FluxCD держит в кластере специальный объект kind: Secret с данными для подключения к git‑репозиторию. Действительно, у секрета был ключ known_hosts, в котором лежал отпечаток сервера. Он выглядел примерно так:

gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=

Петя знал, что отпечаток сервера можно легко получить при помощи утилиты ssh-keyscan. Он именно это сделал. Он настолько вошел во вкус, что почувствовал себя нейрохирургом, оперирующим на живом мозге. Петя вырезал из выхлопа утилиты ssh-keyscan обновленный ключ узла с git’ом. И вставил новое значение в секрет. Дальше для подстраховки он удалил все поды FluxCD однострочником, который он придумал буквально только что:

kubectl delete pods -n flux-system --all

И после этого наступило… молчание? Нет! Просто исчезли все ошибки. Синхронизация восстановилась. Об этом красноречиво говорили сообщения зеленого цвета из технического Slack канала. Теперь действительно можно было откинуться на спинку стула, закрыть глаза и подумать о чем‑то приятном…

Вывод из истории очень простой. Если понимать, какие механизмы под капотом у системы, то можно ВСЕ. Ну, окей, может не ВСЕ, но очень многое.

Этот инстанс GitLab’а живет в таком состоянии еще до сих пор. И сделаны выводы из ошибок: резервные копии снимаются и проверяются. Соломка подстелена.

Кейс 2. Миграция FluxCD из репозитория в репозиторий.

Еще одна компания долгое время сидела на облачном gitlab.com и решила переехать на собственную установку GitLab. так называемый self‑hosted. Не будем обсуждать причины, побудившие компанию к переезду — ведь надо срочно помогать!

Изначально репозиторий назывался gitlab.com/perya-i-hvosty/kubernetes В нем была подготовлена структура, описывающая продуктивный кластер компании — это не оставляло шансов на ошибку. Необходимо было сделать перенос максимально безболезненно и прозрачно. Целевой репозиторий был создан заблаговременно с единственным README.md файлом в корне по адресу gitlab.perya-i-hvosty.com/infra/kubernetes

За эту задачу опять взялся известным нам по прошлой истории инженер Петя. Он решил заняться ею на следующий же день. Придя с утра на работу, Петя влил в себя кружку крепкого кофе. В мозгу тут же активировались нейроны. В петиной голове шла борьба между легкими путями решения задачи и возможными проблемами, которые могли при этом случиться. В первую очередь, Петя вспомнил, что базовая синхронизация настраивается двумя объектами разных типов — объектом GitRepository с названием flux-system и объектом Kustomization с тем же названием, размещенными в пространстве имен кластера flux-system. Они выглядели примерно так:

---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
    branch: master
  secretRef:
    name: flux-system
  url: ssh://git@gitlab.com/perya-i-hvosty/kubernetes
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 10m0s
  path: .
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

Пете еще очень не нравилось, что репозиторий не закладывал возможности для добавления в него других кластеров. А Петя хотел иметь возможность управлять и другими кластерами — проект активно развивался и требовались дополнительные вычислительные мощности на прочих площадках. Поэтому Петя решил провести переезд в несколько этапов.

Первым делом он продублировал содержимое репозитория gitlab.com/perya-i-hvosty/kubernetes в целевой репозиторий gitlab.perya-i-hvosty.com/infra/kubernetes Абсолютно точная копия.

Вторая мысль в его голове была такая: «Ага, раз файл flux-system/gotk-sync.yaml содержит ссылку на старый репозиторий, то это будет означать, что как только я переключу кластер на новый репозиторий, FluxCD перезатрет поле url GitRepository на старое значение. И это будет означать, что переезд не удался.» Поэтому наш герой быстренько поправил файл, и он стал выглядеть так:

---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
    branch: main
  secretRef:
    name: flux-system
  url: ssh://git@gitlab.perya-i-hvosty.com/infra/kubernetes
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 10m0s
  path: .
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

Хорошо. Теперь нужно как‑то переключить кластер на новый GitLab. Петя и тут нашел подводный камень, так как секрет для доступа в новый git явно будет отличаться от старого. Поэтому он решил сделать следующее. Первым действием он удалил секрет

kubectl delete secret -n flux-system flux-system

Это отключило синхронизацию со старым репозиторием. Нет секрета — нет проблем. Вторым шагом Петя решил реконструировать команду, которая используется для первоначальной установки FluxCD в кластер. Петя помнил из документации, что команда могла быть повторно использована на уже существующем кластере. Но ей был необходим административный токен. Петя зашел в настройки своего пользователя и выписал так называемый Personal Access Token. Этот токен позволяет различным программам и интеграциям взаимодействовать безопасным образом с экземпляром GitLab, не требуя пароля или ключа доступа. А еще их легко отзывать, когда токены стали не нужны.

Интерфейс создания Personal Access Token

Интерфейс создания Personal Access Token

Петя конструировал команду из головы, пользуюсь встроенной справкой CLI Flux:

$ flux --help

Здесь же он увидел, что есть подкоманда bootstrap

$ flux bootstrap --help

Из выхлопа этой команды он увидел поддержку GitLab

$ flux bootstrap gitlab --help

Эта команда уже дала самую детализированную справку

Из нее Петя узнал, что, во‑первых, ему нужно поместить свой GitLab токен в окружение:

$ export GITLAB_TOKEN=glpat-Gh_rytUHMTpjyZJnSnMB

Далее он попытался сконструировать команду для первоначальной установки FluxCD. Сначала он указал владельца и имя репозитория:

$ flux bootstrap gitlab --owner=infra --repository kubernetes

После этого он увидел, что нужно добавить следующие опции:

--hostname=gitlab.perya-i-hvosty.com

для нового хоста GitLab

--components-extra='image-reflector-controller,image-automation-controller'

очень полезные компоненты, которые позволяют автоматизировать обновление образов

--path=.

будем брать манифесты от корня репозитория

--branch=main

при переезде поменялась ветка по умолчанию с master на main

--read-write-key

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

Итоговая команда выглядела так

$ flux bootstrap gitlab \
  --owner=infra \
  --repository=kubernetes \
  --hostname=gitlab.perya-i-hvosty.com \
  --branch=main \
  --components-extra='image-reflector-controller,image-automation-controller' \
  --path=. \
  --read-write-key

Петя ее выполнил. И Flux выдал такой результат:

► connecting to https://gitlab.perya-i-hvosty.com
► cloning branch "main" from Git repository "https://gitlab.perya-i-hvosty.com/infra/kubernetes.git"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ committed component manifests to "main" ("545244d0d7e1999979546114bb2097bca823225a")
► pushing component manifests to "https://gitlab.perya-i-hvosty.com/infra/kubernetes.git"
► installing components in "flux-system" namespace
✔ installed components
✔ reconciled components
► determining if source secret "flux-system/flux-system" exists
► generating source secret
✔ public key: ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBDRJTiw+hjHmvfmq8hmqI9CnwCs3t1gw7eIN2vbjvT9hp8oHPAtNQ9xjF6cYpaztWHCzZf4sFsC/7mpyyFmYKuQZ6O3q4b8AQIRc85JRIwLyBNYj+QGpxGDjEvmFgEuRhA==
✔ configured deploy key "flux-system-main-flux-system-." for "https://gitlab.perya-i-hvosty.com/infra/kubernetes"
► applying source secret "flux-system/flux-system"
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ committed sync manifests to "main" ("6cf51cf9b741de86c8ff0919622d456dd983ade2")
► pushing sync manifests to "https://gitlab.perya-i-hvosty.com/infra/kubernetes.git"
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for GitRepository "flux-system/flux-system" to be reconciled
✔ GitRepository reconciled successfully
◎ waiting for Kustomization "flux-system/flux-system" to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ image-automation-controller: deployment ready
✔ image-reflector-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy

Еще Петя был очень удовлетворен тем, что так как он взял последнюю версию CLI Flux, то автоматически обновилась версия FluxCD в кластере. Оставалось буквально последнее. Каким образом аккуратно перенести файлы манифестов в подкаталог? Петя помнил из прошлых экспериментов, что это может быть болезненным опытом, поэтому решил подготовиться получше. Первым делом он отключил очистку уже не существующих ресурсов. Для этого он изменил манифесты таким образом:

---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
    branch: main
  secretRef:
    name: flux-system
  url: ssh://git@gitlab.perya-i-hvosty.com/infra/kubernetes
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 10m0s
  path: .
  prune: false
  sourceRef:
    kind: GitRepository
    name: flux-system

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

$ flux get all -A
...
NAMESPACE  	NAME                           	REVISION          	SUSPENDED	READY	MESSAGE                              
flux-system	kustomization/flux-system      	main@sha1:b48890a3	False    	True 	Applied revision: main@sha1:b48890a3	

Следующим шагом он остановил синхронизацию.

$ flux suspend ks flux-system
$ flux suspend source git flux-system

Дальше он пошел в репозиторий — теперь он мог сделать это безопасно, ничего не сломав и не опасаясь негативных последствий. Петя перенес каталог flux-system в ./cluster/production/flux-system. Все манифесты, которые были рядом — переехали тоже. Также он причесал структуру, чтобы она была более понятной и удобной (в этом он опирался на свой опыт и опыт своих коллег).

Оставалось следующее — надо было сообщить кластеру, что теперь следует брать данные из нового места. Был вариант запустить процедуру bootstrap с указанием нового корневого пути — ./cluster/production/flux-system. Но Петя решил опять побыть нейрохирургом. Он пропатчил путь в объекте Kustomization flux-system на новый. Дальше он осенил себя крестным знамением и восстановил синхронизацию:

$ flux resume source git flux-system
$ flux resume ks flux-system

Немного ожидания и voila! Теперь кластер уже смотрел в новый каталог. На это можно было бы и остановиться, но так как Петя не хотел, чтобы разработчики оставляли в кластере следы своей жизнедеятельности, то нужно было вернуть prune в состояние true. Петя это сделал в новом git‑репозитории и как только он поправил манифест, FluxCD применил его на кластере. Теперь всё точно было готово. И Петя сел смотреть новый сериальчик — сегодня он отлично поработал и мог позволить себе немного расслабиться.

Из приведенных в статье примеров можно увидеть, что хотя FluxCD — очень продуманный инструмент, даже он требует особого обращения, насмотренности и навыков. В противном случае можно наворотить очень нехороших дел. Автор статьи неоднократно стрелял себе по ногам и предостерегает от повторения подобного опыта читателей. Надеемся, эти две истории развлекут вас и вы сможете избежать таких же ошибок.

© Habrahabr.ru