GitOps на мінімалках для Kubernetes

💡 Усі статті, обговорення, новини про DevOps — в одному місці. Приєднуйтесь до DevOps спільноти!

Привіт, спільното! У цьому тексті я хочу розповісти про наш варіант реалізації GitOps для Kubernetes та й взагалі, хотів би почути вашу думку, чи це GitOps чи ні.

Коротко про рішення: release branch в git, helm template як шаблонізатор та звичайний kubectl:

  1. Уся система — це набір helm chart.
  2. Номери білдів кожного сервісу зафіксовані в git, та автоматично збираються CI сервером.
  3. Реліз тільки із release branch.

Якщо цікаво, то почнемо розповідь.

Опис задачі

Спочатку розповім, як у нас побудовано процес розробки та релізу:

  1. одна команда розробляє з десяток сервісів;
  2. спринти по 2 тижня;
  3. все що зробили, загортаємо в реліз та готуємо до деплою на прод:
    • реліз — усі модифіковані сервіси;
    • деплой у kubernetes;
  4. використовуємо git із стандартний trunk-based flow:
    • кожен сервіс у своєму репозіторії;
    • є активний бранч (main);
    • на кожну сторі/таску є окремий бранч;
    • після ревью сторі-бранч мержимо до активного;
    • коли готові збирати реліз — від активного відрізається реліз брач та іменується за шаблоном release/x.x.x;
  5. реліз деплоїться на QA env для тестування, а потім на UAT, ну, і у фіналі на Prod;
  6. так, у нас є CI, але немає повноцінного continues deployment:
    • після кожного мержу до main та release/x.x.x — збирається docker image для кожного сервісу;
    • для Dev env налаштовано автоматичний деплой окремого сервісу після кожного білда;
    • а ось для інших — деплой усього релізу (усіх модифікованих сервісів) зразу, через натискання на кнопки з підтвердженням, але далі все автоматично;

Наша система живе у Azure cloud: kubernetes та пачці інших ресурсів. Уся інфраструктура менеджериться через terraform.

Залишається питання до деплоя у kubernetes: як розгорнути усю систему та оновлювати за один раз, одним релізом.

Звісно всі розуміють, що для деплою кожного сервісу треба: deployment, configmap, service, ingress та custom resources. А ще є декілька енвайроментів для деплою, з різними налаштуваннями. Але як це все підтримувати?

Отже, наші вимоги:

  1. усе лежить у git, правила роботи як з кодом;
    • pull request review;
    • release бранчі;
  2. шаблонізувати все що повторюється, аби зменшити copy-paste;
  3. налаштування сервісів для енвайроментів теж в git, але без секретів (вони в keyvault);
  4. версії білдів та docker image tag кожного сервісу в конкретному релізі теж в git;
  5. деплой в Kubernetes із git, ідемпотентність деплою;
  6. rollback деплою теж через git;
  7. можливість деплой кожного окремого сервісу для dev env.

Як виявилось, для цього вже існує методологія — GitOps. Погугливши про це, я знаходив референси тільки на argo cd та helm, але мені не хотілося додавати щось додаткове у продсередовище. Бо знов то все треба сапортити, оновлювати, а ресурсів у нас немає. Та й kubernetes вміє в diff.

Отже, було вирішено зібрати GitOps на мінімалках з того, що вже є. На цей момент у нас вже була купа deployments, configmaps and etc для кожного сервісу.

Наша ідея:

  1. Мати інструмент який може, генерувати всі необхідні ресурси для kubernetes для кожного сервісу окремо та всіх сервісів разом.
  2. Використовуємо звичайний kubectl apply для деплою в kubernetes, а далі він сам все зробе.
  3. Створювати release на кожний коміт до release бранч.

Використання Helm

Як шаблонізатор ми обрали Helm, але ми не використовуємо його як повноцінний release manager.

Helm — це потужний інструмент, але треба без фанатизму аби не програмувати в конфигах. В результаті маємо один великий файл, який можна згодувати kubectl apply.

Особливості helm, як шаблонізатора:

  1. параметри глобального рівня:
    • можна використовувати для зовнішніх ресурсів: адреса сервера бази даних, kafka bootstrap server і таке інше;
  2. параметри chart рівня:
    • використовуємо як константи в різних файлах;
    • задаємо якісь змінні для сервісу, наприклад docker image tag;
  3. завдяки local dependencies можна будувати чарти для системи з багатьма сервісами;
  4. condition для dependency:
    • завдяки цьому можна генерувати ресурси для одного сервісу, наприклад;
  5. можна використовувати include;
  6. параметри задаються через команду строк або через файл. Можна використовувати декілька файлів:
    helm template -f values-1.yaml -f values-2.yaml --set service-1.tag=123

Важливі вимоги до helm chart:

  1. Мати один і той самий контент на виході, якщо ви запускаєте helm раз за разом без змін в репозиторії.
  2. Deployment повинен «залежати» від усіх похідних ресурсів: бо якщо просто змінити config map, то kuber не рестартує ваш deployment автоматично.
    • додати annotation з чексумами залежних ресурсів до deployment
  3. Знайти баланс у конфігураційних параметрах:
    • виносимо усі 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:

  1. build pipeline для кожного сервісу, де артефактом є docker image та image tag — це build number;
  2. pre-release build pipeline: для збору актуальних версій білдів та створення release pipeline; далі розповім детальніше;
  3. 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:

  1. За допомогою Azure DevOps API зібрати build number всіх сервісів з таким же branch name як і той, що тригернувся;
  2. Оновити файл values-builds.yaml;
  3. Запушити зміни в git. Аби уникнути зациклення, в git commit message додаємо [skip ci];
  4. Через Azure DevOps API створюємо release pipeline з реферонсом на branch та commitId;
  5. Усе готово, можно запускати реліз;

Про деплой окремого сервісу на dev

В нас є автоматичний деплой кожного білда кожного сервісу на dev env.

Тут усе просто — маємо release pipeline per a service, який тригериться на build pipeline з main branch з двох кроків:

  1. helm template -f values-env-dev.yaml --set service-1.enabled =true --set service-1.tag =123 > service-release.yaml
  2. kubectl apply -f service-release.yaml

Але ми хочемо деплоїти тільки один сервіс, а не усі, тому що для main branch ми не зберігаємо версії у git:

  1. у файл values-env-dev.yaml — усі service chart вимкнені через enabled flag, і не будуть опрацьовані;
  2. потрібний сервіс вмикається через: --set service-1.enabled=true --set service-1.tag=123;
  3. values-builds.yaml — не використовується;

Висновки

Маємо непоганий результат з використанням дуже простих інструментів та без додавання зайвого до kubernetes:

  • реліз можна зробити тільки з git;
  • всі зміни kubernetes resources тільки через code review;
  • buildNumber кожного сервісу в кожному релізі зафіксований та можна легко знайти;
  • release rollback — це просто git rollback;

Але є мінус — неможливо видалили доданий kubernetes resource на rollback або якщо змінили йому назву.

Дякую за увагу.
Слава ЗСУ

👍ПодобаєтьсяСподобалось2
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

В себе інтегрував аргосд, там він сам по білкам деплоіть, переоприділяє вейлюс, тримає все завжди в останній версії, замерджив реліз в мастер, зібрався контейнер і поїхав в докерхаб, а там вже аргосд і аргосд імеджапдейтер, то все синхронізує і тд. Плюс прямо з нього можна зробити ролбек на попередній імедж, і все запрацює якщо були проблеми, ну звісно якщо не було якихось міграцій в бд які не сумісні з старим кодом.

А чим Gitflow не підійшов? Один із best practices все-таки www.atlassian.com/...​orkflows/gitflow-workflow

Також на вашому проекті типу немає необхідності в процесі code review між QA i deployment? Хто відповідальний щоб не було гімнокоду в релізах (які у вас досить часті)?

Вибачте не зрозумів запитання — це комент до trunk-based flow?
Так це майже git-flow тільки з однією main гілкою замість main та dev у git flow.
У git flow є main/master із останньою стабільною версією та dev для розробки від якого вже бранчуються гілки для тасок
У trunk-base є тільки «dev» гілка, від неє бранчі для тасок щ ревью і всім чим треба. Коли готові зібрати версію(реліз) ріжете від dev брач і з нього збираеться релізний білд.
Тобто менше надо мержити.

Ну якщо у вас для кожної таски є бренча яку ревювають і потрібно меджити то це не схоже на trunk-base.

ну хз, у нас все є і feature branch і release branch (у кінці циклу), але менша кількість мержу бранчів.

ось тут є гарні картинки різниці у флоу — medium.com/...​-development-3beff578030b

Ну ця стаття досить розмито описує. Ось дещо більш розгорнутий опис trunkbaseddevelopment.com В моємо розумінні trunk-base це коли девелопери комітять напряму в main чи створюють гілки на короткий термін один/два дні для автоматизації тестів чи ревю, бо в іншому випадку їм потрібно робити це локально. Це що ви описали це GitHub flow ось стаття про це docs.github.com/...​ed/quickstart/github-flow

Прочитавши опис задачі та вашого воркфлоу, я поцікавився загалом чому не застосували широко поширений Gitflow і вирішиши експерементувати на вашому проекті із «GitOps на мінімалках»?
Також питання якості коду, враховуючи відсутність code review згідно вашого опису.

здається я щось не так написав, бо люди думають що у нас нема code review — це звідкіля? це з мого твердження про trunk-based ? мабуть я використал не той термін, вище я вже написав — що під цим я розомію gitflow але с тільки с main гілкою замість main та develop

і стаття про деплой 10 сервісів на кубер у кінці спринта одномоментно, а не про розробку.
у нас code review для всіх змін, навіть девопсних.

я робив схожий пайплайн на основі Jenkins, все працювало як годинник. Тільки для застосування темплейтів на кластер використовував helmfile, воно дозволяє ще додатково параметризувати helm темплейти.

мені сподобалось. дякую. flux або argocd можно буде легко добавити у майбутньому. але навищо?

Дякую.
Я почитав про flux, здається він навколо helm працює. Повинно бути просто мігрувати.
Ще не пробував, і так працює.

Таки візьміться за argo всерйоз, замість винаходити лісапєт

арго не занадто, бо сетап і підтримка точно не складніше за все описане

Якщо таки без арго, то skaffold.dev зробить все що ви описали. Але як на мене краще таки просто вліпити арго

а як вам fluxcd.io ? працювали з ним?

Пробував, але не зайшло.

Загалом вони роблять одне й теж, але арго фактично де факто стандарт уже, більша підтримка, плюс в арго є красивий гуй. Конкретних значних плюсів у флюкс не побачив для себе. Довго не сидів з ним, але для мене переваг — або їх нема, або вони дуже нішеві, що не має грати ролі в загальному першому виборі

Якщо є щось, що зачепило в флюксі — то варто зробити вже PoC й порівняти

але арго фактично де факто стандарт уже

Да ладно?

Ну якщо по хорошому критикувати, то чистий GitOps підхід не вписується в багато операційних юз кейсів, хоча переваг у нього більше ніж недоліків нмсд

Запаузити купу апок щоб зробити щось на кластері мене завжди напрягає. Пуре-GitOps трохи занадто напряжно. Може автору буде в принципі в самий раз, кейс маленький відносно

Ну і ще не придумали сільвербулет який не можна пофіксити як слід парою скриптів :)

Ну в моєму розумінні gitops це коли все в git і тільки з нього деплой і він самодостатній

Ну по суті це і є філософія gitops, і постільки поскільки воно красиво все в гіт кладеться — все чудово

Проблеми вилазять коли тре щось робити штучне, наприклад якийсь мейнтененс специфічний, щось не по пайплайну/шаблону, а тут раптом треба це через гіт робити бо інакше не гітопс

Один тип людей їдять гітопс-кактус і роблять якісь автоматизації щоб було автоматизовано й все в гіт, я ложу болт на гітопс і роблю як зручніше по ситуації. Особливо цим страждають коли збирається ціла команда воршиперів — не перепреш

Яскравий приклад — якісь maintenance windows, коли тре просто застопать синхронізацію кластера, зара вже це підтримується з горем пополам хоч і не без костилів, але раніше зовсім ні. Ну і прикладів є багато. K8s то все ж не лише про деплой контейнерів

github.com/...​oproj/argo-cd/issues/4808
До речі той момент де мені флюкс сподобався, там таке можна. Але для мене це був в кінці не дуже вагомий плюс

Плюс «через гіт» зазвичай означає «через ревю, через Ci/CD і через тридев’яте царство». Тут теж як плюси так і мінуси є, особливо коли тре зробити щось ультратерміново

Гітопс просто юна ідеалістична філософія, проривна, але тим не менш не ідеальна і не без мінусів. Якщо виходіть зручніше десь класти болт на неї — треба так і робити нмд

Дякую, за розлогий коментар.
у нас тіма без девопса, отже робили власноруч для себе як розуміємо і як воно у нас працює.

Та все толково, стаття класна, просто це вже зроблено от і все :) як так гут, то й гут, хтось там вище написав що якусь приблуду прикрутити завжди можна пізніше — прав

скоріше загалом на gitops вже бочку кочу

От ваш такий юзкейс, то в принципі таких юзкейсів 9 з 10, я такої думки що ось такого класу тулзи захоплять майбутнє, а зовсім не гітопс

github.com/kubero-dev/kubero

Щось такого порядку, що автоматом грабає ваші репозиторії, автоматично все збирає навіть без докерфайлів, і автоматом все деплоїть без всяких gitops, можливо з якимось low/no code.

Heroku вдома ткскзть. Але це поки досить юна область, і звісно вона не покриє всі потреби й кейси роботи в к8s. Але розробку — покриє. Ну це вже правда спекуляції й фантазії на гущі :))

Підписатись на коментарі