Usuwamy przestarzałą gałąź funkcji w klastrze Kubernetes

Usuwamy przestarzałą gałąź funkcji w klastrze Kubernetes

Cześć! Gałąź funkcjonalna (znana jako podgląd wdrożenia, aplikacja przeglądowa) — to sytuacja, w której wdrażany jest nie tylko główny branch, ale także każdy pull request pod unikalnym URL. Można sprawdzić, czy kod działa w środowisku produkcyjnym, a funkcję można zaprezentować innym programistom lub produktowcom. Gdy pracujesz w pull request’cie, każdy nowy commit aktualizuje wdrożenie dla starego kodu, a nowe wdrożenie dla nowego kodu jest publikowane. Problemy mogą się pojawić, gdy zintegrowałeś pull request z głównym branchem. Gałąź funkcjonalna nie jest już potrzebna, ale zasoby Kubernetes wciąż pozostają w klastrze.

Więcej o gałęziach funkcjonalnych

Jednym z podejść do tworzenia gałęzi funkcjonalnych w Kubernetes jest użycie namespace’ów. W skrócie, konfiguracje produkcyjne wyglądają następująco:

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

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

Dla gałęzi funkcjonalnej tworzy się namespace z jej identyfikatorem (na przykład numer pull request’a) i jakimś prefiksem/podpisem (na przykład, -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
...

Ogólnie rzecz biorąc, napisałem Operator Kubernetes (aplikacja, która ma dostęp do zasobów klastra), link do projektu na Githubie. Usuwa namespace’y związane ze starymi gałęziami funkcjonalnymi. W Kubernetes, jeśli usuniesz namespace, inne zasoby w tym namespace również są automatycznie usuwane.

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

O tym, jak wdrożyć gałęzie funkcjonalne w klastrze, można przeczytać tutaj i tutaj.

Motywacja

Przyjrzyjmy się typowemu cyklowi życia pull request’a z ciągłą integracją (ciągła integracja):

  1. Wysyłamy nowy commit do gałęzi.
  2. Podczas budowy uruchamiane są linty i/lub testy.
  3. Na bieżąco tworzone są konfiguracje Kubernetes dla pull request’a (na przykład numer jest podstawiany w gotowy szablon).
  4. Za pomocą kubectl apply konfiguracje trafiają do klastra (wdrożenie).
  5. Pull request jest scalany z gałęzią główną.

Podczas pracy w pull request’cie każdy nowy commit aktualizuje wdrożenie dla starego kodu, a nowe wdrożenie dla nowego kodu jest publikowane. Jednak gdy pull request jest scalany z gałęzią główną, będzie budowany tylko główny branch. W efekcie możemy zapomnieć o pull request’cie, podczas gdy jego zasoby Kubernetes wciąż pozostają w klastrze.

Jak używać

Zainstaluj projekt poniższą komendą:

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

Utwórz plik z następującą zawartością i zainstaluj za pomocą kubectl apply -f:

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

Parametr namespaceSubstring jest potrzebny, aby filtrować namespace'y dla pull requestów z innych namespace'y. Na przykład, jeśli w klastrze są następujące namespace'y: habr-back-end, habr-front-end, habr-back-end-pr-17, habr-back-end-pr-33, wtedy kandydatami do usunięcia będą habr-back-end-pr-17, habr-back-end-pr-33.

Parametr afterDaysWithoutDeploy jest potrzebny, aby usuwać stare namespace'y. Na przykład, jeśli namespace został utworzony 3 dni 1 godzina temu, a w parametrze wskazano 3 dni, ten namespace zostanie usunięty. Działa to również w drugą stronę, jeśli namespace został utworzony 2 dni 23 godziny temu, a w parametrze wskazano 3 dni, ten namespace nie zostanie usunięty.

Jest jeszcze jeden parametr, który odpowiada za to, jak często skanować wszystkie namespace'y i sprawdzać dni bez deploy’a — checkEveryMinutes. Domyślnie wynosi 30 minut.

Jak to działa

W praktyce, potrzebne będzie:

  1. Docker do pracy w izolowanym środowisku.
  2. Minikube uruchomi lokalnie klaster Kubernetes.
  3. kubectl — interfejs wiersza poleceń do zarządzania klastrem.

Uruchamiamy lokalnie klaster Kubernetes:

$ minikube start --vm-driver=docker
minikube v1.11.0 na Darwin 10.15.5
Używając sterownika docker opartego na istniejącym profilu.
Rozpoczynanie węzła kontrolnego minikube w klastrze minikube.

Ustawiamy kubectl użyj lokalnego klastra domyślnie:

$ kubectl config use-context minikube
Przełączono kontekst na "minikube".

Pobieramy konfiguracje dla środowiska produkcyjnego:

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

Ponieważ produkcyjne konfiguracje są skonfigurowane do sprawdzania starych namespace'ów, a w naszym nowo uruchomionym klastrze ich nie ma, zmienimy zmienną środowiskową IS_DEBUG na true. Przy takim ustawieniu parametr afterDaysWithoutDeploy nie jest brany pod uwagę i namespace'y nie są sprawdzane pod kątem dni bez deploy’a, tylko na obecność podciągu (-pr-).

Jeśli jesteś na Linuxa:

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

Jeśli jesteś na macOS:

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

Instalujemy projekt:

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

Sprawdzamy, czy w klastrze pojawił się zasób StaleFeatureBranch:

$ kubectl api-resources | grep stalefeaturebranches
NAZWA                 ... APIGROUP                             ... RODZAJ
stalefeaturebranches ... feature-branch.dmytrostriletskyi.com ... StaleFeatureBranch

Sprawdzamy, czy w klastrze pojawił się operator:

$ kubectl get pods --namespace stale-feature-branch-operator
NAZWA                                           ... STATUS  ... WIEK
stale-feature-branch-operator-6bfbfd4df8-m7sch ... Działa ... 38s

Jeśli zajrzymy do jego logów, jest gotowy do przetwarzania zasobów StaleFeatureBranch:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Wersja operatora: 0.0.1"}
...
... "msg":"Rozpoczynam źródło zdarzeń", ... , "source":"rodzaj źródła: /, Rodzaj="}
... "msg":"Rozpoczynam kontrolera", ...}
... "msg":"Rozpoczynam pracowników", ..., "liczba pracowników":1}

Instalujemy gotowe fixtures (gotowe konfiguracje do modelowania zasobów klastra) dla zasobu StaleFeatureBranch:

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

W konfiguracjach wskazano, aby szukać namespace’ów zawierających podciąg -pr- raz na 1 minutę.:

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

Operator zareagował i jest gotowy do sprawdzenia namespace’ów:

$ 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"}

Instalujemy fixtures, zawierających dwa namespace’y (project-pr-1, project-pr-2) i ich deployments, services, ingress, i tak dalej:

$ 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

Sprawdzamy, czy wszystkie powyższe zasoby zostały pomyślnie utworzone:

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

Ponieważ włączyliśmy debug, namespace’y project-pr-1 i project-pr-2, co oznacza, że wszystkie pozostałe zasoby powinny zostać natychmiast usunięte niezależnie od parametru afterDaysWithoutDeploy. W logach operatora widać to:

$ 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"}

Jeśli sprawdzimy dostępność zasobów, będą one w statusie Terminating (proces usuwania) lub już usunięte (wyjście z polecenia puste).

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

Możesz powtórzyć proces tworzenia fixtures Kilka razy upewnij się, że zostaną usunięte w ciągu minuty.

Alternatywy

Co można zrobić zamiast operatora działającego w klastrze? Istnieje kilka podejść, wszystkie są niedoskonałe (ich wady są subiektywne), a każdy sam decyduje, co najlepiej pasuje do konkretnego projektu:

  1. Usunąć gałąź feature podczas budowy ciągłej integracji gałęzi master.

    • Aby to zrobić, należy wiedzieć, który pull request dotyczy commit’u, który jest budowany. Ponieważ przestrzeń nazw gałęzi feature zawiera identyfikator pull request’a — jego numer lub nazwę gałęzi, identyfikator zawsze trzeba będzie podać w commit’cie.
    • Budowy gałęzi master kończą się niepowodzeniem. Na przykład, masz następujące etapy: pobierz projekt, uruchom testy, zbuduj projekt, wydaj wersję, wyślij powiadomienia, oczyść gałąź feature ostatniego pull request’a. Jeśli budowa zakończy się niepowodzeniem podczas wysyłania powiadomień, będziesz musiał ręcznie usunąć wszystkie zasoby w klastrze.
    • Bez odpowiedniego kontekstu, usunięcie gałęzi feature w budowie master nie jest oczywiste.

  2. Wykorzystanie webhooków (przykład).

    • Może to nie jest twoje podejście. Na przykład w Jenkins, tylko jeden rodzaj pipeline’u obsługuje możliwość przechowywania jego konfiguracji w kodzie źródłowym. Przy użyciu webhooków należy napisać własny skrypt do ich przetwarzania. Skrypt ten będzie trzeba umieścić w interfejsie Jenkins’a, co jest trudne do utrzymania.

  3. Napisać Cronjob i dodać klaster Kubernetes.

    • Czas poświęcony na pisanie i utrzymanie.
    • Operator już działa w podobnym stylu, jest udokumentowany i wspierany.

Dziękuję za uwagę do artykułu. Link do projektu na Githubie.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster