Kubernetes to nie tylko nowy sposób wdrażania. To zmiana nawyków operacyjnych
Przeniesienie aplikacji z serwerów do kontenerów to mniejsza część pracy. Większa to porzucenie przyzwyczajeń — monitoringu hostów, logów w /var/log, założenia, że aplikacja działa w jednym egzemplarzu, i tego, że wiadomo, na której maszynie działa.
Kiedy aplikacje działają jako ręczne wdrożenia na konkretnych serwerach, cała wiedza operacyjna zespołu jest zbudowana wokół tych serwerów: wiadomo, na której maszynie co działa, gdzie leżą logi, jak wygląda „normalne” obciążenie. Przenosząc tę samą platformę na Kubernetes, szybko przekonaliśmy się, że zbudowanie obrazów i manifestów to mniejsza część pracy. Większa to zmiana nawyków — w monitoringu, w logach i w samej architekturze aplikacji.
Poniżej cztery obszary, w których stare podejście przestało działać, i to, czym je zastąpiliśmy.
Monitoring: od hostów do usług
Na serwerach monitoring klasy Zabbix sprawdza się świetnie: host, jego usługi, dyski, porty, procesy. Problem w Kubernetesie nie polega na tym, że Zabbix coś „przegapia”. Polega na tym, że nie widzi wnętrza klastra. Pody komunikują się przez osobną sieć klastra (overlay), mają adresy, które żyją tyle co one, i nie są stałymi celami, które da się wpisać do konfiguracji monitoringu. Zabbix widzi to, co na wierzchu: węzły, ich obciążenie, dostępność wystawionych usług.
Wgląd do środka dają nam Prometheus i Grafana:
- metryki zbierane z podów automatycznie, z etykietami (aplikacja, środowisko, wersja), a nie z listy hostów,
- alerty definiowane na poziomie usługi — „odsetek błędów w API przekroczył próg” — a nie „proces na maszynie X nie odpowiada”,
- widok zasobów kontenera: zużycie pamięci względem limitu, dławienie procesora, restarty.
Ten ostatni punkt okazał się kluczowy. Sporadyczne błędy 503 w jednej z usług nie miały nic wspólnego z kodem — kontener był dławiony przez mechanizm limitu pamięci. Na serwerze takie zjawisko w ogóle by nie wystąpiło, a w klasycznym monitoringu hosta nie było go widać.
Zabbix nie zniknął — nadal pilnuje węzłów fizycznych, sieci i usług spoza klastra. Ale to, co dzieje się wewnątrz, oglądamy już innymi narzędziami.
Logi: pod znika razem ze swoimi logami
Na serwerze grep w /var/log albo journalctl -u usluga rozwiązuje większość problemów. W Kubernetesie oba nawyki zawodzą z jednego powodu: pody są efemeryczne. Nowe wdrożenie, przeskalowanie albo restart po przekroczeniu limitu pamięci i logi poprzedniego poda przestają być dostępne. Logi bieżących kontenerów też rotują — u nas historia z węzła potrafiła sięgać zaledwie kilku godzin wstecz.
Do tego jedna usługa to kilka replik na różnych węzłach. Szukanie błędu oznaczałoby logowanie się na każdy węzeł i zgadywanie, który pod obsłużył dane żądanie.
Rozwiązaniem jest agregator logów — u nas Loki z Grafaną:
- logi wszystkich podów trafiają w jedno miejsce i zostają tam po zniknięciu poda,
- wyszukiwanie po etykietach (usługa, środowisko, pod) zamiast po nazwie serwera,
- logi i metryki w tym samym narzędziu — z wykresu błędów można przejść prosto do logów z tego samego okresu.
Zmiana nawyku jest prosta do opisania i trudna do wdrożenia: przy incydencie pierwszym odruchem ma być otwarcie agregatora, a nie terminala na węźle.
Skalowanie poziome: strzał w stopę, jeśli aplikacja nie jest na nie gotowa
Jedna linijka w manifeście — replicas: 3 — i usługa działa w trzech egzemplarzach. To jedna z największych obietnic Kubernetesa i jednocześnie pułapka. Aplikacja pisana z założeniem, że działa w jednym egzemplarzu, po przeskalowaniu robi wszystko kilka razy.
Przekonaliśmy się o tym na powiadomieniach dla klientów. Po zwiększeniu liczby replik wiadomości, które miały wychodzić raz dziennie, zaczęły wychodzić dwa razy. Na szczęście nie jednocześnie — z przesunięciem o kilka godzin, bo każda replika miała własny harmonogram zadań. Nic się nie zepsuło w kodzie; zmieniło się tylko to, ile instancji ten kod wykonuje.
Rozwiązaliśmy to na dwa sposoby, w zależności od rodzaju zadania:
- lock w bazie danych — zadanie przed startem zakłada blokadę we wspólnej bazie; jeśli blokadę ma już inna replika, pomija uruchomienie,
- wybór lidera — spośród replik wybierany jest jeden lider, który jako jedyny wykonuje zadania cykliczne; gdy lider znika, jego rolę przejmuje inna replika.
Te same zasady dotyczą wszystkiego, co aplikacja trzyma lokalnie: blokad w pamięci, cache, sesji, plików tymczasowych. Wszystko, co ma być wspólne dla instancji, musi być wspólne naprawdę — w bazie, w kolejce, w zewnętrznym magazynie.
Natywne narzędzia klastra
Czasem lepiej w ogóle nie uruchamiać harmonogramu wewnątrz aplikacji. Kubernetes ma do tego własny mechanizm: CronJob. Zadanie okresowe staje się osobnym, krótko żyjącym podem uruchamianym przez klaster — dokładnie raz, z własną historią wykonań, limitami zasobów i polityką ponowień. Aplikacja obsługująca ruch nie musi wtedy wiedzieć nic o harmonogramach.
To nie zawsze jest proste. Zadania często korzystają z kontekstu aplikacji — konfiguracji, połączeń, logiki domenowej — i wydzielenie ich wymaga refaktoryzacji. Dlatego w praktyce łączymy oba podejścia: nowe zadania okresowe planujemy od razu jako CronJoby, a istniejące zabezpieczamy lockiem lub wyborem lidera, dopóki nie przyjdzie czas na ich wydzielenie.
Lokalność: scheduler nie wie, co jest blisko
Na serwerach o tym, co działa obok czego, decydował człowiek: baza i aplikacja, która z niej intensywnie korzysta, stały na tej samej maszynie albo w tej samej szafie. W Kubernetesie tę decyzję podejmuje scheduler — i bez dodatkowych wskazówek kieruje się przede wszystkim wolnymi zasobami węzłów, a nie tym, jak daleko od siebie wylądują współpracujące komponenty.
U nas klaster rozciąga się na kilka serwerów fizycznych, a węzły komunikują się przez sieć overlay. Pod, który trafi na węzeł na innym serwerze fizycznym niż baza, z której korzysta, płaci za każde zapytanie dodatkowym przejściem przez tunel i sieć między serwerami. Pojedynczo to ułamki milisekund; przy setkach zapytań na jedno żądanie użytkownika — wyraźnie wolniejsza aplikacja, bez żadnej zmiany w kodzie. Takie spowolnienie łatwo przegapić, bo pojawia się i znika w zależności od tego, gdzie akurat zostały uruchomione pody po ostatnim wdrożeniu.
Kubernetes ma na to narzędzia, ale trzeba z nich świadomie skorzystać:
- etykiety topologii na węzłach — informacja, na którym serwerze fizycznym (lub w której strefie) działa węzeł; bez niej scheduler nie ma z czego wywnioskować odległości,
- pod affinity — „uruchamiaj ten komponent blisko tamtego”, np. aplikację przy jej bazie danych lub cache,
- pod anti-affinity i topology spread constraints — odwrotnie: repliki tej samej usługi rozłożone na różne serwery fizyczne, żeby awaria jednej maszyny nie zabrała wszystkich naraz,
- storage lokalny — wolumen na dysku konkretnego węzła przywiązuje do niego pod; to szybkie, ale ogranicza swobodę schedulera i trzeba to uwzględnić w planie awaryjnym.
Lokalność i odporność na awarie ciągną w przeciwne strony — im bliżej siebie komponenty, tym większa szansa, że padną razem. Dobrego ustawienia nie ma „domyślnie”; trzeba je zaprojektować dla każdej krytycznej ścieżki osobno.
Lista kontrolna przed migracją
Zanim przeniesiesz aplikację z serwera do klastra, sprawdź:
- Monitoring — czy metryki aplikacji są wystawione (np. dla Prometheusa) i czy alerty są zdefiniowane na poziomie usługi, a nie hosta?
- Logi — czy aplikacja pisze na standardowe wyjście i czy logi trafiają do agregatora, który przeżyje restart poda?
- Zadania cykliczne — co się stanie, gdy aplikacja będzie działać w trzech egzemplarzach? Lock, lider czy CronJob?
- Stan lokalny — sesje, cache, pliki tymczasowe, blokady w pamięci: czy coś zakłada, że instancja jest jedna?
- Zasoby — czy limity pamięci i procesora są dobrane do realnego zużycia i czy monitoring pokazuje dławienie?
- Konfiguracja — czy wszystko, co dziś leży w plikach na serwerze, da się przekazać przez zmienne środowiskowe i sekrety?
- Lokalność — które komponenty muszą działać blisko siebie (affinity), a które daleko (anti-affinity, rozkład replik)? Czy węzły mają etykiety topologii?
Kubernetes daje rolling update, automatyczne restarty i skalowanie jednym poleceniem. Ale korzysta z nich tylko zespół, który zmienił sposób patrzenia na działającą aplikację — z „tej jednej na tamtym serwerze” na „usługę, której egzemplarze przychodzą i odchodzą”.
Więcej o samej infrastrukturze: Migracja kilkunastu aplikacji na własny klaster k3s.
Powiązane teksty
2026-09-25
Migracja kilkunastu aplikacji na własny klaster k3s — problemy i korzyści
Nasz trzeci klaster Kubernetes na bare metal — pierwszy produkcyjny u klienta. Działa od listopada 2023 roku i obsługuje około 20 aplikacji w czterech środowiskach. Opisujemy architekturę, siedem problemów, które nas zaskoczyły, i to, co realnie zyskaliśmy.
2026-10-10
AI nie psuje kodu. W rękach architekta podnosi jego jakość
Rynkowa opinia mówi, że modele językowe zalewają repozytoria kodem gorszej jakości. Nasze doświadczenie z modernizacji dużej platformy jest odwrotne — pod jednym warunkiem.