Очень удобным способом настройки кластеров kubernetes является использование GitOps подхода. Его суть заключается в том, что выделяется отдельный git репозиторий, который становится хранилищем всех манифестов, определяющих состояние кластера. Таким образом мы получаем единое хранилище истины и единую точку конфигурации для управления кластером. Одним из пионеров и проповедников этого подхода являлась компания WeaveWorks, в рамках которой было разработано множество интересных решений, среди которых есть и GitOps‑оператор FluxCD. Именно он и реализует цикл синхронизации манифестов из git репозитория с кластером.
Подробнее об этом инструменте можно почитать в различных статьях и послушать в следующих видео:
-
Георг Гаал «Краш‑курс по fluxcd в реальной жизни»
-
Георг Гаал «FluxCD в действии»
-
Артем Мещеряков «GitOps + FluxCD: продвинутые практики управления K8s‑инфраструктурой»
-
Виталий Медведев «flux, flux и в production»
Можно посмотреть уже готовые к использованию примеры репозиториев:
И, конечно, есть официальная архитектурный гайд «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, не требуя пароля или ключа доступа. А еще их легко отзывать, когда токены стали не нужны.
Петя конструировал команду из головы, пользуюсь встроенной справкой 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 — очень продуманный инструмент, даже он требует особого обращения, насмотренности и навыков. В противном случае можно наворотить очень нехороших дел. Автор статьи неоднократно стрелял себе по ногам и предостерегает от повторения подобного опыта читателей. Надеемся, эти две истории развлекут вас и вы сможете избежать таких же ошибок.
Автор: gecube