Видаляємо застарілу feature branch в кластері Kubernetes

Видаляємо застарілу feature branch в кластері Kubernetes

Привіт! Feature branch (aka deploy preview, review app) - це коли деплоїться не тільки master гілка, а й кожен pull request на унікальний URL. Можна перевірити чи працює код у production-оточенні, фічу можна показати іншим програмістам чи продуктологам. Поки ви працюєте в pull request'і, кожен новий commit поточний deploy для старого коду видаляється, а новий deploy для нового коду викочується. Питання можуть виникнути тоді, коли ви смердили pull request в master гілку. Feature branch вам більше не потрібна, але ресурси Kubernetes все ще знаходяться у кластері.

Ще про feature branch'і

Один з підходів як зробити feature branch'і в Kubernetes - використовувати namespace'и. Якщо коротко, production конфігурації виглядає так:

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end
spec:
  replicas: 3
...

Для feature branch створюється namespace з її ідентифікатором (наприклад, номер pull request'а) і якимось префіксом/постфіксом (наприклад, -pr-):

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end-pr-17
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end-pr-17
spec:
  replicas: 1
...

Загалом, я написав Оператор Kubernetes (Додаток, який має доступ до ресурсів кластера), посилання на проект на Github. Він видаляє namespace'и, які відносяться до старих feature branch'ам. У Kubernetes, якщо видалити namespace, інші ресурси в namespace також видаляються автоматично.

$ kubectl get pods --all-namespaces | grep -e "-pr-"
NAMESPACE            ... AGE
habr-back-end-pr-264 ... 4d8h
habr-back-end-pr-265 ... 5d7h

Про те як впровадити feature branch'і в кластер, можна почитати тут и тут.

Мотивація

Давайте подивимося на типовий життєвий цикл pull request'a з безперервною інтеграцією (continuous integration):

  1. Пухаємо новий commit у гілку.
  2. На білді, запускаються лінтери та/або тести.
  3. На льоту формуються конфігурації Kubernetes pull request'a (наприклад, готовий шаблон підставляється його номер).
  4. За допомогою kubectl apply конфігурації потрапляють у кластер (deploy).
  5. Pull request зливається в master гілку.

Поки ви працюєте в pull request'і, кожен новий commit поточний deploy для старого коду видаляється, а новий deploy для нового коду викочується. Але коли pull request зливається в master гілку, буде білдиться тільки master гілка. У результаті виходить, що про pull request ми вже забули, а його ресурси Kubernetes все ще знаходяться в кластері.

Як використовувати

Встановити проект командою нижче:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml

Створити файл з наступним змістом та встановити через kubectl apply -f:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 3

Параметр namespaceSubstring потрібен, щоб відфільтрувати namespace'и для pull request'ів від інших namespace'ів. Наприклад, якщо в кластері є такі namespace'и: habr-back-end, habr-front-end, habr-back-end-pr-17, habr-back-end-pr-33, тоді кандидатами на вилучення будуть habr-back-end-pr-17, habr-back-end-pr-33.

Параметр afterDaysWithoutDeploy потрібно, щоб видаляти старі namespace'и. Наприклад, якщо namespace створено 3 дня 1 час назад, а в параметрі вказано 3 дня, цей namespace буде видалено. Працює і у зворотний бік, якщо namespace створено 2 дня 23 часа назад, а в параметрі вказано 3 дня, цей namespace не буде видалено.

Є ще один параметр, він відповідає за те, як часто сканувати всі namespace'и і перевіряти на дні без deploy'я. checkEveryMinutes. За умовчанням він дорівнює 30 минутам.

Як це працює

На практиці знадобиться:

  1. Docker для роботи в ізольованому оточенні.
  2. Мінікубе підніме Kubernetes кластер локально.
  3. кубектл - Інтерфейс командного рядка для керування кластером.

Піднімаємо Kubernetes кластер локально:

$ minikube start --vm-driver=docker
minikube v1.11.0 on Darwin 10.15.5
Using the docker driver based on existing profile.
Starting control plane node minikube in cluster minikube.

вказуємо kubectl використовувати локальний кластер за замовчуванням:

$ kubectl config use-context minikube
Switched to context "minikube".

Завантажуємо конфігурації для production-середовища:

$ curl https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml > stale-feature-branch-production-configs.yml

Так як production конфігурації налаштовані перевіряти старі namespace'и, а в нашому ново піднятому кластері їх немає, замінимо змінну оточення IS_DEBUG на true. При такому значенні параметр afterDaysWithoutDeploy не враховується і namespace'и не перевіряються на дні без deploy'я, тільки на входження підрядка (-pr-).

Якщо ви на Linux:

$ sed -i 's|false|true|g' stale-feature-branch-production-configs.yml

Якщо ви на macOS:

$ sed -i "" 's|false|true|g' stale-feature-branch-production-configs.yml

Встановлюємо проект:

$ kubectl apply -f stale-feature-branch-production-configs.yml

Перевіряємо, що у кластері з'явився ресурс StaleFeatureBranch:

$ kubectl api-resources | grep stalefeaturebranches
NAME                 ... APIGROUP                             ... KIND
stalefeaturebranches ... feature-branch.dmytrostriletskyi.com ... StaleFeatureBranch

Перевіряємо, що у кластері з'явився оператор:

$ kubectl get pods --namespace stale-feature-branch-operator
NAME                                           ... STATUS  ... AGE
stale-feature-branch-operator-6bfbfd4df8-m7sch ... Running ... 38s

Якщо заглянути до його логів, він готовий обробляти ресурси StaleFeatureBranch:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Operator Version: 0.0.1"}
...
... "msg":"Starting EventSource", ... , "source":"kind source: /, Kind="}
... "msg":"Starting Controller", ...}
... "msg":"Starting workers", ..., "worker count":1}

Встановлюємо готові fixtures (готові конфігурації для моделювання ресурсів кластера) для ресурсу StaleFeatureBranch:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/stale-feature-branch.yml

У конфігураціях вказано шукати namespace'и з підрядком -pr- раз в 1 минуту.:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 1 
  checkEveryMinutes: 1

Оператор відреагував і готовий перевіряти namespace'и:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Stale feature branch is being processing.","namespaceSubstring":"-pr-","afterDaysWithoutDeploy":1,"checkEveryMinutes":1,"isDebug":"true"}

Встановлюємо fixtures, що містять два namespace'а (project-pr-1, project-pr-2) та їх deployments, services, ingress, і так далі:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/first-feature-branch.yml -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/second-feature-branch.yml
...
namespace/project-pr-1 created
deployment.apps/project-pr-1 created
service/project-pr-1 created
horizontalpodautoscaler.autoscaling/project-pr-1 created
secret/project-pr-1 created
configmap/project-pr-1 created
ingress.extensions/project-pr-1 created
namespace/project-pr-2 created
deployment.apps/project-pr-2 created
service/project-pr-2 created
horizontalpodautoscaler.autoscaling/project-pr-2 created
secret/project-pr-2 created
configmap/project-pr-2 created
ingress.extensions/project-pr-2 created

Перевіряємо, що всі ресурси вище успішно створені:

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...
NAME                              ... READY ... STATUS  ... AGE
pod/project-pr-1-848d5fdff6-rpmzw ... 1/1   ... Running ... 67s

NAME                         ... READY ... AVAILABLE ... AGE
deployment.apps/project-pr-1 ... 1/1   ... 1         ... 67s
...

Тому що ми включили debug, namespace'и project-pr-1 и project-pr-2, отже і всі інші ресурси, повинні будуть відразу видалитись не враховуючи параметр afterDaysWithoutDeploy. У логах оператора видно:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-1"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-1","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-1"}
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-2"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-2","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-2"}

Якщо перевірити наявність ресурсів, вони будуть у статусі Terminating (процес видалення) або вже видалені (висновок команди порожній).

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...

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

Альтернативи

Що можна зробити замість оператора, який працює у кластері? Підходів кілька, всі вони неідеальні (і їхні недоліки суб'єктивні), і кожен сам вирішує, що найкраще підійде на конкретному проекті:

  1. Видалити feature branch під час білда безперервної інтеграції master гілки.

    • Для цього треба знати, який pull request відноситься до commit'у, який білдиться. Оскільки feature branch namespace містить ідентифікатор pull request'a — його номер, або назву гілки, ідентифікатор завжди доведеться вказувати в commit'e.
    • Білди master гілок фейля. Наприклад, у вас такі етапи: завантажити проект, запустити тести, зібрати проект, зробити реліз, надіслати повідомлення, очистити feature branch останнього pull request'a. Якщо білд з'явиться на надсиланні повідомлення, вам доведеться видаляти всі ресурси в кластері руками.
    • Без належного контексту, видалення feature branch'і у master білді неочевидне.

  2. Використання webhook'ів (приклад).

    • Можливо це не ваш підхід. Наприклад, в Дженкінс, лише один вид пайплайну підтримує можливість зберігати його конфігурації у вихідному коді. При використанні webhook'ів потрібно написати свій скрипт для їхньої обробки. Цей скрипт доведеться розміщувати в інтерфейсі Jenkins'а, що важко підтримувати.

  3. Написати Кронджоб та додати Kubernetes кластер.

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

Дякую за увагу до статті. Посилання на проект на Github.

Джерело: habr.com

Додати коментар або відгук