Finalizery to mechanizm w Kubernetes, który pozwala kontrolować proces usuwania obiektów. Dzięki finalizerom można opóźnić ostateczne usunięcie zasobu, dopóki nie zostaną spełnione określone warunki lub wykonane dodatkowe czynności (np. sprzątanie powiązanych zasobów). W poniższej analizie wyjaśniamy, czym są finalizery i jak działają, przedstawiamy krok po kroku ich mechanizm działania, omawiamy kiedy i dlaczego warto je stosować – wraz z przykładami praktycznymi – a także skupiamy się na specjalnym przypadku finalizerów dla woluminów PersistentVolume (PV) i roszczeń PersistentVolumeClaim (PVC). Na końcu znajduje się podsumowanie w formie krótkiej notatki.
Czym są finalizery i jak działają w Kubernetes?
Finalizer (z ang. finalizer) to specjalny wpis (klucz) w metadanych obiektu Kubernetes, który instruuje system, by wstrzymał fizyczne usunięcie danego obiektu, dopóki nie zostaną wykonane pewne zadania porządkowe lub spełnione warunki. Innymi słowy, finalizery każą Kubernetesowi poczekać z ostatecznym usunięciem zasobu, aż do momentu zakończenia dodatkowych operacji związanych z jego usunięciem. Finalizery są definiowane jako lista ciągów znaków w polu metadata.finalizers obiektu (podobnie jak adnotacje). Zazwyczaj mają postać unikalnych nazw (np. example.com/finalizer albo wbudowane klucze Kubernetes jak kubernetes.io/pv-protection).
Ważne jest, że finalizer sam w sobie nie zawiera kodu ani logiki – jest tylko etykietą/sygnalizatorem. Gdy obiekt posiada finalizer, jego usunięcie nie następuje od razu – zamiast tego Kubernetes oznacza taki obiekt do usunięcia i czeka, aż zewnętrzny mechanizm (np. kontroler) usunie ten finalizer po wykonaniu wymaganych czynności. Finalizery informują kontrolery (np. operatory działające w klastrze), że obiekt jest usuwany i że powinny wykonać określone operacje cleanup zanim obiekt zostanie naprawdę skasowany. Dopiero kiedy lista finalizerów obiektu stanie się pusta, Kubernetes uznaje, że można dokończyć usunięcie i usuwa obiekt z etcd (bazy danych stanu klastra).
Podsumowując, finalizery pełnią rolę haków pre-usuwania – pozwalają wstrzymać usunięcie zasobu do czasu wykonania dodatkowych zadań. Dzięki nim można bezpiecznie zarządzać zależnościami i sprzątać zasoby powiązane z usuwanym obiektem, zanim ten zniknie z klastra.
Przykład: Często spotykanymi wbudowanymi finalizerami są kubernetes.io/pv-protection oraz kubernetes.io/pvc-protection, używane dla woluminów. Kubernetes automatycznie dodaje te finalizery do PV/PVC, aby zapobiec ich usunięciu, dopóki są w użyciu (np. podłączone do Poda). W dalszej części omówimy dokładniej ten mechanizm na przykładzie PVC/PV.
Mechanizm działania finalizerów – krok po kroku
Aby lepiej zrozumieć, jak działają finalizery, prześledźmy proces usuwania obiektu z i bez finalizera:
Dodanie finalizera do obiektu: Finalizery można określić już podczas tworzenia obiektu w manifeście Kubernetes (wpisując ciągi w polu
metadata.finalizers), albo dodać je później (np. programowo przez kontroler lub ręcznie przez edycję obiektu). Jeżeli obiekt nie posiada finalizera, jego usunięcie przebiega normalnie – po wywołaniu polecenia usunięcia, obiekt jest od razu kasowany z klastra (ewentualnie z uwzględnieniem reguł zależności/OwnerReferences). Natomiast jeśli obiekt ma przynajmniej jeden finalizer, to wywołanie jego usunięcia inicjuje specjalny tryb terminating.Zainicjowanie usunięcia (kubectl delete): Gdy użytkownik lub kontroler żąda usunięcia obiektu z finalizerem, serwer API nie usuwa od razu tego obiektu. Zamiast tego:
Ustawiane jest pole
metadata.deletionTimestampobiektu (znacznik czasu, kiedy zażądano usunięcia).Kubernetes zwraca kod
HTTP 202 Accepted, sygnalizując, że przyjął żądanie usunięcia, ale obiekt wciąż istnieje (jest w trakcie usuwania).Obiekt przechodzi w stan “Terminating” (można to zobaczyć np. poprzez
kubectl get– zasób będzie oznaczony jako Terminating). Nadal jednak pozostaje on w etcd i jest widoczny (np. komendakubectl getnadal go zwróci, wraz z informacją o pending deletion).Zablokowanie usunięcia: Kubernetes blokuje fizyczne skasowanie obiektu, dopóki lista jego finalizerów nie będzie pusta. Oznacza to, że obiekt będzie tkwił w stanie “usuwania”, a system nie usunie go, dopóki ktoś nie usunie (wyczyści) finalizerów z metadanych. W praktyce od tego momentu pałeczkę przejmują kontrolery.
Działanie kontrolera: Załóżmy, że finalizer został dodany przez jakiś kontroler (np. operator) w celu wykonania akcji porządkowej. Gdy obiekt z finalizerem ma ustawiony
deletionTimestamp(co oznacza, że został oznaczony do usunięcia), odpowiedni kontroler wykrywa to zdarzenie. Typowy kontroler zostanie powiadomiony o zmianie (obiekt zaktualizowano odeletionTimestamp) i sprawdzi, czy nadal obecny jest finalizer, za który jest odpowiedzialny. Jeśli tak, to znaczy, że musi wykonać określone operacje przed usunięciem obiektu.Wykonanie operacji finalizującej: Kontroler wykonuje zaprogramowane czynności “cleanup” powiązane z usuwanym obiektem. To mogą być np. usunięcie zasobów zewnętrznych (skasowanie woluminu w chmurze, wpisu DNS, obiektu w zewnętrznym systemie) lub inne działania porządkujące stan systemu. Ważne, aby logika ta była idempotentna (ponawialna bez skutków ubocznych), na wypadek gdyby kontroler musiał ją wykonać ponownie. Dopóki finalizer nie zostanie zdjęty, obiekt pozostaje w stanie Terminating, dlatego operacja ta powinna być możliwie krótka i odporna na błędy.
Usunięcie finalizera: Gdy kontroler pomyślnie wykona czynności cleanup, usuwa on wpis finalizera z obiektu (aktualizując pole
metadata.finalizers). Każdy finalizer po wykonaniu swojego zadania powinien zostać usunięty. Kontroler może usunąć finalizer np. przez aktualizację obiektu (analogicznie jak robi się to ręcznie poleceniemkubectl patchlubkubectl edit). W momencie gdy lista finalizerów staje się pusta, Kubernetes może dokończyć usunięcie obiektu. Usunięcie finalizera jest sygnałem: “wykonałem zadanie, obiekt można już bezpiecznie usunąć”.Finalne skasowanie obiektu: Po usunięciu ostatniego finalizera Kubernetes automatycznie usuwa obiekt z systemu – znika on z etcd, a wszelkie jego zasoby są zwalniane. Dla użytkownika oznacza to, że obiekt przestaje być widoczny w klastrze (komendy get już go nie pokażą). Proces usunięcia został tym samym domknięty.
Warto zauważyć, że gdy obiekt nie ma finalizerów, krok 2 od razu skutkuje krokiem 7 – obiekt jest usuwany natychmiast (ewentualnie z uwzględnieniem standardowego mechanizmu garbage collectora zależnych obiektów, jeśli ustawiona jest propagacja usuwania). Finalizery wprowadzają więc dodatkową, zamierzoną zwłokę między zainicjowaniem usunięcia a faktycznym skasowaniem, dając czas na wykonanie pewnych akcji.
Uwaga: Jeśli finalizer nie zostanie usunięty (np. kontroler ulegnie awarii lub finalizer nie jest w ogóle obsługiwany przez żaden mechanizm), obiekt pozostanie w stanie Terminating w nieskończoność. Taki “zakleszczony” zasób wymaga interwencji – zwykle ręcznego usunięcia finalizera przez administratora, aby odblokować usunięcie. Dlatego dobrą praktyką jest upewnienie się, że każdy dodany finalizer ma odpowiedni kod/kontroler, który go obsłuży i zdejmie. W przeciwnym razie finalizer staje się martwy (niezarządzany) i uniemożliwi usunięcie obiektu.
Dlaczego i kiedy stosuje się finalizery?
Finalizery stosujemy wtedy, gdy chcemy zagwarantować wykonanie określonych akcji podczas usuwania zasobu lub zapobiec usunięciu zasobu w niewłaściwym momencie. Poniżej typowe sytuacje i motywacje do użycia finalizerów:
Sprzątanie zasobów zewnętrznych (cleanup): Jeśli obiekt Kubernetes reprezentuje zasoby poza klastrem (np. bazę danych w chmurze, dysk w usłudze cloud, bucket w S3, wpis w zewnętrznym serwisie), to usunięcie obiektu w Kubernetesie powinno pociągnąć za sobą usunięcie powiązanego zasobu zewnętrznego. Finalizer jest idealnym mechanizmem do tego celu – zapewnia wykonanie logiki czyszczącej. Przykładem może być własny Custom Resource obsługiwany przez operator: załóżmy CRD Database i operator, który przy tworzeniu instancji bazy zakłada bazę w chmurze. Dodając finalizer do obiektu Database, operator zapewni, że przy usunięciu tego obiektu najpierw skasuje odpowiadającą mu bazę w chmurze, a dopiero potem dopuści do usunięcia samego zasobu Kubernetes. Chroni to przed sytuacją, w której w klastrze usunięto obiekt, a zewnętrzny zasób pozostał osierocony.
Zachowanie porządku i spójności danych: Finalizery pomagają uniknąć utraty danych lub usunięcia obiektu, który jest jeszcze potrzebny. Na przykład, finalizer ochronny woluminów (
pv-protection/pvc-protection) gwarantuje, że dysk (PersistentVolume) lub claim (PersistentVolumeClaim) nie zostanie usunięty, dopóki jest używany przez jakiś Pod. Zapobiega to przypadkowemu skasowaniu storage z danymi, do którego nadal podłączona jest aplikacja. Innym przykładem jest finalizer dodawany do Namespace – Kubernetes potrafi dodać finalizer kubernetes do obiektu Namespace, by wymusić najpierw usunięcie wszystkich zasobów w przestrzeni nazw, zanim zniknie sama przestrzeń. Chroni to przed przypadkowym pozostawieniem “wiszących” obiektów bez namespace.Własne zależności i zależności między obiektami: Standardowo Kubernetes posiada mechanizm Owner References i usuwania kaskadowego (foreground/background deletion) do radzenia sobie z zależnościami (np. usunięcie Deployment usuwa automatycznie Pods/RS). Jednak finalizery dają bardziej elastyczną kontrolę w nietypowych scenariuszach. Można ich użyć, aby wstrzymać usunięcie obiektu, dopóki nie zostaną wykonane pewne działania na obiektach zależnych lub powiązanych, które nie są ujęte w modelu Owner Reference. Przykładowo, Jobs domyślnie usuwając się mogą usuwać swoje Pody, ale jeśli chcemy zachować te Pody dla analizy powykonawczej, możemy zastosować finalizer (lub odpowiednią politykę usuwania) oznaczający „nie usuwaj podów po Jobie” – efekt jest taki, że Job zostanie usunięty inaczej (np. poprzez orphan finalizer, który pozostawi dzieci nietknięte). Choć w tym wypadku Kubernetes oferuje flagę –cascade=orphan, mechanizm finalizerów jest tym, co pod spodem może oznaczyć obiekt do zachowania zależności.
Implementacja złożonych operacji usunięcia: W systemach rozproszonych, gdzie usunięcie jednego obiektu wymaga skoordynowanych akcji w wielu miejscach (np. powiadomienia innych usług, aktualizacja statusu, backup danych), finalizer służy jako checkpoint. Dopóki te akcje nie zostaną wykonane, finalizer blokuje usunięcie. To bywa stosowane we własnych operatorach Kubernetes, gdzie logika usuwania jest bardziej skomplikowana niż zwykłe skasowanie obiektu. Finalizer umożliwia traktowanie operacji usunięcia jako części procesu rekonsyliacji stanu systemu – np. operator wykrywa, że obiekt ma deletionTimestamp i finalizer, wtedy wykonuje niestandardowe czynności (transakcje, komunikaty) i dopiero po ich zakończeniu pozwala na usunięcie obiektu.
Podsumowując, finalizery stosujemy gdy potrzebujemy gwarancji, że “coś” zostanie zrobione przy usuwaniu lub chcemy zabezpieczyć ważne zasoby przed przedwczesnym skasowaniem. Typowe przypadki to sprzątanie zasobów zewnętrznych, zachowanie integralności danych (nie usuwać, dopóki używane), wymuszenie określonej kolejności usuwania elementów systemu, czy implementacja niestandardowych operacji zamykających dla obiektów.
Przykłady praktycznego zastosowania finalizerów
Aby lepiej zobrazować, jak finalizery są wykorzystywane w praktyce, poniżej kilka scenariuszy wraz z dobrymi praktykami:
Czyszczenie zasobów cloud przez operator (CustomResource + finalizer): Wyobraźmy sobie Custom Resource o nazwie MyBucket, a w kontrolerze (operatorze) logikę tworzącą bucket w usłudze chmurowej (np. AWS S3) dla każdego zasobu MyBucket. Operator podczas tworzenia obiektu CR dodaje finalizer, np. storage.example.com/bucket-cleanup. Gdy użytkownik usunie obiekt MyBucket, Kubernetes ustawi deletionTimestamp, a operator otrzyma informację, że obiekt jest w trakcie usuwania. Operator wtedy faktycznie usuwa zewnętrzny bucket S3 powiązany z tym obiektem, po czym usuwa finalizer z MyBucket. Dopiero to pozwoli Kubernetesowi skasować obiekt CR. W ten sposób mamy pewność, że nie zostaną porzucone zewnętrzne zasoby, a usunięcie jest w pełni odsprzątane. Jest to wzorzec powszechnie stosowany we wszystkich operatorach zarządzających infrastrukturą poprzez CRD.
Ochrona krytycznych zasobów przed skasowaniem: Kubernetes domyślnie chroni PersistentVolume i PersistentVolumeClaim za pomocą finalizerów pv-protection i pvc-protection. Jeśli spróbujemy usunąć PV, który jest nadal zarezerwowany (bound) dla jakiegoś PVC, lub usunąć PVC używany przez działający Pod, to finalizer zablokuje takie usunięcie. Obiekty te przejdą w status Terminating i będą oczekiwać, aż zniknie warunek blokujący (odpowiednio: aż PVC zostanie zwolniony, albo aż Pod przestanie używać danego PVC). Dopiero wtedy finalizer zostanie automatycznie usunięty przez kontroler i zasób się skasuje. Taka ochrona zapobiega utracie danych i gwarantuje, że np. wolumen nie zniknie spod działającej aplikacji. (Szerzej ten mechanizm omawiamy w następnym rozdziale poświęconym PVC/PV).
Kaskadowe usuwanie złożonych aplikacji: Gdy kasujemy złożoną aplikację, np. Namespace zawierający wiele obiektów, Kubernetes dodaje finalizer do Namespace (oraz używa mechanizmu foregroundDeletion). Finalizer w przestrzeni nazw sprawia, że Namespace pozostaje w stanie Terminating tak długo, aż wszystkie obiekty w środku nie zostaną usunięte. Dopiero wyczyszczenie całej przestrzeni pozwala ją skasować. Czasem zdarza się, że Namespace utknie w tym stanie (z powodu finalizerów na obiektach w środku zarządzanych przez zewnętrzne kontrolery). Wówczas administrator musi zidentyfikować, który finalizer blokuje usunięcie i usunąć go ręcznie, albo usunąć “ręcznie” zalegające obiekty. Ten scenariusz pokazuje potęgę i pułapkę finalizerów – zapewniają porządek przy usuwaniu (nic nie zostanie pominięte), ale wymagają uwagi, by nie pozostawić ich bez obsługi.
Zachowanie obiektów podrzędnych przy usuwaniu nadrzędnego: Standardowo usunięcie kontrolera (Deployment, ReplicaSet, Job itd.) usuwa również obiekty podrzędne (Pods itp.) – to tzw. usuwanie kaskadowe. Jeśli z jakiegoś powodu chcemy odstąpić od tej zasady i pozostawić dzieci przy życiu (orphan), również można to osiągnąć finalizerem. Kubernetes udostępnia wbudowany finalizer
orphanorazforegroundDeletionużywane we fladze--cascade. Przykładowo, usunięcie obiektu z flagą--cascade=orphanspowoduje, że kontroler doda finalizer orphan i usunie zależność ownerReference, co skutkuje pozostawieniem dzieci nietkniętych. Choć ten przypadek jest mniej codzienny, warto wiedzieć, że finalizery są narzędziem także do niestandardowego zarządzania zależnościami – w tym wypadku do celowego zachowania zasobów podrzędnych (np. w celu analizy, debugowania, przeniesienia ich pod inny kontroler itp.).
Dobre praktyki przy stosowaniu finalizerów:
Dodawaj finalizer tylko wtedy, gdy masz kod (kontroler) gotowy go obsłużyć. Unikaj “sierocych” finalizerów dodanych ręcznie bez mechanizmu zdejmującego – będą one blokować usunięcie obiektu na zawsze.
Upewnij się, że logika wykonywana w ramach finalizera jest odporna na awarie i powtarzalna (idempotentna). Jeśli operacja się nie powiedzie za pierwszym razem, kontroler spróbuje ponownie albo finalizer będzie wymagał interwencji – w obu przypadkach ważne, by nie doszło do niepożądanego stanu (np. częściowo usunięty zasób zewnętrzny).
Staraj się, aby operacje finalizera były możliwie krótkie. Długi proces w finalizerze oznacza, że obiekt długo będzie w stanie Terminating (co czasem może blokować inne działania, np. usunięcie namespace). Jeśli musisz wykonać czasochłonne zadanie, rozważ podzielenie go lub oznaczenie stanu w innym miejscu – tak, by finalizer był zdjęty jak najszybciej po rozpoczęciu procesu usuwania.
Monitoruj zasoby w stanie Terminating. Obiekt “wisi” z finalizerem zbyt długo? To sygnał, że coś poszło nie tak – albo bug w kontrolerze, albo brakujący kontroler. W razie potrzeby bezpiecznie usuń finalizer ręcznie (kubectl edit …) po upewnieniu się, że można pominąć już akcję cleanup (lub wykonałeś ją manualnie). Usunięcie finalizerów z pola metadata.finalizers wymusi dokończenie usunięcia obiektu.
Finalizery w kontekście PersistentVolumeClaim (PVC) i PersistentVolume (PV)
Finalizery pełnią szczególnie istotną rolę w obszarze trwałej pamięci masowej w Kubernetes. Zarówno obiekty typu PersistentVolumeClaim (żądanie woluminu od użytkownika), jak i PersistentVolume (reprezentacja rzeczywistego woluminu w klastrze) korzystają z finalizerów, aby zachować spójność między sobą oraz zapobiec utracie danych. Poniżej omawiamy, co dzieje się przy usuwaniu PVC/PV i jak finalizery wpływają na cykl życia tych zasobów, na przykładzie typowego scenariusza:
Ochrona PVC/PV przy usuwaniu
Jeśli wolumin jest w użyciu, Kubernetes musi upewnić się, że nie zostanie on usunięty spod działającej aplikacji. Dlatego wprowadzone zostały finalizery ochronne:
kubernetes.io/pvc-protection – dodawany automatycznie do każdego PersistentVolumeClaim.
kubernetes.io/pv-protection – dodawany do każdego PersistentVolume.
Te finalizery są zarządzane przez kontroler ochrony woluminów. Ich działanie sprowadza się do zapobiegania usunięciu obiektu, który jest jeszcze powiązany z drugim lub używany:
Kubernetes dodaje
pvc-protectiondo PVC zaraz po jego utworzeniu. Gdy ten PVC zostanie przypięty do PV i podłączony do Poda, finalizer zapewnia, że jeśli użytkownik spróbuje skasować ten PVC w czasie gdy Pod nadal z niego korzysta, to usunięcie zostanie wstrzymane. PVC przejdzie w stan Terminating, a w jego metadanych wciąż będzie widoczny finalizer. System poczeka, aż dany wolumin nie będzie już używany (np. aż Pod zostanie usunięty lub odmontuje wolumin). Dopiero wtedy finalizerpvc-protectionzostanie automatycznie usunięty przez kontroler – co odblokuje usunięcie PVC.Analogicznie finalizer
pv-protectionna PV uniemożliwia skasowanie woluminu, który jest wciąż związany z jakimś roszczeniem (PVC). Jeżeli administrator spróbuje usunąć obiekt PersistentVolume, który ma polespec.ClaimRefwskazujące na istniejący PVC, to taki PV nie zostanie od razu usunięty. Zostanie oznaczony jako Terminating, a finalizer zatrzyma jego usunięcie dopóki powiązany PVC nie zniknie (lub nie zostanie zwolniony). Kiedy tylko dany PV nie jest już zarezerwowany dla żadnego claimu, Kubernetes usunie finalizerpv-protectioni pozwoli skasować PersistentVolume.
Efektem działania tych finalizerów jest ochrona danych przed utratą. W praktyce użytkownik widzi to tak, że komenda kubectl delete pvc mydata może nie spowodować natychmiastowego zniknięcia PVC – jeśli gdzieś w klastrze istnieje Pod korzystający z mydata, to kubectl get pvc mydata pokaże status Terminating, a w opisie (kubectl describe pvc) widoczny będzie finalizer kubernetes.io/pvc-protection.
Gdy tylko ten Pod zostanie usunięty lub przestanie używać PVC, finalizer zostanie zdjęty, a PVC faktycznie się usunie. Podobnie dla PV: usunięcie PV z wciąż istniejącym powiązanym PVC spowoduje zawieszenie PV w stanie Terminating z finalizerem pv-protection, dopóki ten claim nie zostanie zwolniony.
Wpływ finalizerów na cykl życia PVC/PV
Finalizery pvc-protection i pv-protection wpływają na kolejność i warunki usuwania tych obiektów:
Usuwanie PVC przed Podem: Normalnie, gdy usuwamy Poda, który korzysta z PVC, to najpierw Pod jest usuwany, a PVC można usunąć niezależnie. Ale jeśli ktoś spróbuje odwrotnie – usunąć PVC przed usunięciem Poda – finalizer zablokuje to do czasu usunięcia Poda. Zapewnia to, że Pod nie straci nagle dostępu do dysku (co mogłoby spowodować błąd w działaniu aplikacji). Z punktu widzenia cyklu życia: PVC może zostać całkowicie usunięty dopiero po zakończeniu używania go przez wszystkie Pody.
Usuwanie PV przed PVC: Podobna zależność w drugą stronę – PersistentVolume istnieje w klastrze dopóki istnieje powiązany z nim PersistentVolumeClaim. Jeżeli administrator chce usunąć PV, musi najpierw upewnić się, że nie jest on już zarezerwowany przez żaden claim. W przeciwnym razie finalizer chroni PV przed usunięciem, co zapobiega sytuacji, w której użytkownik ma PVC wskazujące na nieistniejący wolumin. Cykl życia PV jest zatem przedłużony do momentu, gdy nie jest już potrzebny żadnemu PVC.
Warto dodać, że poza ochroną przy usuwaniu, mechanizmy te współgrają z polityką odzyskiwania (reclaim policy) woluminów:
Jeśli PV ma politykę Retain, to po usunięciu PVC wolumin pozostaje (dane nie są kasowane automatycznie). Administrator może wtedy ręcznie usunąć PV (np. po zabezpieczeniu danych). Finalizer nie blokuje takiego usunięcia, bo PVC już nie istnieje – PV nie jest in use. Po prostu PV stanie się “Released” i można go skasować bez przeszkód (chyba że inne finalizery, np. od drivera, są ustawione).
Jeśli PV ma politykę Delete i został dynamicznie stworzony przez CSI, to często do PV dodawany jest inny finalizer, np.
external-provisioner.volume.kubernetes.io/finalizer. Ten finalizer zadziała, gdy usuwamy PVC/PV – zewnętrzny provisioner (plugin CSI) usuwa fizyczny wolumin w infrastrukturze chmurowej, po czym usuwa ten finalizer, pozwalając skasować PV. To analogiczny mechanizm cleanup, tylko sterowany przez kontroler CSI. Z punktu widzenia użytkownika dzieje się to automatycznie po usunięciu PVC (przy polityce Delete). Najpierw PVC przechodzi w Terminating dopóki PV (i jego finalizer CSI) nie załatwi usunięcia zasobu, potem obydwa znikają.
Przykład działania finalizerów dla PVC/PV
Rozważmy konkretny scenariusz:
Użytkownik tworzy PVC o nazwie
dane-pvc. W momencie utworzenia, Kubernetes automatycznie przypisuje mu finalizerkubernetes.io/pvc-protection(co widać w yaml-u PVC w polumetadata.finalizers). Klaster dynamicznie tworzy nowy PV i binduje go do tego PVC; PV także dostaje finalizerkubernetes.io/pv-protection.Tworzony jest Pod korzystający z
dane-pvc. Pod montuje wolumin, zapisuje dane itp. Teraz PVC i PV są w stanie Bound i PVC jest In Use przez Poda.Jeśli w tym momencie ktoś wyda polecenie kubectl delete pvc dane-pvc, Kubernetes:
Oznaczy PVC jako do usunięcia (ustawi deletionTimestamp) ale nie usunie go – zobaczymy PVC w stanie Terminating. Finalizer
pvc-protectionnadal widnieje przy PVC.Ponieważ Pod nadal działa i korzysta z woluminu, kontroler ochrony PVC nie usunie finalizera. PVC pozostaje więc w klastrze (Terminating).
Użytkownik usuwa Poda (np. skalując Deployment do 0 lub kasując go). Gdy tylko Pod zniknie, PVC nie jest już w użyciu. Kontroler PVC-protection to wykrywa i usuwa finalizer z
dane-pvc. Natychmiast Kubernetes dokańcza usunięcie PVC – znika on z listy zasobów.Usunięcie PVC powoduje zwolnienie PV (jego status zmienia się na Released). W zależności od polityki reclaim:
Jeśli Delete: kontroler storage (np. CSI provisioner) przystąpi do usunięcia fizycznego woluminu. PV może mieć finalizer sterownika (
external-provisioner...), który zadziała podobnie – PV będzie Terminating dopóki wolumin w chmurze nie zostanie skasowany. Gdy to nastąpi, finalizer zostanie zdjęty, a PV usunięty.Jeśli Retain: PV pozostanie w stanie Released, oczekując na działania administratora. Finalizer
pv-protectionjuż nie blokuje, bo nie ma powiązanego claimu, więc PV można od razu usunąć (lub podłączyć do innego PVC po wyczyszczeniu).
Ostatecznie zarówno PVC jak i PV zostają usunięte we właściwej kolejności, chroniąc integralność danych i zapobiegając utracie lub wyciekom zasobów.
Powyższy przykład pokazuje, jak finalizery wbudowane w Kubernetes automatycznie dbają o lifecycle zasobów storage. Jako użytkownicy zwykle nie musimy nic specjalnego robić – te finalizery działają w tle. Jednak świadomość ich istnienia jest ważna np. przy debugowaniu: gdy PVC/PV nie chce się usunąć, warto sprawdzić, czy nie jest to spowodowane przez finalizer (i np. weryfikować, czy dany wolumin nadal nie jest gdzieś używany).
Podsumowanie (streszczenie)
Finalizery to mechanizm Kubernetes umożliwiający wykonanie pewnych działań przed ostatecznym usunięciem obiektu. Dodawane są w polu
metadata.finalizersi blokują skasowanie zasobu, dopóki nie zostaną usunięte.Finalizery służą m.in. do czyszczenia zasobów zewnętrznych powiązanych z obiektem (np. usunięcie woluminu w chmurze gdy usuwamy PVC), ochrony danych przed utratą (np. nie usuwać PV/PVC dopóki jest używany przez Poda), oraz kontrolowania kolejności usuwania złożonych zależności (np. najpierw usuń dzieci, potem rodzica).
Gdy obiekt z finalizerem jest usuwany, Kubernetes oznacza go jako Terminating (ustawia deletionTimestamp) i czeka aż finalizer zostanie zdjęty po wykonaniu akcji cleanup. Za zdjęcie finalizera odpowiada zwykle kontroler/operator, który uprzednio go dodał – np. operator CRD sprzątający zasoby, lub wbudowany kontroler ochrony PVC/PV w Kubernetes.
Należy stosować finalizery rozważnie – zapewnić obsługę ich usuwania, aby nie pozostawić “zawieszonych” obiektów. W razie utknięcia zasobu w stanie Terminating, można ręcznie usunąć finalizer (
kubectl edit/patch), co spowoduje dokończenie usuwania.Finalizery to potężne narzędzie zwiększające niezawodność i spójność operacji w klastrze. Umożliwiają bezpieczne sprzątanie po obiektach i zapobiegają wielu klasom błędów (np. wycieki zasobów, utrata danych przez pochopne usunięcie). Dobrą praktyką jest korzystać z nich wszędzie tam, gdzie zwykłe usunięcie mogłoby pozostawić “bałagan” lub zagrozić działaniu aplikacji. Dzięki finalizerom proces usuwania staje się kontrolowany i przewidywalny – co jest kluczowe zwłaszcza w środowiskach produkcyjnych.