Krótka scenka: pierwsze zderzenie z Kubernetesem
Projekt działa od miesięcy w Docker Compose, kilka kontenerów, prosty plik docker-compose.yml. Szef mówi: „Klient chce wysoką dostępność, monitoring, autoskalowanie – wrzućmy to na Kubernetesa”. Otwierasz pierwszego YAML-a z Deploymentem i Ingressem, widzisz kilkadziesiąt linii niewiadomych – i pojawia się pytanie: od czego w ogóle zacząć.
W takim momencie łatwo uznać, że Kubernetes to czarna magia zarezerwowana dla „prawdziwych” DevOpsów. W praktyce to zestaw powtarzalnych klocków, które można zrozumieć krok po kroku. Klucz tkwi w tym, żeby najpierw ogarnąć jeden działający klaster i jedną prostą aplikację, a dopiero potem sięgać po autoskalowanie, ingressy i resztę fajerwerków.
Cel jest dość prosty: przejść z etapu „umiem zbudować obraz Dockera” do poziomu „potrafię wdrożyć tę aplikację na Kubernetesie, wiem jak ją skalować i jak diagnozować problemy”. Reszta to już tylko powtarzanie sprawdzonych wzorców.
Co to właściwie jest Kubernetes i kiedy ma sens
Orkiestracja kontenerów w praktyce
Kubernetes to platforma do orkiestracji kontenerów. Można o nim myśleć jak o automacie, który wykonuje za admina trzy kluczowe zadania:
- uruchom kontenery zgodnie z deklaracją w YAML-u,
- utrzymaj zadaną liczbę replik (jeśli jedna replika padnie – podnieś kolejną),
- udostępnij aplikację w przewidywalny sposób (Service, Ingress, load balancing).
Ty deklarujesz w pliku Deploymentu: „chcę trzy repliki aplikacji w kontenerze z takim obrazem, z taką ilością pamięci, odsłuchujące port 8080”. Kubernetes sam dobiera, na których węzłach je uruchomi, jak zarestartuje kontener, jeśli ten się wysypie, i jak równoważyć ruch między replikami. To jest kluczowa różnica względem gołego Dockera – nie musisz ręcznie odpalać kontenerów na każdym serwerze.
Składniki klastra bez przeładowania teorią
Klaster Kubernetes składa się z dwóch grup elementów: control plane (często mówimy „master”) i worker nodes (węzły robocze). W praktyce:
- Control plane – zestaw komponentów, które podejmują decyzje: scheduler, API server, etcd (przechowuje stan), controller manager. To mózg klastra. Zwykle nie dotykasz go ręcznie – rozmawiasz z nim przez kube-API, używając
kubectl. - Worker node – zwykła maszyna (VM lub fizyczna), na której faktycznie działają pody z kontenerami. Na każdym węźle pracują:
- kubelet – agent, który pilnuje, by na danym węźle działało to, co zadeklarowane w klastrze,
- kube-proxy – odpowiada za sieć i przekierowanie ruchu do odpowiednich podów,
- silnik kontenerowy (np. containerd, czasem Docker).
Jako osoba wdrażająca aplikację najczęściej pracujesz z trzema rzeczami: kubectl, plikami YAML i rejestrem obrazów (Docker Hub, GitHub Container Registry, ECR, GCR). Resztę zarządzają dostawcy chmury lub administratorzy.
Kiedy Kubernetes jest dobrym wyborem, a kiedy to przesada
Kubernetes błyszczy w scenariuszach, gdzie:
- masz wiele usług (mikrousługi, kilka aplikacji współdzielących infrastrukturę),
- wymagasz wysokiej dostępności – kilka węzłów, automatyczny restart i przenoszenie podów,
- masz zróżnicowane środowiska: dev, test, stage, prod i chcesz, żeby były spójne,
- chcesz skalować poziomo na podstawie CPU, pamięci lub metryk aplikacyjnych.
Z drugiej strony, Kubernetes bywa „armatą na muchę”, gdy:
- masz jedną prostą aplikację hostowaną na małym VPS,
- nie potrzebujesz skalowania, a restart kontenera możesz ogarnąć systemd,
- budżet i czas na utrzymanie klastra są znikome.
Docker Compose vs Kubernetes – kiedy co wybrać
Wiele projektów zaczyna od Docker Compose. To wygodne: jeden plik, jedna komenda, lokalne środowisko działa. Pojawia się jednak ściana, gdy:
- chcesz uruchomić to samo na kilku serwerach,
- musisz mieć roll-outy, roll-backi i kontrolę wersji konfiguracji,
- chcesz płynnie dodawać kolejne repliki w reakcji na ruch.
| Cecha | Docker Compose | Kubernetes |
|---|---|---|
| Skala projektu | Małe, proste aplikacje | Średnie i duże systemy, mikrousługi |
| Środowiska | Głównie lokalne dev | Dev, test, stage, prod |
| Skalowanie | Ręczne, ograniczone | Automatyczne, HPA, wiele węzłów |
| Wysoka dostępność | Brak natywnego wsparcia | Repliki, rozłożenie na węzły |
| Złożoność | Niska | Średnia / wysoka |
Jeżeli aplikacja ma szansę urosnąć – nowe usługi, więcej użytkowników, nowe środowiska – sensownie jest poznać podstawy Kubernetes wcześniej. Unikniesz gwałtownej migracji „na wczoraj”, gdy zacznie się sypać obecna infrastruktura.
Wniosek z perspektywy praktyka
Kubernetes nie jest celem samym w sobie. To narzędzie, które opłaca się poznać, gdy potrzebujesz powtarzalności, skalowania i odporności na awarie. Jeśli twój projekt rośnie lub dopiero go planujesz, znajomość podstawowych obiektów i mechanizmów oszczędzi sporo zaskoczeń na późniejszych etapach.
Przygotowanie środowiska: jak postawić klaster do nauki
Wybór narzędzia: minikube, kind, k3d
Do nauki lokalnej nie potrzebujesz od razu klastra w chmurze. Wystarczy narzędzie, które na twoim laptopie postawi mały, jedno- lub kilku-węzłowy klaster. Najpopularniejsze opcje:
- minikube – klasyk. Tworzy pojedynczy węzeł (VM lub kontener) z działającym clusterem. Ma sporo dodatków (addons) i dobrze odzwierciedla zachowanie „prawdziwego” klastra.
- kind (Kubernetes in Docker) – klaster budowany z kontenerów Dockera. Szybki start, świetny do CI, bardzo lekki, ale nieco więcej ręcznej konfiguracji przy sieci.
- k3d (K3s in Docker) – oparty na lekkiej dystrybucji K3s, także działa w oparciu o kontenery. Lżejszy niż klasyczny Kubernetes, dobra opcja na słabsze laptopy.
Dla początkujących najwygodniejszy jest minikube. Ma dużo dokumentacji, a większość tutoriali zakłada jego użycie.
Wymagania sprzętowe i typowe problemy przy starcie
Aby komfortowo uruchomić minikube, przydaje się:
- co najmniej 4 GB RAM dostępne dla klastra (8 GB RAM w całym laptopie to rozsądne minimum),
- kilka wolnych CPU (2 vCPU to sensowna baza),
- działająca wirtualizacja (VT-x/AMD-V) w BIOS/UEFI, jeśli używasz drivera VM,
- zainstalowany Docker lub inny driver (VirtualBox, Hyper-V) – w zależności od systemu.
Najczęstsze problemy przy starcie lokalnego klastra:
- wyłączona wirtualizacja w BIOS/UEFI,
- porty zajęte przez inne usługi (np. 8443, 10250),
- antywirus lub firewall blokujący połączenia do lokalnej VM,
- zbyt mało RAM – minikube startuje, ale pody wiecznie wiszą w stanie Pending.
Jeśli masz Docker Desktop, najwygodniej jest użyć sterownika docker, bo unikasz VM i problemów z wirtualizacją.
Instalacja minikube krok po kroku
Zakładając, że Docker już działa, możesz postawić minikube bardzo szybko:
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
(na macOS lub Windows użyjesz odpowiedniego instalatora z dokumentacji minikube).
Start klastra z użyciem Dockera:
minikube start --driver=docker
Jeśli wszystko pójdzie dobrze, zobaczysz informację o uruchomionym klastrze i skonfigurowanym kubectl. Podstawowa komenda weryfikująca:
kubectl get nodes
Oczekiwany wynik: jeden node w stanie Ready. To znak, że można przejść do pierwszego wdrożenia aplikacji.
Kontekst kubeconfig i praca z wieloma klastrami
Plik ~/.kube/config przechowuje dane o klastrach, użytkownikach i kontekstach. Kontekst mówi kubectl, do którego klastra ma się łączyć domyślnie. Gdy do minikube dojdzie np. klaster w chmurze, łatwo się pogubić, dlatego warto znać podstawowe komendy:
kubectl config get-contexts– lista wszystkich dostępnych kontekstów,kubectl config current-context– jaki kontekst jest obecnie ustawiony,kubectl config use-context minikube– przełączenie się na kontekstminikube.
Prosta zasada bezpieczeństwa: przed każdym zastosowaniem kubectl apply lub kubectl delete rzuć okiem na wynik kubectl config current-context. Jedna pomyłka może sprawić, że zamiast usunąć zasoby w minikube, skasujesz coś w produkcji.
Stabilne środowisko przed YAML-ami
Dopiero gdy masz pewność, że klaster działa, kubectl łączy się bez błędów, a context jest ustawiony na minikube, ma sens przechodzenie do pisania plików YAML. Wiele frustracji z Kubernetesem wynika z prób debugowania aplikacji przy niedziałającym lub niestabilnym klastrze. Najpierw solidny fundament, potem reszta.
Najważniejsze obiekty Kubernetes w praktyce, bez przesytu teorii
Pod, ReplicaSet, Deployment – trio do codziennej pracy
Podstawowa jednostka, którą uruchamia Kubernetes, to Pod. Pod to zazwyczaj jeden kontener (czasem kilka blisko ze sobą powiązanych). W praktyce jednak rzadko tworzysz pody bezpośrednio. Zamiast tego korzystasz z wyższego poziomu abstrakcji.
- Pod – pojedyncza instancja aplikacji (lub kilku kontenerów). Jeśli padnie, sam go nie wstanie – potrzebny jest mechanizm wyżej.
- ReplicaSet – obiekt pilnujący, aby liczba podów konkretnego typu się zgadzała (np. 3 repliki). Gdy jeden padnie, tworzy nowy.
- Deployment – obiekt, z którym pracujesz na co dzień. Deklarujesz w nim szablon poda, liczbę replik i sposób aktualizacji (rollout). Deployment sam tworzy ReplicaSety.
Typowy workflow: edytujesz YAML Deploymentu, następnie stosujesz go komendą kubectl apply. Deployment aktualizuje ReplicaSet, a ten odpowiednio skaluje pody. Przy aktualizacji obrazu Deployment stworzy nowego ReplicaSeta, powoli przełączy ruch i usunie stary, jeśli wszystko poszło dobrze.
Service: stały adres dla zmiennych podów
Pod to twór ulotny: może zostać przeniesiony na inny węzeł, zrestartowany, dostać nowy IP. Dlatego komunikowanie się z podami bezpośrednio jest kiepskim pomysłem. Tu wchodzi Service – stabilny punkt kontaktu dla aplikacji.
Najczęstsze typy Service:
- ClusterIP – domyślny. Wystawia usługę pod stałym adresem IP dostępnym tylko wewnątrz klastra. Używany do komunikacji między usługami.
- NodePort – wystawia usługę na każdym węźle na jednym porcie (zwykle wysokim). Przydatny lokalnie lub w prostych scenariuszach.
- LoadBalancer – deleguje stworzenie zewnętrznego load balancera (np. w chmurze). Standardowy sposób wystawiania usług na świat w środowiskach produkcyjnych.
Service używa selektorów (np. app: my-app), aby znaleźć pody, do których ma kierować ruch. Zachowuje się jak wbudowany, prosty load balancer – równoważy zapytania między replikami.
ConfigMap i Secret – konfiguracja zamiast przepisywania obrazu
Wyobraź sobie, że masz ten sam obraz aplikacji webowej, ale trzy środowiska: dev, test i prod. Za każdym razem, gdy zmienia się adres bazy albo endpoint zewnętrznego API, ktoś proponuje „przebudujmy obraz z innym plikiem konfiguracyjnym”. Trzy iteracje później nikt nie pamięta, który tag obrazu jest do czego.
W Kubernetes konfiguracja powinna żyć obok obrazu, a nie w nim. Do tego służą dwa podstawowe obiekty:
- ConfigMap – jawna konfiguracja typu „nie wstydzę się tego”: URL-e, flagi featurowe, nazwy kolejek,
- Secret – dane poufne: hasła, tokeny, klucze API, certyfikaty.
Najprostszy przykład ConfigMap jako zbiór zmiennych środowiskowych:
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
APP_ENV: "dev"
APP_LOG_LEVEL: "debug"
APP_API_URL: "https://api-dev.example.com"
Secret wygląda podobnie, ale przechowuje dane w formacie base64 (to nie jest pełne szyfrowanie, raczej utrudnienie czytania gołym okiem):
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
data:
DB_USER: bXl1c2Vy
DB_PASSWORD: c2VjcmV0cHdk
Prawdziwe wartości kodujesz tak:
echo -n "myuser" | base64
echo -n "secretpwd" | base64
Podłączenie ConfigMap i Secret do poda następuje zwykle przez zmienne środowiskowe w specyfikacji kontenera:
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: myapp-config
key: APP_ENV
- name: DB_USER
valueFrom:
secretKeyRef:
name: myapp-secret
key: DB_USER
Dzięki temu możesz używać tego samego obrazu w wielu środowiskach – różni je tylko zestaw ConfigMap i Secret. Gdy nagle trzeba zmienić endpoint zewnętrznego API, poprawiasz ConfigMap, robisz rollout i problem znika bez ingerencji w pipeline budowania obrazów.
Namespace – porządek w jednym klastrze
W pewnym momencie do lokalnego klastra zaczyna zaglądać więcej osób. Jeden z programistów wrzuca swoje pody do domyślnego namespace default, ktoś inny kasuje „stare” serwisy, żeby posprzątać. Po chwili okazuje się, że usunął testowy system kolegi.
Namespace to logiczna „przegroda” w klastrze. Pozwala trzymać różne środowiska lub projekty w jednym Kubernetesie, ale nie mieszać ich zasobów:
dev,test,stage– oddzielne miejsca na różne fazy cyklu życia aplikacji,team-a,team-b– podział między zespoły,- specjalne namespace jak
kube-systemczymonitoringdla narzędzi platformowych.
Utworzenie prostego namespace:
kubectl create namespace dev
Można też deklaratywnie, w YAML:
apiVersion: v1
kind: Namespace
metadata:
name: dev
Każdy Deployment, Service, ConfigMap czy Secret trafia do konkretnego namespace (o ile nie podasz innego, domyślny to default). W kubectl określasz go na dwa sposoby:
- globalnie ustawiając kontekst:
kubectl config set-context --current --namespace=dev - ad hoc:
kubectl get pods -n dev
Prosta zasada z praktyki: osobny namespace na każde środowisko aplikacji. Dzięki temu nie kasujesz cudzych zasobów „sprzątając po sobie”, a przy okazji możesz precyzyjniej ustawiać uprawnienia, limity i polityki sieciowe.

Przygotowanie aplikacji do wdrożenia: obraz kontenera i konfiguracja
Minimalny Dockerfile pod Kubernetes
Ktoś przynosi działającą aplikację Node.js albo Pythona i pyta: „To kiedy możemy ją wrzucić na Kubernetesa?”. Pierwsze pytanie brzmi: czy masz sensowny obraz kontenera. „Działa u mnie w Dockerze” jeszcze nie znaczy, że będzie komfortowo działać w klastrze.
Przykładowy, prosty Dockerfile dla aplikacji Node.js:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]
Kilka zasad, które ułatwiają życie w Kubernetesie:
- używaj oficjalnych, lekkich baz (np.
alpine) zamiast ciężkich obrazów z pełnym systemem, - buduj obraz tak, aby konfiguracja przychodziła z zewnątrz (env, pliki z ConfigMap),
- proces główny musi działać na pierwszym planie (PID 1) – bez demonizowania w tle,
- obsłuż poprawnie sygnały zakończenia (SIGTERM), żeby kontener zdążył się zamknąć po łagodnym shutdownie.
Budowanie i publikowanie obrazu
Przy lokalnym klastrze minikube możesz użyć lokalnego Dockera albo zewnętrznego rejestru. Dla prostego scenariusza wystarczy standardowy build:
docker build -t myuser/myapp:1.0.0 .
Następnie wypchnięcie do rejestru (np. Docker Hub):
docker push myuser/myapp:1.0.0
W prawdziwym projekcie lepiej zintegrować to z CI, ale na start ręczny build i push w zupełności wystarczą. Ważne, aby klaster miał dostęp do tego rejestru – lokalnie zwykle nie ma z tym problemu, w bardziej zamkniętych środowiskach trzeba dodać dane logowania jako Secret typu docker-registry.
Parametryzacja przez zmienne środowiskowe
Zamiast trzymać w obrazie stałe adresy czy hasła, aplikacja powinna czytać konfigurację z env. W praktyce często potrzebujesz:
- adresu bazy danych (host, port, nazwa bazy),
- credów (user, password) – z Secret,
- flagi mówiącej, w jakim środowisku działa (dev/stage/prod),
- ustawień logowania (poziom, format).
Jeżeli dziś aplikacja czyta plik konfiguracyjny z dysku, warto dodać obsługę zmiennych środowiskowych przynajmniej dla kluczowych parametrów. Później możesz ten plik generować z ConfigMap lub Secret, ale pierwszym krokiem jest „nauczenie” aplikacji patrzenia w env.
Pierwszy Deployment krok po kroku: od YAML do działającego poda
Prosty Deployment dla aplikacji webowej
Załóżmy, że masz obraz myuser/myapp:1.0.0, który wystawia HTTP na porcie 3000. Czas zamienić to na Deployment, który uruchomi jedną lub kilka replik.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myuser/myapp:1.0.0
ports:
- containerPort: 3000
env:
- name: APP_ENV
value: "dev"
Zapisz to jako deployment.yaml i zastosuj:
kubectl apply -f deployment.yaml
Status podów sprawdzisz tak:
kubectl get pods
Powinny pojawić się dwa pody z nazwami w stylu myapp-deployment-xxxxxx-yyyyy w stanie Running. Jeśli któryś utknął w CrashLoopBackOff, trzeba zajrzeć do logów.
Diagnozowanie problemów z pierwszym Deployementem
Gdy pody nie startują, zamiast zgadywać, można przejść prostą ścieżkę diagnostyczną:
- Podgląd stanu Deploymentu:
kubectl describe deployment myapp-deployment - Lista podów z etykietą:
kubectl get pods -l app=myapp - Podgląd szczegółowy pada, który się nie podnosi:
kubectl describe pod <nazwa-poda> - Logi z aplikacji:
kubectl logs <nazwa-poda>
Najczęstsze przyczyny problemów na starcie:
- zła nazwa/tak obrazu (błąd ściągania image),
- aplikacja natychmiast się wyłącza przez błąd konfiguracyjny,
- port w kontenerze inny niż ten, który deklarujesz w
containerPort(nie blokuje startu, ale myli przy Service), - brak dostępu do zewnętrznego serwisu wymagany przy starcie (np. baza danych), a aplikacja nie ma retry.
Jeżeli obraz nie chce się ściągnąć (status ImagePullBackOff), można szybciej testować, budując obraz bezpośrednio w środowisku Dockera używanym przez minikube. Wtedy używa lokalnego cache i nie musi uderzać do zewnętrznego rejestru.
Skalowanie ręczne: zmiana liczby replik
Pierwsza zabawa ze skalowaniem sprowadza się do prostej zmiany liczby replik. Masz 2 pody – zobacz, co się stanie, gdy ustawisz 5:
kubectl scale deployment myapp-deployment --replicas=5
Po chwili:
kubectl get pods -l app=myapp
Powinno być pięć podów w stanie Running. Ten sam efekt osiągniesz edytując YAML i używając kubectl apply -f. W praktyce ręczne skalowanie przydaje się w sytuacjach awaryjnych (np. nagły ruch marketingowy), zanim wdrożysz automatyczne HPA.
Rolowanie nowej wersji obrazu
Zmiana wersji aplikacji polega zazwyczaj na podmianie taga obrazu. Możesz to zrobić edytując YAML i stosując go na nowo albo używając prostego polecenia:
kubectl set image deployment/myapp-deployment
myapp=myuser/myapp:1.1.0
Deployment zacznie rollout: uruchomi nowe pody z wersją 1.1.0 i stopniowo wyłączy stare, jeśli wszystko działa. Postęp obserwujesz tak:
kubectl rollout status deployment/myapp-deployment
Gdyby nowa wersja okazała się wadliwa, możesz szybko wrócić do poprzedniej konfiguracji:
kubectl rollout undo deployment/myapp-deployment
Ta możliwość cofnięcia zmian bez grzebania w Git często ratuje skórę, szczególnie na testowych środowiskach, gdzie eksperymentujesz z parametrami aplikacji.
Udostępnienie aplikacji: Service i pierwszy dostęp z zewnątrz
Service typu ClusterIP – komunikacja wewnętrzna
Masz działające pody, ale bez Service są „bezimienne” dla reszty klastra. Nawet jeśli IP poda da się podejrzeć, zmieni się przy restarcie. Pierwszy krok to nadanie aplikacji stałego adresu w klastrze.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 3000
type: ClusterIP
W tym przykładzie:
selectorwskazuje na pody z etykietąapp: myapp,port80 to port Service – taki, którego będą używać inni klienci,targetPort3000 to port w kontenerze.
Zastosowanie manifestu:
kubectl apply -f service.yaml
Service dostanie własne IP z puli klastra oraz wpis DNS: w tym samym namespace aplikacje odwołają się do niego po nazwie myapp-service. To wystarczy, aby np. backend mógł zawołać frontend lub odwrotnie, bez martwienia się o zmieniające się IP poszczególnych podów.
Service typu NodePort – pierwszy kontakt z przeglądarki
Na lokalnym minikube najszybsza droga do sprawdzenia, czy aplikacja faktycznie działa „z zewnątrz”, to Service typu NodePort. Wystawia wybrany port każdego węzła na zewnątrz, przekierowując ruch do podów.
Zmodyfikowana definicja Service może wyglądać tak:
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 3000
nodePort: 30080
type: NodePort
Po zastosowaniu manifestu sprawdź Service:
kubectl get service myapp-service
Adres dostępu będzie zależał od narzędzia. Dla minikube:
Dostęp przez minikube service i przeglądarkę
Częsty obrazek: Deployment śmiga, Service utworzony, ale w przeglądarce jedyne co widać to komunikat, że strona nie odpowiada. Zaczyna się więc ręczne zgadywanie IP noda i portu, a i tak coś nie działa. Z minikube można skrócić ten etap do jednego polecenia.
Jeżeli używasz minikube, najwygodniej skorzystać z wbudowanego tunelu:
minikube service myapp-service
Minikube otworzy tymczasowy proxy/tunel i zazwyczaj automatycznie wystrzeli URL w przeglądarce. Jeśli nie, w konsoli zobaczysz adres w stylu:
http://127.0.0.1:xxxxx
Ten adres przekierowuje ruch do Service, a ten – do jednego z podów Twojej aplikacji. Jeśli wszystko poszło dobrze, zobaczysz stronę startową aplikacji lub API zwróci spodziewaną odpowiedź JSON.
Przy realnym klastrze (np. zarządzanym w chmurze) typ NodePort bywa tylko etapem przejściowym. IP nodów zwykle nie jest nawet wystawione bezpośrednio do internetu, a do ruchu zewnętrznego używa się LoadBalancerów lub Ingressów.
Service typu LoadBalancer – prostszy dostęp w chmurze
W managed Kubernetesach (GKE, EKS, AKS) naturalnym krokiem jest Service typu LoadBalancer. Wyobraź sobie, że backend stoi już w klastrze, a produktowiec wysyła link do testów klientowi. Nie chcesz mu tłumaczyć, na którym nodzie i porcie leży aplikacja – URL ma być zwykłym adresem HTTP.
Definicja Service jest podobna do NodePort, ale typ zmienia się na LoadBalancer, a nodePort zwykle można pominąć:
apiVersion: v1
kind: Service
metadata:
name: myapp-lb
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 3000
type: LoadBalancer
Po utworzeniu:
kubectl apply -f myapp-lb.yaml
kubectl get service myapp-lb
przez chwilę status w kolumnie EXTERNAL-IP będzie <pending>. Gdy chmura utworzy load balancer, pojawi się tam publiczny adres IP lub hostname. To będzie Twój punkt wejścia z internetu – tymczasowy, ale już wystarczający na testy.
Ten mechanizm jest bardzo wygodny, ale ma koszt – każdy LoadBalancer to osobny zasób w chmurze (i osobna faktura). Przy większej liczbie serwisów dochodzi jeszcze temat certyfikatów HTTPS i routingu po hostach/ścieżkach, dlatego do bardziej złożonych scenariuszy dochodzi Ingress.
Prosty Ingress – jeden adres dla wielu serwisów
W wielu projektach przychodzi moment, gdy pojawia się drugi, trzeci serwis HTTP i robi się bałagan z portami. QA dopytuje, który port jest teraz od frontendu, a który od API, a developerzy mają otwartych po kilka zakładek z różnymi numerami. Ingress porządkuje to jednym adresem.
Aby użyć Ingressu, w klastrze musi działać kontroler Ingress (np. NGINX Ingress Controller). W minikube można go szybko włączyć:
minikube addons enable ingress
Załóżmy, że chcesz, aby aplikacja była dostępna pod hostem myapp.local. Prosty Ingress może wyglądać tak:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
rules:
- host: myapp.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Po zastosowaniu manifestu:
kubectl apply -f ingress.yaml
trzeba jeszcze skierować ruch z lokalnej maszyny na Ingress Controller. Dla minikube:
minikube ip
zwróci adres IP klastra. Dodaj wpis do pliku /etc/hosts (na Windows odpowiedni plik w katalogu systemowym):
<IP_z_minikube_ip> myapp.local
Po tym kroku przejście pod http://myapp.local powinno trafić do Ingressu, a dalej do Twojego Service. Z zewnątrz wygląda to jak zwykły host w sieci, wewnątrz wszystko pilnuje kubernetowy routing.
Ta warstwa przydaje się szczególnie, gdy zaczynasz korzystać z TLS. Kontrolery Ingress (np. z cert-managerem) potrafią automatycznie odświeżać certyfikaty, a Ty dbasz głównie o to, by manifesty miały poprawne hosty i anotacje.
Weryfikacja ruchu: który pod dostał request?
Na początku przydaje się upewnić, że load balancing pomiędzy podami faktycznie działa. Typowa sytuacja: zwiększasz liczbę replik, ale chcesz zobaczyć, czy ruch rozkłada się równomiernie.
Najprościej dodać do aplikacji endpoint diagnostyczny, który wypisuje np. nazwę poda lub hostname. W wielu językach pobranie hostname to jedna linijka. Wtedy przy każdym odświeżeniu strony możesz zobaczyć, czy zmienia się identyfikator instancji.
Jeśli nie masz takiej możliwości, wciąż możesz podejrzeć ruch od strony klastra. Dla HTTP wystarczy nawet prosty log access log w aplikacji – widać po czasie, czy żądania rozchodzą się na wiele instancji. Przy większym ruchu pomaga warstwa observability (Prometheus, Grafana, Jaeger), ale do pierwszych testów wystarczą zwykłe logi.
Automatyczne skalowanie: Horizontal Pod Autoscaler w praktyce
Pierwsze spotkanie z HPA
Moment, który prędzej czy później nadchodzi: aplikacja jest wystawiona, userów przybywa, a ktoś pyta, czy „to się samo skaluje”. Nie chcesz ręcznie podbijać liczby replik za każdym razem, gdy marketing odpala kampanię – tu wchodzi Horizontal Pod Autoscaler (HPA).
HPA zmienia liczbę replik Deploymentu (lub innego obiektu kontrolującego pody) w zależności od metryk, najczęściej zużycia CPU. Na początek przyda się prosty wariant oparty o CPU, bo jest dostępny praktycznie „z pudełka”.
Wymagania: metrics-server i limity zasobów
HPA nie zadziała bez metryk. W małych klastrach często trzeba doinstalować metrics-server. W minikube można to zrobić jednym poleceniem:
minikube addons enable metrics-server
Po chwili metryki CPU i pamięci zaczną się pojawiać w klastrze. Da się to sprawdzić tak:
kubectl top pods
kubectl top nodes
Drugi wymóg to zdefiniowane resources.requests dla kontenera – HPA przelicza procent obciążenia na podstawie requested CPU. Bez tego nie będzie mieć punktu odniesienia.
Przykład uzupełnionego kontenera:
containers:
- name: myapp
image: myuser/myapp:1.0.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
Tu kontener „prosi” o 100 milicore CPU (0.1 CPU) i 128 MiB RAM, a maksymalnie może dostać pięć razy więcej CPU i 512 MiB pamięci. Dla HPA 100m to 100% przy docelowym usage.
Tworzenie prostego HPA na podstawie CPU
Gdy aplikacja ma ustawione requests i w klastrze działa metrics-server, można dodać HPA jednym poleceniem:
kubectl autoscale deployment myapp-deployment
--cpu-percent=60
--min=2
--max=10
To utworzy obiekt HorizontalPodAutoscaler, który:
- utrzymuje średnie użycie CPU na poziomie 60% wartości requestu,
- nie schodzi poniżej 2 replik,
- nie przekracza 10 replik.
Aktualny stan można podejrzeć:
kubectl get hpa
W kolumnach zobaczysz docelowe usage, aktualne usage i aktualną liczbę replik. Jeżeli aplikacja jest mocno obciążona, HPA zacznie zwiększać liczbę podów, jeśli zaś ruch spadnie – powoli je zmniejszy, ale nie poniżej minReplicas.
HPA w YAML – pełna kontrola konfiguracji
W realnych projektach definicja HPA zwykle ląduje w repozytorium wraz z innymi manifestami. Przykładowy manifest może wyglądać tak:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
Tu widać wyraźnie, do którego Deploymentu HPA się „doczepia” (scaleTargetRef) oraz jaki jest target CPU. W wersji autoscaling/v2 można też definiować bardziej zaawansowane metryki (np. liczbę requestów na sekundę), ale CPU to rozsądny pierwszy krok.
Przy testach z obciążeniem dobrze obserwować zachowanie skalowania w czasie. Prosty watch pozwala zobaczyć, jak liczba podów rośnie i maleje:
watch -n 2 kubectl get hpa,myapp-deployment,pods -l app=myapp
Symulowanie obciążenia i obserwacja autoskalowania
Aby sprawdzić, czy HPA dział, trzeba dostarczyć aplikacji nieco „bólu”. W praktyce można użyć prostych generatorów ruchu, np. hey, ab albo nawet pętli w curl. Przykład bardzo prostego obciążenia z innego terminala:
while true; do
curl -s http://<adres_aplikacji>/ > /dev/null
done
Oczywiście w prawdziwych testach używa się bardziej kontrolowanych narzędzi, ale nawet taki „młotek” pokaże, czy CPU podów rośnie na tyle, aby HPA zareagowało. Po kilku minutach przy rosnącym ruchu liczba replik powinna zacząć rosnąć, a przy zatrzymaniu obciążenia – stopniowo spadać.
Kluczowy wniosek z tej zabawy: HPA nie działa natychmiast, ale reaguje z pewnym opóźnieniem, żeby nie przeskalowywać klastra przy krótkich pikach. Projektując aplikację trzeba więc uwzględnić, że krótkie „szpile” ruchu mogą być obsłużone przez aktualny zestaw podów i cache, zanim HPA dołoży kolejne repliki.
Zdrowie aplikacji: liveness, readiness i graceful shutdown
Kiedy Kubernetes uznaje, że pod żyje?
Na początku łatwo przyjąć założenie, że skoro proces w kontenerze działa, to aplikacja działa. Potem przychodzi awaria bazy, wątek się zawiesza, a użytkownicy zgłaszają błędy, mimo że z punktu widzenia klastra wszystko wygląda na „Running”. Tu wchodzą proby zdrowia.
Kubernetes ma dwa kluczowe typy prob dla aplikacji HTTP (i nie tylko):
- livenessProbe – mówi, czy kontener jest wciąż „żywy”; gdy zawiedzie, pod jest restartowany,
- readinessProbe – mówi, czy aplikacja jest gotowa do obsługi ruchu; gdy zawiedzie, pod jest wyłączany z load balancingu, ale nie restartowany.
Dodanie prostych prob HTTP
Załóżmy, że aplikacja wystawia endpoint /health, który zwraca 200, jeśli wszystko jest w porządku. Można go wykorzystać zarówno do liveness, jak i readiness:
containers:
- name: myapp
image: myuser/myapp:1.0.0
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
Tu liveness dostał nieco większe opóźnienie startowe, aby aplikacja miała czas wstać zanim zostanie uznana za „martwą”. Readiness jest bardziej czuły i szybciej zgłasza, że pod nie jest gotowy do ruchu, dzięki czemu Service przestaje do niego routować.
W praktyce endpointy dla liveness i readiness mogą mieć osobną logikę. Liveness może sprawdzać tylko to, czy proces nie jest w stanie „zombie”, a readiness – zależności zewnętrzne (np. dostępność bazy, kolejek). Dzięki temu padnięta baza spowoduje wyłączenie poda z ruchu, ale nie będzie się on w kółko restartował.
Graceful shutdown: jak uniknąć uciętych requestów
Przy aktualizacjach Deploymentu lub skalowaniu w dół Kubernetes wysyła do kontenera sygnał SIGTERM, a po określonym czasie (domyślnie 30 sekund) SIGKILL. Jeśli aplikacja nie reaguje poprawnie, część requestów może się urwać w połowie, a użytkownik zobaczy błędy.
Żeby tego uniknąć, aplikacja powinna:
- nasłuchiwać SIGTERM,
- przestać przyjmować nowe połączenia,
- poczekać, aż aktualne requesty się skończą,
- zakończyć proces w rozsądnym czasie (krótszym niż
terminationGracePeriodSecondsw specyfikacji poda).
Po stronie Deploymentu można dodatkowo wydłużyć czas na łagodne zakończenie:
Najczęściej zadawane pytania (FAQ)
Czym jest Kubernetes i czym różni się od samego Dockera?
Scenariusz bywa podobny: aplikacja od miesięcy śmiga w Docker Compose, a gdy pojawia się temat wysokiej dostępności i skalowania, nagle wszyscy mówią „wrzućmy to na Kubernetesa”. Wtedy wychodzi, że Docker to za mało, gdy masz więcej niż jeden serwer i chcesz, by wszystko działało automatycznie.
Docker uruchamia pojedyncze kontenery na konkretnej maszynie, a Kubernetes zarządza całym „stadem” kontenerów na wielu węzłach. Ty deklarujesz w YAML-u, ile replik i z jakim obrazem ma działać, a Kubernetes sam rozkłada je po węzłach, restartuje, gdy coś padnie, i wystawia ruch przez Service/Ingress. Innymi słowy: Docker to narzędzie do pakowania i uruchamiania, Kubernetes – do orkiestracji i utrzymania całości przy życiu.
Kiedy Kubernetes ma sens, a kiedy lepiej zostać przy Docker Compose?
Często wygląda to tak: startujesz z jedną apką na małym VPS-ie, Docker Compose ogarnia temat i przez dłuższy czas niczego więcej nie trzeba. Problem zaczyna się, gdy dochodzą kolejne usługi, środowiska testowe i wymagania typu SLA, monitoring, autoskalowanie.
Kubernetes ma sens, gdy:
- masz kilka usług lub mikrousługi, które muszą działać spójnie na wielu węzłach,
- potrzebujesz wysokiej dostępności i chcesz, by pody same się odradzały na innych maszynach,
- chcesz mieć powtarzalne środowiska (dev/test/stage/prod) z tą samą deklaracją YAML.
Przy jednej prostej aplikacji bez dużego ruchu, z jednym serwerem i minimalnym budżetem na utrzymanie – Kubernetes będzie armatą na muchę, a Docker Compose w zupełności wystarczy.
Jak postawić prosty klaster Kubernetes do nauki na laptopie?
Typowy początek: chcesz „dotknąć” Kubernetesa, ale nie masz budżetu na chmurę ani ochoty walczyć z siecią w firmowym środowisku. Tu wchodzi lokalny klaster – jedno polecenie, jeden node, można bez stresu psuć i przeinstalowywać.
Najprościej skorzystać z minikube lub kind/k3d:
- minikube – najczęściej wybierany na start, jeden węzeł, dużo dodatków i świetna dokumentacja,
- kind – Kubernetes odpalany wewnątrz Dockera, szybki i lekki, idealny do CI,
- k3d – klaster oparty na K3s, bardzo oszczędny zasobowo, dobry na słabsze laptopy.
Przy zainstalowanym Dockerze minikube podnosisz w kilku komendach: instalujesz binarkę, a potem uruchamiasz minikube start --driver=docker. Jeśli po kubectl get nodes widzisz jeden node w stanie Ready – masz gotowe środowisko do ćwiczeń.
Jakie są minimalne wymagania sprzętowe do lokalnego Kubernetesa i typowe problemy?
Często ktoś odpala minikube na biurowym laptopie, wszystko „mieli” kilka minut, a potem pody wiszą w Pending i nic nie działa. Winny jest najczęściej zbyt słaby sprzęt lub blokady na poziomie wirtualizacji i sieci.
Do sensownej pracy lokalnej przydaje się:
- co najmniej 8 GB RAM w laptopie (ok. 4 GB dla klastra),
- minimum 2 vCPU przeznaczone na node,
- włączona wirtualizacja (VT-x/AMD-V) w BIOS/UEFI – jeśli używasz VM,
- działający Docker lub inny driver (VirtualBox, Hyper-V).
Najczęstsze problemy to: wyłączona wirtualizacja, porty zajęte przez inne usługi (np. 8443), firewall blokujący ruch do lokalnej VM oraz brak pamięci, przez co scheduler nie może nigdzie upchnąć podów.
Jak wygląda pierwsze wdrożenie aplikacji na Kubernetesie w praktyce?
Moment prawdy jest wtedy, gdy masz już obraz Dockera w rejestrze, klaster działa, a ty chcesz „po prostu” zobaczyć apkę w przeglądarce. Zamiast jednego docker-compose.yml dostajesz kilka plików YAML i nie bardzo wiadomo, co jest do czego.
Podstawowy zestaw na pierwsze wdrożenie wygląda zazwyczaj tak:
- Deployment – opisuje obraz, liczbę replik, zasoby i port kontenera,
- Service – wystawia pody w klastrze pod stałym adresem IP/nazwą DNS,
- (opcjonalnie) Ingress – jeśli chcesz mieć ładną domenę i routing HTTP/HTTPS.
Najpierw tworzysz Deployment i Service z pomocą kubectl apply -f, sprawdzasz kubectl get pods i kubectl get svc, a dopiero potem bawisz się w ingressy i bardziej zaawansowane mechanizmy skalowania.
Jak działa skalowanie aplikacji w Kubernetesie i od czego zacząć?
Częsty błąd na starcie: ktoś od razu próbuje konfigurować autoskalowanie po metrykach aplikacyjnych, gdy jeszcze nie ma stabilnie działającej jednej repliki. Prościej jest podejść do tematu dwustopniowo.
Na początku wystarczy ręczne skalowanie Deploymentu, np. kubectl scale deployment nazwa-app --replicas=3. Kubernetes sam zadba o uruchomienie nowych podów i rozłożenie ruchu przez Service. Gdy to działa i monitoring jest ogarnięty, można dołożyć HPA (Horizontal Pod Autoscaler), który na podstawie CPU, pamięci lub własnych metryk będzie automatycznie zwiększał lub zmniejszał liczbę replik.
Co to jest kubeconfig i jak przełączać się między wieloma klastrami?
Do momentu, gdy masz tylko minikube, wszystko jest proste. Prawdziwe zamieszanie zaczyna się, gdy dochodzi drugi klaster w chmurze, a twoje kubectl apply nagle ląduje nie tam, gdzie trzeba.
Plik ~/.kube/config przechowuje informacje o klastrach, użytkownikach i kontekstach. Kontekst mówi kubectl, z którym klastrem ma gadać domyślnie. Najważniejsze operacje to:
- podgląd kontekstów:
kubectl config get-contexts, - zmiana aktywnego klastra:
kubectl config use-context NAZWA, - dodawanie nowych kontekstów (np. z chmury) – zwykle robi to za ciebie narzędzie dostawcy lub odpowiedni instalator.
Ustawienie poprawnego kontekstu przed jakąkolwiek zmianą w klastrze szybko staje się nawykiem, który ratuje przed wdrożeniem testowej wersji prosto na produkcję.
Kluczowe Wnioski
- Kubernetes nie jest „czarną magią”, tylko zbiorem powtarzalnych klocków – sens ma zaczęcie od jednego działającego klastra i prostej aplikacji, a dopiero potem dokładanie ingressów, autoskalowania i reszty dodatków.
- Główna przewaga nad gołym Dockerem polega na deklaratywnym podejściu: opisujesz w YAML-u liczbę replik, zasoby i obraz, a Kubernetes sam decyduje, gdzie co uruchomić, jak wznowić padnięty kontener i jak rozłożyć ruch.
- Jako osoba wdrażająca aplikacje pracujesz głównie z kubectl, plikami YAML i rejestrem obrazów; szczegóły control plane i konfiguracja węzłów zwykle są domeną chmury lub administratora, więc nie trzeba od razu znać całej teorii.
- Kubernetes ma sens przy systemach rosnących – wiele usług, kilka środowisk (dev/test/stage/prod), wymagania wysokiej dostępności i poziome skalowanie; przy małym VPS-ie z jedną apką szybciej i taniej bywa zostać przy prostszych rozwiązaniach.
- Docker Compose sprawdza się jako lokalne środowisko i dla małych projektów, ale staje pod ścianą, gdy potrzebujesz uruchomienia na wielu serwerach, kontrolowanych rolloutów/rollbacków i łatwego dokładania replik.
- Praktyczny punkt odniesienia: gdy zaczynasz myśleć o mikrousługach, większej liczbie użytkowników lub rozdzielnych środowiskach, lepiej wcześniej wejść w podstawy Kubernetes niż robić nerwową migrację „na wczoraj”.
- Do nauki wystarczy lokalny klaster z minikube, kind lub k3d – pozwala przećwiczyć pełne wdrożenie (build obrazu → push do rejestru → Deployment + Service) bez inwestowania w „prawdziwą” infrastrukturę.
Źródła
- Kubernetes Documentation. Cloud Native Computing Foundation – Oficjalne definicje klastra, control plane, node, obiektów Deployment, Service, Ingress
- Kubernetes Patterns: Reusable Elements for Designing Cloud-Native Applications. O’Reilly Media (2019) – Wzorce wdrażania i skalowania aplikacji kontenerowych na Kubernetesie
- The Kubernetes Book. Independently published (Nigel Poulton) (2023) – Wprowadzenie do architektury klastra, podstawowych obiektów i praktyk operacyjnych
- Docker Documentation. Docker Inc. – Opis Docker Engine, Docker Compose, porównanie podejść do uruchamiania kontenerów
- Production Kubernetes. Manning Publications (2021) – Praktyki produkcyjne: wysoką dostępność, skalowanie, zarządzanie węzłami i zasobami






