Привет всем. Меня зовут Алексей, я DevOps-инженер, и сегодня я хочу рассказать немного об одном инфраструктурном решении моего ключевого заказчика.
Немного о моей работе и разделении ответственности. Я предоставляю услуги по настройке и сопровождению облачной инфраструктуры на основе GCP, а также мониторинг, алертинг, логи и т.д. В Altenar я являюсь частью команды автоматизации, которая также занимается релизными пайплайнами, улучшением различных процессов в компании и другими важными вещами. CI, сборки, сеть, доступы - за это отвечают другие команды, и этих частей я касаюсь очень мало либо совсем не трогаю их.
В моей команде издревна использовался flux v1 для кластеров на VM и для GKE в формате "один кластер - один репозиторий", с подключенными "шаблонами" в виде Git submodules. У такого подхода есть свои минусы, один из самых существенных - первая версия flux уже не поддерживается с конца 2020 года.
Таким образом, у нас был некий техдолг в виде первого flux, и необходимость рано или поздно обновляться до версии 2 или переходить на другие инструменты. Был выбран первый вариант, заодно я решил кое-что улучшить в нашей текущей структуре, убрав кучу различных репозиториев и отказавшись от "шаблонов-сабмодулей".
Особенность переезда - часть кластеров я мог пересоздавать без проблем сразу с новой инсталляцией flux v2, например, мой сэндбокс и дев кластер, а часть - в продакшене, для которых надо было продумать вопросы DR если что-то пойдет не так и создать мануал для переезда с минимальным downtime.
Как я построил новую монорепу и почему.
Старая схема.
repo-1
└── flux-v1-cluster
├── submodule-base
│ └── charts
└── submodule-gke
└── charts
repo-2
└── submodule-base
└── charts
repo-3
└── submodule-gke
└── charts
Допустим, мне надо посмотреть, что там у меня за чарт катится в кластер. Я вижу в кластере, что это часть flux, иду в репо flux-v1-cluster, пытаюсь там найти искомое и не нахожу. Ищу дальше, вспоминаю, что тут есть сабмодули, лезу в Submodule-base, ищу там, а там нет... иду в соседнее репо Submodule-gke и ага! Оно тут! ????
Причем в следующий раз, когда ты вспоминаешь, что какой-то чарт ты находил в Submodule-gke, ты идешь сразу туда, а там его нет... потому что он в другой репе Submodule-base... В общем, если вы когда-нибудь оперировали связанными репозиториями раз в месяц-два, вы уже поняли в чем проблема.
Сюда же добавляется проблема с версионированием сабмодулей. В какой-то момент я долго не мог понять, что не так, т.к. в один кластер лился чарт, которого не было ни в одном сабмодуле. Как оказалось, этот чарт был добавлен в какую-то кастомную ветку сабмодуля, и догадаться об этом было бы довольно сложно, если не вчитываться в версию используемого сабмодуля.
Как сказала нейронка про эту структуру:
Такая организация кода имеет несколько недостатков:
1. Сложность управления зависимостями: в данной структуре управление зависимостями между разными репозиториями и их сабмодулями может быть довольно сложным и может приводить к конфликтам зависимостей.
2. Увеличение сложности развертывания: такая организация кода может привести к усложнению процесса развертывания, поскольку необходимо координировать обновления в различных репозиториях и сабмодулях.
3. Ограничение гибкости: при такой организации кода изменение одного компонента может потребовать обновления зависимостей в нескольких репозиториях и сабмодулях, что может привести к ограничению гибкости и увеличению времени на изменение системы.
4. Увеличение времени разработки: в случае, если несколько команд работают над различными репозиториями и сабмодулями, это может привести к увеличению времени разработки и снижению производительности команды в целом.
5. Затруднение в масштабировании: в случае, если требуется добавить новый компонент или изменить имеющийся, это может привести к затруднениям в масштабировании системы, поскольку необходимо будет координировать изменения в нескольких репозиториях и сабмодулях.
Новая схема.
Несмотря на то, что flux v2 также поддерживает "сабмодули" в виде отдельных репозиториев, я реализовал монорепозиторий по одному из примеров в документации:
https://fluxcd.io/flux/guides/repository-structure
.
├── apps
│ ├── common
│ │ └── monitoring
│ │ ├── prometheus
│ │ └── prometheus-rules
│ ├── kafka
│ ├── rabbitmq
│ └── redis
├── clusters
│ ├── cluster-1
│ │ ├── custom-resources
│ │ └── sync-code
│ ├── cluster-2
│ │ ├── custom-resources
│ │ └── sync-code
│ ├── gke-template
│ └── self-hosted-template
└── infrastructure
├── controllers
│ └── nginx-ingress
└── lb
└── metallb
Преимущества:
Все нужные "модули" и приложения у меня на виду, в одном репозитории.
Легко добавлять новые приложения как в кластер, так и в обобщенные "шаблоны" кластеров.
При использовании кастомной ветки для кластера - все приложения также под рукой и их версии не "уедут" случайно из-за работы коллег с мастер веткой.
При создании ПРов в мастер монорепы в отличие от ПРов в сабмодуль - ты точно уверен, что это влияет на все ресурсы и поэтому ты будешь более внимателен ????
Недостатки:
Если вы плохо продумали структуру кластера и решили ощутимо поменять ее на ходу, когда несколько кластеров уже используют монорепу - будет довольно больно.
Про ПРы и их влияние на всю инфраструктуру сразу справедливо и к минусам отнести.
У нейронки своё мнение на этот счет, конечно же, попросил ее сравнить, что лучше:
Оценить, лучше или хуже, зависит от требований проекта и организации команды разработчиков.
Однорепозиторная структура имеет следующие преимущества:
1. Упрощение управления зависимостями: такая структура позволяет упростить управление зависимостями между компонентами, поскольку все они находятся в одном репозитории.
2. Упрощение процесса развертывания: упрощение управления зависимостями также приводит к упрощению процесса развертывания, поскольку обновления можно координировать в рамках одного репозитория.
3. Улучшение гибкости: в случае необходимости изменения компонента, изменения можно вносить в рамках одного репозитория, что улучшает гибкость и уменьшает время на изменение системы.
4. Улучшение производительности команды: однорепозиторная структура может улучшить производительность команды, поскольку упрощает координацию работы над различными компонентами.
Однако, у однорепозиторной структуры также есть некоторые недостатки:
1. Увеличение размера репозитория: все компоненты находятся в одном репозитории, что может привести к увеличению его размера и усложнению управления версиями.
2. Ограничение масштабирования: в случае, если требуется добавить новый компонент или изменить имеющийся, это может привести к затруднениям в масштабировании системы, поскольку необходимо будет изменять код в рамках одного репозитория.
3. Увеличение времени разработки: увеличение размера репозитория может привести к увеличению времени разработки и снижению производительности команды в целом.
Итак, обе структуры имеют свои преимущества и недостатки, и выбор между ними зависит от конкретных требований проекта и команды разработчиков.
Немного про упаковку приложений
Даже прочитав несколько раз документацию, я долгое время не мог понять, что именно из себя представляет "упакованное приложение для flux v2".
В итоге, самая простая и понятная схема для описания этого у меня получилась такая:
По сути, чтобы упаковать приложение для монорепы, ты собираешь набор ресурсов, которые нужны приложению, например, неймспейс, полиси и прочие роу манифесты. Если у приложения есть хелм чарт - добавляешь хельм релиз. И все это ты просто кладешь в одну директорию и добавляешь kustomization, в котором перечисляешь используемые файлы.
Best practice from my experience:
Если приложение имеет какие-либо конфигурируемые параметры (например, классические values для helm чарта) - подключайте их сразу как configmap и делайте его использование обязательным. Если потребуется установка с параметрами по умолчанию - просто добавьте пустой configmap.
Если вы хотите добавить в кластер какие-то компоненты, требующие CRD - подумайте о том, как вы их будете упаковывать в отдельное “приложение” в flux v2.
Пример - kafka-alerts в моём репозитории.
Домашнее задание - попробуйте добавить любые прометей правила сразу в кластер и вы сразу поймете, зачем я разделил код кластера на 2 директории: custom-resources и sync-code.
Пример упаковки прометея можно посмотреть в репозитории с примером и даже раскатить себе, подробнее в конце статьи.
Порядок миграции flux v1 -> flux v2
Тут всё более-менее просто, но есть ряд нюансов.
Мы используем практику подготовки плана ченжа “как для пятилетнего ребенка”, чтобы любой из доступных инженеров мог пройти по пунктам чеклиста и ничего критичного не забыть (хотя, ситуации бывают разные).
Если у вас сильно устаревший прометей, как был у меня - он не зареконсилится, т.к. у него не совпадут CRD с новой версией. Необходимо удалить все старые ресурсы и definitions прометея ручками перед выкаткой нового.
В общих чертах:
Скейлим деплоймент flux v1 в 0
Бутстрапим flux v2 в кластер (есть несколько вариантов, как вы будете это делать, в моем репозитории описан самый простой)
Применяем код нового кластера
Меняем\удаляем старые аннотации руками у компонентов, которые управлялись flux v1 (но я предпочитаю удалить старую инсталляцию и установить приложение заново, т.к. я могу себе это позволить в окно обслуживания)
В целом, все должно пройти без проблем. Ну а в случае, если вы делаете это впервые - рекомендую экспериментировать на демо-кластере.
Пример кода для создания сэндбокса в gke через terraform тут: https://github.com/ksemele/tf-gke-test
Пример кода монорепы flux v2 для этого кластера тут: https://github.com/ksemele/fluxv2-test