{"id":86902,"date":"2020-07-01T07:42:28","date_gmt":"2020-07-01T05:42:28","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere"},"modified":"2020-07-01T07:42:28","modified_gmt":"2020-07-01T05:42:28","slug":"udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere","title":{"rendered":"Usuwamy przestarza\u0142\u0105 ga\u0142\u0105\u017a funkcji w klastrze Kubernetes","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Usuwamy przestarza\u0142\u0105 ga\u0142\u0105\u017a funkcji w klastrze Kubernetes\" src=\"\/wp-content\/uploads\/2020\/07\/d97c0cce385c587e16f26f6d19b9d108.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p>Cze\u015b\u0107! <b>Ga\u0142\u0105\u017a funkcjonalna<\/b> (aka deploy preview, review app) \u2014 to sytuacja, gdy deploy odbywa si\u0119 nie tylko dla ga\u0142\u0119zi master, ale tak\u017ce dla ka\u017cdego pull requestu na unikalnym URL. Mo\u017cna sprawdzi\u0107, czy kod dzia\u0142a w \u015brodowisku produkcyjnym, a funkcj\u0119 mo\u017cna pokaza\u0107 innym programistom lub produktologom. Podczas pracy nad pull requestem, ka\u017cdy nowy commit aktualizuje bie\u017c\u0105cy deploy dla starego kodu, a nowy deploy dla nowego kodu jest wdra\u017cany. Pytania mog\u0105 si\u0119 pojawi\u0107, gdy po\u0142\u0105czysz pull request z ga\u0142\u0119zi\u0105 master. Ga\u0142\u0105\u017a funkcjonalna nie jest ju\u017c potrzebna, ale zasoby Kubernetes wci\u0105\u017c znajduj\u0105 si\u0119 w klastrze.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Jeszcze o ga\u0142\u0119ziach funkcjonalnych<\/h2>\n<p><\/p>\n<p>Jednym ze sposob\u00f3w na tworzenie ga\u0142\u0119zi funkcjonalnych w Kubernetes jest u\u017cycie namespace'\u00f3w. W skr\u00f3cie, konfiguracja produkcyjna wygl\u0105da tak:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">kind: Namespace\napiVersion: v1\nmetadata:\n  name: habr-back-end\n...\n\nkind: Deployment\napiVersion: apps\/v1\nmetadata:\n  namespace: habr-back-end\nspec:\n  replicas: 3\n...<\/code><\/pre>\n<p><\/p>\n<p>Dla ga\u0142\u0119zi funkcjonalnej tworzony jest namespace z jej identyfikatorem (na przyk\u0142ad numer pull requestu) oraz jakim\u015b prefiksem\/podpisem (na przyk\u0142ad, <b>-pr-<\/b>):<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">kind: Namespace\napiVersion: v1\nmetadata:\n  name: habr-back-end-pr-17\n...\n\nkind: Deployment\napiVersion: apps\/v1\nmetadata:\n  namespace: habr-back-end-pr-17\nspec:\n  replicas: 1\n...<\/code><\/pre>\n<p><\/p>\n<p>Og\u00f3lnie rzecz bior\u0105c, napisa\u0142em <b>Operator Kubernetes<\/b> (aplikacja, kt\u00f3ra ma dost\u0119p do zasob\u00f3w klastra), <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/dmytrostriletskyi\/stale-feature-branch-operator\">link do projektu na Githubie<\/a><\/noindex>. Usuwa namespace'y, kt\u00f3re odnosz\u0105 si\u0119 do starych ga\u0142\u0119zi funkcjonalnych. W Kubernetes, je\u015bli usuniesz namespace, inne zasoby w tym namespace r\u00f3wnie\u017c zostan\u0105 automatycznie usuni\u0119te.<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl get pods --all-namespaces | grep -e \"-pr-\"\nNAMESPACE            ... AGE\nhabr-back-end-pr-264 ... 4d8h\nhabr-back-end-pr-265 ... 5d7h<\/code><\/pre>\n<p><\/p>\n<p>O tym, jak wdro\u017cy\u0107 ga\u0142\u0119zie funkcjonalne w klastrze, mo\u017cna przeczyta\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/itnext.io\/feature-deployments-in-kubernetes-c74bdcff0d8e\">tutaj<\/a><\/noindex> i <noindex><a rel=\"nofollow\" href=\"https:\/\/codefresh.io\/kubernetes-tutorial\/dynamically-creating-k8s-namespaces-every-branch-pull-request-2\">tutaj<\/a><\/noindex>.<\/p>\n<p><\/p>\n<h2>Motywacja<\/h2>\n<p><\/p>\n<p>Zobaczmy typowy cykl \u017cycia pull requestu z ci\u0105g\u0142\u0105 integracj\u0105 (<code>ci\u0105g\u0142a integracja<\/code>):<\/p>\n<p><\/p>\n<ol>\n<li>Wysy\u0142amy nowy commit do ga\u0142\u0119zi.<\/li>\n<li>Podczas budowy uruchamiane s\u0105 linty i\/lub testy.<\/li>\n<li>Na bie\u017c\u0105co formu\u0142owane s\u0105 konfiguracje Kubernetes dla pull requestu (na przyk\u0142ad do gotowego szablonu podstawiany jest jego numer).<\/li>\n<li>Za pomoc\u0105 kubectl apply konfiguracje trafiaj\u0105 do klastra (wdro\u017cenie).<\/li>\n<li>Pull request jest scalany z ga\u0142\u0119zi\u0105 g\u0142\u00f3wn\u0105.<\/li>\n<\/ol>\n<p><\/p>\n<p>Podczas pracy nad pull requestem, ka\u017cdy nowy commit aktualizuje bie\u017c\u0105cy deploy dla starego kodu, a nowy deploy dla nowego kodu jest wdra\u017cany. Jednak gdy pull request zostaje po\u0142\u0105czony z ga\u0142\u0119zi\u0105 master, b\u0119dzie budowana tylko ga\u0142\u0105\u017a master. W rezultacie zapominamy o pull requestcie, a jego zasoby Kubernetes wci\u0105\u017c znajduj\u0105 si\u0119 w klastrze.<\/p>\n<p><\/p>\n<h2>Jak u\u017cywa\u0107<\/h2>\n<p><\/p>\n<p>Zainstaluj projekt poni\u017csz\u0105 komend\u0105:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl apply -f https:\/\/raw.githubusercontent.com\/dmytrostriletskyi\/stale-feature-branch-operator\/master\/configs\/production.yml<\/code><\/pre>\n<p><\/p>\n<p>Utw\u00f3rz plik z nast\u0119puj\u0105c\u0105 zawarto\u015bci\u0105 i zainstaluj za pomoc\u0105 <code>kubectl apply -f<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">apiVersion: feature-branch.dmytrostriletskyi.com\/v1\nkind: StaleFeatureBranch\nmetadata:\n  name: stale-feature-branch\nspec:\n  namespaceSubstring: -pr-\n  afterDaysWithoutDeploy: 3<\/code><\/pre>\n<p><\/p>\n<p>Parametr <b>namespaceSubstring<\/b> jest potrzebny, aby filtrowa\u0107 namespace'y dla pull request\u00f3w od innych namespace'\u00f3w. Na przyk\u0142ad, je\u015bli w klastrze s\u0105 nast\u0119puj\u0105ce namespace'y: <code>habr-back-end<\/code>, <code>habr-front-end<\/code>, <code>habr-back-end-pr-17<\/code>, <code>habr-back-end-pr-33<\/code>, wtedy kandydatami do usuni\u0119cia b\u0119d\u0105 <code>habr-back-end-pr-17<\/code>, <code>habr-back-end-pr-33<\/code>.<\/p>\n<p><\/p>\n<p>Parametr <b>afterDaysWithoutDeploy<\/b> jest potrzebny, aby usuwa\u0107 stare namespace'y. Na przyk\u0142ad, je\u015bli namespace zosta\u0142 stworzony <code>3 dni 1 godzina<\/code> temu, a w parametrze wskazano <code>3 dni<\/code>, ten namespace zostanie usuni\u0119ty. Dzia\u0142a to r\u00f3wnie\u017c w drug\u0105 stron\u0119, je\u015bli namespace zosta\u0142 utworzony <code>2 dni 23 godziny<\/code> temu, a w parametrze wskazano <code>3 dni<\/code>, ten namespace nie zostanie usuni\u0119ty.<\/p>\n<p><\/p>\n<p>Jest jeszcze jeden parametr, kt\u00f3ry odpowiada za to, jak cz\u0119sto skanowa\u0107 wszystkie namespace'y i sprawdza\u0107 dni bez deploy'u \u2014 <b>checkEveryMinutes<\/b>. Domy\u015blnie wynosi <code>30 minut<\/code>.<\/p>\n<p><\/p>\n<h2>Jak to dzia\u0142a<\/h2>\n<p><\/p>\n<p>W praktyce, potrzebne b\u0119dzie:<\/p>\n<p><\/p>\n<ol>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/get-docker\">Docker<\/a><\/noindex> do pracy w izolowanym \u015brodowisku.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/tools\/install-minikube\">Minikube<\/a><\/noindex> uruchomi lokalnie klaster Kubernetes.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/kubernetes.io\/docs\/tasks\/tools\/install-kubectl\">kubectl<\/a><\/noindex> \u2014 interfejs wiersza polece\u0144 do zarz\u0105dzania klastrem.<\/li>\n<\/ol>\n<p><\/p>\n<p>Uruchamiamy lokalnie klaster Kubernetes:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ minikube start --vm-driver=docker\nminikube v1.11.0 na Darwin 10.15.5\nU\u017cywaj\u0105c sterownika docker opartego na istniej\u0105cym profilu.\nRozpoczynanie w\u0119z\u0142a kontrolnego minikube w klastrze minikube.<\/code><\/pre>\n<p><\/p>\n<p>Ustawiamy <code>kubectl<\/code> u\u017cyj lokalnego klastra domy\u015blnie:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl config use-context minikube\nPrze\u0142\u0105czono na kontekst \"minikube\".<\/code><\/pre>\n<p><\/p>\n<p>Pobieramy konfiguracje dla \u015brodowiska produkcyjnego:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ curl https:\/\/raw.githubusercontent.com\/dmytrostriletskyi\/stale-feature-branch-operator\/master\/configs\/production.yml &gt; stale-feature-branch-production-configs.yml<\/code><\/pre>\n<p><\/p>\n<p>Poniewa\u017c konfiguracje produkcyjne s\u0105 skonfigurowane do sprawdzania starych namespace'\u00f3w, a w naszym nowo utworzonym klastrze ich nie ma, zmie\u0144my zmienn\u0105 \u015brodowiskow\u0105 <code>IS_DEBUG<\/code> na <code>true<\/code>. Przy takim ustawieniu parametr <code>afterDaysWithoutDeploy<\/code> nie jest brane pod uwag\u0119, a namespace'y nie s\u0105 sprawdzane na dni bez deploy'u, tylko na obecno\u015b\u0107 podci\u0105gu (<code>-pr-<\/code>).<\/p>\n<p><\/p>\n<p>Je\u015bli jeste\u015b na <code>Linuxa<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ sed -i 's|false|true|g' stale-feature-branch-production-configs.yml<\/code><\/pre>\n<p><\/p>\n<p>Je\u015bli jeste\u015b na <code>macOS<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ sed -i \"\" 's|false|true|g' stale-feature-branch-production-configs.yml<\/code><\/pre>\n<p><\/p>\n<p>Instalujemy projekt:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl apply -f stale-feature-branch-production-configs.yml<\/code><\/pre>\n<p><\/p>\n<p>Sprawdzamy, czy w klastrze pojawi\u0142 si\u0119 zas\u00f3b <code>StaleFeatureBranch<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl api-resources | grep stalefeaturebranches\nNAZWA                 ... APIGROUP                             ... RODZAJ\nstalefeaturebranches ... feature-branch.dmytrostriletskyi.com ... StaleFeatureBranch<\/code><\/pre>\n<p><\/p>\n<p>Sprawdzamy, czy w klastrze pojawi\u0142 si\u0119 operator:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl get pods --namespace stale-feature-branch-operator\nNAZWA                                           ... STATUS  ... WIEK\nstale-feature-branch-operator-6bfbfd4df8-m7sch ... Dzia\u0142a ... 38s<\/code><\/pre>\n<p><\/p>\n<p>Je\u015bli zajrzymy do jego log\u00f3w, jest gotowy do przetwarzania zasob\u00f3w <code>StaleFeatureBranch<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator\n... \"msg\":\"Operator Version: 0.0.1\"}\n...\n... \"msg\":\"Starting EventSource\", ... , \"source\":\"kind source: \/, Kind=\"}\n... \"msg\":\"Starting Controller\", ...}\n... \"msg\":\"Starting workers\", ..., \"worker count\":1}<\/code><\/pre>\n<p><\/p>\n<p>Instalujemy gotowe <code>fixtures<\/code> (gotowe konfiguracje do modelowania zasob\u00f3w klastra) dla zasobu <code>StaleFeatureBranch<\/code>:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl apply -f https:\/\/raw.githubusercontent.com\/dmytrostriletskyi\/stale-feature-branch-operator\/master\/fixtures\/stale-feature-branch.yml<\/code><\/pre>\n<p><\/p>\n<p>W konfiguracjach wskazano, aby wyszukiwa\u0107 namespace'y zawieraj\u0105ce podci\u0105g <code>-pr-<\/code> raz na <code>1 minut\u0119<\/code>.:<\/p>\n<p><\/p>\n<pre><code class=\"plaintext\">apiVersion: feature-branch.dmytrostriletskyi.com\/v1\nkind: StaleFeatureBranch\nmetadata:\n  name: stale-feature-branch\nspec:\n  namespaceSubstring: -pr-\n  afterDaysWithoutDeploy: 1 \n  checkEveryMinutes: 1<\/code><\/pre>\n<p><\/p>\n<p>Operator zareagowa\u0142 i jest gotowy do sprawdzania namespace'\u00f3w:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator\n... \"msg\":\"Stale feature branch jest przetwarzany.\",\"namespaceSubstring\":\"-pr-\",\"afterDaysWithoutDeploy\":1,\"checkEveryMinutes\":1,\"isDebug\":\"true\"}<\/code><\/pre>\n<p><\/p>\n<p>Instalujemy <code>fixtures<\/code>, zawieraj\u0105ce dwa namespace'y (<code>project-pr-1<\/code>, <code>project-pr-2<\/code>) i ich <code>deployments<\/code>, <code>services<\/code>, <code>ingress<\/code>, i tak dalej:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ 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\n...\nnamespace\/project-pr-1 created\ndeployment.apps\/project-pr-1 created\nservice\/project-pr-1 created\nhorizontalpodautoscaler.autoscaling\/project-pr-1 created\nsecret\/project-pr-1 created\nconfigmap\/project-pr-1 created\n ... ingress.extensions\/project-pr-1 created\nnamespace\/project-pr-2 created\ndeployment.apps\/project-pr-2 created\nservice\/project-pr-2 created\nhorizontalpodautoscaler.autoscaling\/project-pr-2 created\nsecret\/project-pr-2 created\nconfigmap\/project-pr-2 created\ningress.extensions\/project-pr-2 created<\/code><\/pre>\n<p><\/p>\n<p>Sprawdzamy, czy wszystkie powy\u017csze zasoby zosta\u0142y pomy\u015blnie utworzone:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 &amp;&amp; kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2\n...\nNAME ... READY ... STATUS ... AGE\npod\/project-pr-1-848d5fdff6-rpmzw ... 1\/1 ... Running ... 67s\n\nNAME ... READY ... AVAILABLE ... AGE\ndeployment.apps\/project-pr-1 ... 1\/1 ... 1 ... 67s\n...<\/code><\/pre>\n<p><\/p>\n<p>Poniewa\u017c w\u0142\u0105czyli\u015bmy <code>debug<\/code>, namespace'y <code>project-pr-1<\/code> i <code>project-pr-2<\/code>, co oznacza, \u017ce wszystkie pozosta\u0142e zasoby powinny zosta\u0107 natychmiast usuni\u0119te niezale\u017cnie od parametru <code>afterDaysWithoutDeploy<\/code>. W logach operatora wida\u0107 to:<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator\n... \"msg\":\"Namespace powinien zosta\u0107 usuni\u0119ty, poniewa\u017c tryb debugowania jest w\u0142\u0105czony.\",\"namespaceName\":\"project-pr-1\"}\n... \"msg\":\"Namespace jest przetwarzany.\",\"namespaceName\":\"project-pr-1\",\"namespaceCreationTimestamp\":\"2020-06-16 18:43:58 +0300 EEST\"}\n... \"msg\":\"Namespace zosta\u0142 usuni\u0119ty.\",\"namespaceName\":\"project-pr-1\"}\n... \"msg\":\"Namespace powinien zosta\u0107 usuni\u0119ty, poniewa\u017c tryb debugowania jest w\u0142\u0105czony.\",\"namespaceName\":\"project-pr-2\"}\n... \"msg\":\"Namespace jest przetwarzany.\",\"namespaceName\":\"project-pr-2\",\"namespaceCreationTimestamp\":\"2020-06-16 18:43:58 +0300 EEST\"}\n... \"msg\":\"Namespace zosta\u0142 usuni\u0119ty.\",\"namespaceName\":\"project-pr-2\"}<\/code><\/pre>\n<p><\/p>\n<p>Je\u015bli sprawdzimy dost\u0119pno\u015b\u0107 zasob\u00f3w, b\u0119d\u0105 one w statusie <code>Terminating<\/code> (proces usuwania) lub ju\u017c usuni\u0119te (wyj\u015bcie z polecenia puste).<\/p>\n<p><\/p>\n<pre><code class=\"bash\">$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 &amp;&amp; kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2\n...<\/code><\/pre>\n<p><\/p>\n<p>Mo\u017cesz powt\u00f3rzy\u0107 proces tworzenia <code>fixtures<\/code> Kilka razy upewnij si\u0119, \u017ce zostan\u0105 usuni\u0119te w ci\u0105gu minuty.<\/p>\n<p><\/p>\n<h2>Alternatywy<\/h2>\n<p><\/p>\n<p>Co mo\u017cna zrobi\u0107 zamiast operatora dzia\u0142aj\u0105cego w klastrze? Istnieje kilka podej\u015b\u0107, wszystkie s\u0105 niedoskona\u0142e (ich wady s\u0105 subiektywne), a ka\u017cdy sam decyduje, co najlepiej pasuje do konkretnego projektu:<\/p>\n<p><\/p>\n<ol>\n<li>\n<p>Usun\u0105\u0107 ga\u0142\u0105\u017a feature podczas budowy ci\u0105g\u0142ej integracji ga\u0142\u0119zi master.<\/p>\n<p><\/p>\n<ul>\n<li>Aby to zrobi\u0107, musisz wiedzie\u0107, kt\u00f3ry pull request dotyczy commit'u, kt\u00f3ry jest budowany. Poniewa\u017c namespace ga\u0142\u0119zi funkcjonalnej zawiera identyfikator pull request'a \u2014 jego numer lub nazw\u0119 ga\u0142\u0119zi, identyfikator zawsze b\u0119dzie musia\u0142 by\u0107 podany w commit'cie.<\/li>\n<li>Budowy ga\u0142\u0119zi master si\u0119 nie udaj\u0105. Na przyk\u0142ad masz nast\u0119puj\u0105ce etapy: pobranie projektu, uruchomienie test\u00f3w, zbudowanie projektu, wydanie, wys\u0142anie powiadomie\u0144, usuni\u0119cie feature branch ostatniego pull request'a. Je\u015bli budowa nie uda si\u0119 podczas wysy\u0142ania powiadomienia, b\u0119dziesz musia\u0142 r\u0119cznie usun\u0105\u0107 wszystkie zasoby w klastrze.<\/li>\n<li>Bez odpowiedniego kontekstu, usuni\u0119cie feature branch w budowie master nie jest oczywiste.<\/li>\n<\/ul>\n<p>\n<\/li>\n<li>\n<p>U\u017cycie webhook'\u00f3w (<noindex><a rel=\"nofollow\" href=\"https:\/\/github.community\/t\/trigger-jenkins-job-when-a-pull-request-is-merged-to-a-branch\/1169\">przyk\u0142ad<\/a><\/noindex>).<\/p>\n<p><\/p>\n<ul>\n<li>Mo\u017ce to nie jest twoje podej\u015bcie. Na przyk\u0142ad w <noindex><a rel=\"nofollow\" href=\"https:\/\/www.jenkins.io\">Jenkins<\/a><\/noindex>, tylko jeden rodzaj pipeline'u obs\u0142uguje mo\u017cliwo\u015b\u0107 przechowywania jego konfiguracji w kodzie \u017ar\u00f3d\u0142owym. Przy u\u017cyciu webhook'\u00f3w musisz napisa\u0107 w\u0142asny skrypt do ich obs\u0142ugi. Ten skrypt b\u0119dziesz musia\u0142 umie\u015bci\u0107 w interfejsie Jenkins'a, co jest trudne do utrzymania.<\/li>\n<\/ul>\n<p>\n<\/li>\n<li>\n<p>Napisa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/cron-jobs\/\">Cronjob<\/a><\/noindex> i doda\u0107 klaster Kubernetes.<\/p>\n<p><\/p>\n<ul>\n<li>Czas po\u015bwi\u0119cony na pisanie i utrzymanie.<\/li>\n<li>Operator ju\u017c dzia\u0142a w podobnym stylu, jest udokumentowany i wspierany.<\/li>\n<\/ul>\n<p>\n<\/li>\n<\/ol>\n<p><\/p>\n<p>Dzi\u0119kuj\u0119 za uwag\u0119 do artyku\u0142u. <strong><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/dmytrostriletskyi\/stale-feature-branch-operator\">Link do projektu na Githubie<\/a><\/noindex><\/strong>.<\/p>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/508534\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438\u0432\u0435\u0442! Feature branch (aka deploy preview, review app) \u2014 \u044d\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e master \u0432\u0435\u0442\u043a\u0430, \u043d\u043e \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 pull request \u043d\u0430 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 URL. \u041c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043b\u0438 \u043a\u043e\u0434 \u0432 production-\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0438, \u0444\u0438\u0447\u0443 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c \u0438\u043b\u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u043b\u043e\u0433\u0430\u043c. \u041f\u043e\u043a\u0430 \u0432\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0435 \u0432 pull request&#8217;\u0435, \u043a\u0430\u0436\u0434\u044b\u0439 \u043d\u043e\u0432\u044b\u0439 commit \u0442\u0435\u043a\u0443\u0449\u0438\u0439 deploy \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f, \u0430 \u043d\u043e\u0432\u044b\u0439 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":86903,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-86902","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442!\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0443\u044e feature branch \u0432 Kubernetes \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0435 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442!\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-07-01T05:42:28+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-07-01T05:42:28+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Usuwanie przestarza\u0142ej ga\u0142\u0119zi feature w klastrze Kubernetes | ProHoster","description":"Cze\u015b\u0107!","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0443\u044e feature branch \u0432 Kubernetes \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0435 | ProHoster","og:description":"\u041f\u0440\u0438\u0432\u0435\u0442!","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/udalyaem-ustarevshuyu-feature-branch-v-kubernetes-klastere","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-07-01T05:42:28+00:00","article:modified_time":"2020-07-01T05:42:28+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"86902","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 14:03:55","updated":"2022-10-03 08:48:02","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/86902","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=86902"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/86902\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/86903"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=86902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=86902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=86902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}