Tshem tawm ib qho kev ua haujlwm tsis tu ncua hauv Kubernetes pawg

Tshem tawm ib qho kev ua haujlwm tsis tu ncua hauv Kubernetes pawg

Nyob zoo! 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 c ее идентификатором (например, номер 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 Operator (приложение, которое имеет доступ к ресурсам кластера), ссылка на проект на 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’и в кластер, можно почитать S, SѓS, и S, SѓS,.

Kev Txhawb

Давайте посмотрим на типичный жизненный цикл 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 ресурсы все еще находятся в кластере.

Yuav siv li cas

Установить проект командой ниже:

$ 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

Parameter 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.

Parameter afterDaysWithoutDeploy нужен чтобы, удалять старые namespace’ы. Например, если namespace создан 3 дня 1 час назад, а в параметре указано 3 дня, этот namespace будет удален. Работает и в обратную сторону, если namespace создан 2 дня 23 часа назад, а в параметре указано 3 дня, этот namespace не будет удален.

Есть еще один параметр, он отвечает за то как часто сканировать все namespace’ы и проверять на дни без deploy’я — checkEveryMinutes. По умолчанию он равен 30 минутам.

Yuav ua li cas li no ua hauj lwm

На практике, понадобится:

  1. docker для работы в изолированном окружении.
  2. Minikube поднимет Kubernetes кластер локально.
  3. kubtl ua — интерфейс командной строки для управления кластером.

Поднимаем 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.

Peb qhia tau 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 rau true. При таком значении параметр afterDaysWithoutDeploy не учитывается и namespace’ы не проверяются на дни без deploy’я, только на вхождение подстроки (-pr-).

Yog koj nyob rau Linux:

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

Yog koj nyob rau 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- ib zaug hauv 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"}

Nruab 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 несколько раз и убедиться, что они будут удалены в течение минуты.

Lwm yam

Что можно сделать вместо оператора, который работает в кластере? Подходов несколько, все они неидеальны (и их недостатки субъективны), и каждый сам решает что лучше всего подойдет на конкретном проекте:

  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’ов (Piv txwv).

    • Возможно, это не ваш подход. Например, в Jenkins, только один вид пайплайна поддерживает возможность сохранять его конфигурации в исходном коде. При использовании webhook’ов нужно написать свой скрипт для их обработки. Этот скрипт придется размещать в интерфейсе Jenkins’а, что трудно поддерживать.

  3. Sau Cronjob и добавить Kubernetes кластер.

    • Затрата времени на написание и поддержку.
    • Оператор уже работает в подобном стиле, задокументирован и поддерживается.

Спасибо за внимание к статье. Ссылка на проект на Github.

Tau qhov twg los: www.hab.com

Ntxiv ib saib