GitOps на мінімалках для Kubernetes
Привіт, спільното! У цьому тексті я хочу розповісти про наш варіант реалізації GitOps для Kubernetes та й взагалі, хотів би почути вашу думку, чи це GitOps чи ні.
Коротко про рішення: release branch в git, helm template як шаблонізатор та звичайний kubectl:
- Уся система — це набір helm chart.
- Номери білдів кожного сервісу зафіксовані в git, та автоматично збираються CI сервером.
- Реліз тільки із release branch.
Якщо цікаво, то почнемо розповідь.
Опис задачі
Спочатку розповім, як у нас побудовано процес розробки та релізу:
- одна команда розробляє з десяток сервісів;
- спринти по 2 тижня;
- все що зробили, загортаємо в реліз та готуємо до деплою на прод:
- реліз — усі модифіковані сервіси;
- деплой у kubernetes;
- використовуємо git із стандартний trunk-based flow:
- кожен сервіс у своєму репозіторії;
- є активний бранч (main);
- на кожну сторі/таску є окремий бранч;
- після ревью сторі-бранч мержимо до активного;
- коли готові збирати реліз — від активного відрізається реліз брач та іменується за шаблоном release/x.x.x;
- реліз деплоїться на QA env для тестування, а потім на UAT, ну, і у фіналі на Prod;
- так, у нас є CI, але немає повноцінного continues deployment:
- після кожного мержу до main та release/x.x.x — збирається docker image для кожного сервісу;
- для Dev env налаштовано автоматичний деплой окремого сервісу після кожного білда;
- а ось для інших — деплой усього релізу (усіх модифікованих сервісів) зразу, через натискання на кнопки з підтвердженням, але далі все автоматично;
Наша система живе у Azure cloud: kubernetes та пачці інших ресурсів. Уся інфраструктура менеджериться через terraform.
Залишається питання до деплоя у kubernetes: як розгорнути усю систему та оновлювати за один раз, одним релізом.
Звісно всі розуміють, що для деплою кожного сервісу треба: deployment, configmap, service, ingress та custom resources. А ще є декілька енвайроментів для деплою, з різними налаштуваннями. Але як це все підтримувати?
Отже, наші вимоги:
- усе лежить у git, правила роботи як з кодом;
- pull request review;
- release бранчі;
- шаблонізувати все що повторюється, аби зменшити copy-paste;
- налаштування сервісів для енвайроментів теж в git, але без секретів (вони в keyvault);
- версії білдів та docker image tag кожного сервісу в конкретному релізі теж в git;
- деплой в Kubernetes із git, ідемпотентність деплою;
- rollback деплою теж через git;
- можливість деплой кожного окремого сервісу для dev env.
Як виявилось, для цього вже існує методологія — GitOps. Погугливши про це, я знаходив референси тільки на argo cd та helm, але мені не хотілося додавати щось додаткове у продсередовище. Бо знов то все треба сапортити, оновлювати, а ресурсів у нас немає. Та й kubernetes вміє в diff.
Отже, було вирішено зібрати GitOps на мінімалках з того, що вже є. На цей момент у нас вже була купа deployments, configmaps and etc для кожного сервісу.
Наша ідея:
- Мати інструмент який може, генерувати всі необхідні ресурси для kubernetes для кожного сервісу окремо та всіх сервісів разом.
- Використовуємо звичайний kubectl apply для деплою в kubernetes, а далі він сам все зробе.
- Створювати release на кожний коміт до release бранч.
Використання Helm
Як шаблонізатор ми обрали Helm, але ми не використовуємо його як повноцінний release manager.
Helm — це потужний інструмент, але треба без фанатизму аби не програмувати в конфигах. В результаті маємо один великий файл, який можна згодувати kubectl apply.
Особливості helm, як шаблонізатора:
- параметри глобального рівня:
- можна використовувати для зовнішніх ресурсів: адреса сервера бази даних, kafka bootstrap server і таке інше;
- параметри chart рівня:
- використовуємо як константи в різних файлах;
- задаємо якісь змінні для сервісу, наприклад docker image tag;
- завдяки local dependencies можна будувати чарти для системи з багатьма сервісами;
- condition для dependency:
- завдяки цьому можна генерувати ресурси для одного сервісу, наприклад;
- можна використовувати include;
- параметри задаються через команду строк або через файл. Можна використовувати декілька файлів:
helm template -f values-1.yaml -f values-2.yaml --set service-1.tag=123
Важливі вимоги до helm chart:
- Мати один і той самий контент на виході, якщо ви запускаєте helm раз за разом без змін в репозиторії.
- Deployment повинен «залежати» від усіх похідних ресурсів: бо якщо просто змінити config map, то kuber не рестартує ваш deployment автоматично.
- додати annotation з чексумами залежних ресурсів до deployment
- додати annotation з чексумами залежних ресурсів до deployment
- Знайти баланс у конфігураційних параметрах:
- виносимо усі env-specific конфіги сервісу до параметрів (values);
- або щось залишаємо у шаблоні та використовуємо if;
Наша структура helm чарту для всієї системи виглядає наступним чином:
app-chart/ |--commons/ |--templates/ |-- _deployment.yaml |--_shared-config-properties.tpl |--Chart.yaml |--service-1 |--templates/ |--deployment.yaml |--configmap.yaml |--values.yaml |--Chart.yaml |--service-2 |--templates/ |--deployment.yaml |--configmap.yaml |--values.yaml |--Chart.yaml |--Chart.yaml |--values.yaml |--values-builds.yaml |--values-env-1.yaml |--values-env-2.yaml
У рутовому Chart.yaml
усі сервіси додані як dependencies, їх можна вмикати/ вимикати за допомогою condition.
dependencies:
dependencies: - name: commons version: 1.0.0 - name: service-1 version: 1.0.0 condition: service-1.enabled - name: service-2 version: 1.0.0 condition: service-2.enabled
Як ви вже могли здогадатися, у файлі values-builds.yaml
лежать docker image tag для кожного сервісу. Цей файл генерується білд системою і комітится у репозиторій автоматично, але можна менеджети і руками.
Файл values-builds.yaml
виглядає якось так (звісно, можна додати скільки завгодно інфи про білд сервісу):
service-1: tag: 111 commitId: aaaaaaaaaaaa service-2: tag: 222 commitId: bbbbbbbbbbb
Звісно, всі параметри сервісу мають бути задекларовані у values.yml
, наприклад service-1/values.yaml
enabled: false tag: commitId:
Також параметри кожного середовища зберігаються у відповідному файлі, наприклад values-env-2.yaml
globals: kafka: bootstrap: 127.0.0.1:9092 service-1: param1: "service-1 env2 value" enabled: true service-2: param2: "service-2 env2 value" enabled: true
Як результат, ми маємо можливість генерувати kubernetes resources definition для релізу усієї системи за допомогою команди:
helm template -f app-chart/values-env-2.yaml -f app-chart/values-builds.yaml ./app-chart > all-services.yaml
А якщо нам треба тільки один сервіс, то у файлі values-env-1.yaml
вимикаємо усі сервіси і вмикаємо потрібний через параметри команди:
helm template -f app-chart/values-env-1.yaml ./app-chart--set service-1.enabled=true --set service-1.tag=123 --set service-1.commitId=ccccccc ./app-chart > single-service.yaml
Налаштування CI сервера
Раз ми в вже в Azure, то для білдів та релізів використовуємо Azure DevOps.
Його особливістю є те, що, крім самих звичайних build pipeline, там є release pipeline. Задум такий, що build pipeline збирає артефакт, а release — деплоїть його.
Про release pipeline
Release pipeline — цікавий інструмент, якщо у вас є декілька середовищ. Можна будувати залежність між ним або налаштовувати різні типи підтверджень для релізу та сповіщень.
Але є один суттєвий мінус — їх не можна зберігати as a code. А отже там треба мати мінімальну кількість простих кроків.
Зображення з офіційного сайту
Про реліз усієї системи
Отже, що ми маємо на CI:
- build pipeline для кожного сервісу, де артефактом є docker image та image tag — це build number;
- pre-release build pipeline: для збору актуальних версій білдів та створення release pipeline; далі розповім детальніше;
- release pipeline: деплой системи в кожне середовище з двох кроків:
helm template -f values-builds.yaml-f values-env-2.yaml > release.yaml
kubectl apply -f release.yaml
Pre-release build pipeline
Цей build pipeline тригериться на створення/ зміни у release branch нашого реліз репозиторію.
Його задача — оновити values-builds.yaml
та створення release pipeline:
- За допомогою Azure DevOps API зібрати build number всіх сервісів з таким же branch name як і той, що тригернувся;
- Оновити файл
values-builds.yaml
; - Запушити зміни в git. Аби уникнути зациклення, в git commit message додаємо
[skip ci]
; - Через Azure DevOps API створюємо release pipeline з реферонсом на branch та commitId;
- Усе готово, можно запускати реліз;
Про деплой окремого сервісу на dev
В нас є автоматичний деплой кожного білда кожного сервісу на dev env.
Тут усе просто — маємо release pipeline per a service, який тригериться на build pipeline з main branch з двох кроків:
helm template -f values-env-dev.yaml --set service-1.enabled =true --set service-1.tag =123 > service-release.yaml
kubectl apply -f service-release.yaml
Але ми хочемо деплоїти тільки один сервіс, а не усі, тому що для main branch ми не зберігаємо версії у git:
- у файл
values-env-dev.yaml
— усі service chart вимкнені через enabled flag, і не будуть опрацьовані; - потрібний сервіс вмикається через:
--set service-1.enabled=true --set service-1.tag=123
; values-builds.yaml
— не використовується;
Висновки
Маємо непоганий результат з використанням дуже простих інструментів та без додавання зайвого до kubernetes:
- реліз можна зробити тільки з git;
- всі зміни kubernetes resources тільки через code review;
- buildNumber кожного сервісу в кожному релізі зафіксований та можна легко знайти;
- release rollback — це просто git rollback;
Але є мінус — неможливо видалили доданий kubernetes resource на rollback або якщо змінили йому назву.
Дякую за увагу.
Слава ЗСУ
23 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів