Выдаляем састарэлую 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 канфігурацыі выглядае так:
Увогуле, я напісаў 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'і ў кластар, можна пачытаць тут и тут.
Матывацыя
Давайце паглядзім на тыповы жыццёвы цыкл pull request'a з бесперапыннай інтэграцыяй (continuous integration):
Пушым новы commit у галінку.
На білдзе, запускаюцца лінтэры і/або тэсты.
На лёце фармуюцца канфігурацыі Kubernetes pull request'a (напрыклад, у гатовы шаблон падстаўляецца яго нумар).
З дапамогай kubectl apply канфігурацыі пападаюць у кластар (deploy).
Pull request зліваецца ў master галінку.
Пакуль вы працуеце ў pull request'е, кожны новы commit бягучы deploy для старога кода выдаляецца, а новы deploy для новага кода выкочваецца. Але калі pull request зліваецца ў master галінку, будзе білдзіцца толькі master галінка. У выніку атрымліваецца, што пра pull request мы ўжо забыліся, а ягоныя Kubernetes рэсурсы ўсё яшчэ знаходзяцца ў кластары.
Параметр 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 минутам.
кубектль - Інтэрфейс каманднага радка для кіравання кластарам.
Падымаем 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-асяроддзя:
Так як 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 get pods --namespace stale-feature-branch-operator
NAME ... STATUS ... AGE
stale-feature-branch-operator-6bfbfd4df8-m7sch ... Running ... 38s
Калі зазірнуць у яго логі, ён гатовы апрацоўваць рэсурсы StaleFeatureBranch:
Аператар адрэагаваў і гатовы правяраць 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 некалькі разоў і пераканацца, што яны будуць выдаленыя на працягу хвіліны.
альтэрнатывы
Што можна зрабіць замест аператара, які працуе ў кластары? Падыходаў некалькі, усе яны неідэальныя (і іх недахопы суб'ектыўныя), і кожны сам вырашае што лепш за ўсё падыдзе на пэўным праекце:
Выдаляць feature branch падчас білда бесперапыннай інтэграцыі master галінкі.
Для гэтага трэба ведаць які pull request адносіцца да commit'у, які білдзіцца. Бо feature branch namespace утрымоўвае ў сабе ідэнтыфікатар pull request'a - яго нумар, ці назоў галінкі, ідэнтыфікатар заўсёды прыйдзецца паказваць у commit'e.
Білды master галінак фейлят. Напрыклад, у вас наступныя этапы: спампаваць праект, запусціць тэсты, сабраць праект, зрабіць рэліз, адправіць апавяшчэнні, ачысціць feature branch апошняга pull request'a. Калі білд сфейліцца на адпраўцы апавяшчэння, вам прыйдзецца выдаляць усе рэсурсы ў кластары рукамі.
Без належнага кантэксту, выдаленне feature branch'і ў master білдзе невідавочна.
Магчыма, гэта не ваш падыход. Напрыклад, у Джэнкінс, толькі адзін від пайплайна падтрымлівае магчымасць захоўваць яго канфігурацыі ў зыходным кодзе. Пры выкарыстанні webhook'аў трэба напісаць свой скрыпт для іх апрацоўкі. Гэты скрыпт давядзецца размяшчаць у інтэрфейсе Jenkins'а, што цяжка падтрымліваць.