Blog JSystems - uwalniamy wiedzę!
Blog JSystems - uwalniamy wiedzę!
„Uczyć się Dockera czy Kubernetesa?" - to jedno z najczęstszych pytań osób wchodzących w świat konteneryzacji. Tkwi w nim ukryte założenie, że to wybór typu albo-albo, jak Windows kontra Linux. Otóż nie. Docker i Kubernetes to nie rywale o ten sam tron - to dwa narzędzia robiące zupełnie różne rzeczy, które najczęściej działają razem. Docker pakuje pojedynczą aplikację w kontener. Kubernetes zarządza setkami takich kontenerów rozsianych po wielu serwerach. W tym artykule pokażemy konkretnym kodem, co każde z nich robi, gdzie kończy się Docker, a zaczyna Kubernetes - i uczciwie odpowiemy na najtrudniejsze pytanie: czy Twój projekt w ogóle potrzebuje Kubernetesa.
Wyobraź sobie firmę kurierską. Najpierw ktoś musi zapakować przesyłkę: włożyć produkt do pudełka, zabezpieczyć, nakleić etykietę. To jest Docker - bierze Twoją aplikację razem ze wszystkim, czego potrzebuje do działania (środowiskiem uruchomieniowym, bibliotekami, konfiguracją), i zamyka w jednym, przenośnym pudełku zwanym kontenerem (container). Takie pudełko działa identycznie na laptopie programisty, na serwerze testowym i na produkcji.
Ale jedna firma kurierska to nie jedno pudełko. To tysiące paczek, dziesiątki samochodów, magazyny, trasy, zastępstwa, gdy auto się zepsuje. Ktoś musi tym wszystkim dyrygować - i to jest właśnie Kubernetes. On nie pakuje przesyłek. On zarządza całą flotą spakowanych już kontenerów: decyduje, na którym serwerze co uruchomić, pilnuje, żeby zawsze działała zaplanowana liczba kopii, i przekierowuje ruch, gdy któraś maszyna padnie.
Docker pakuje aplikację. Kubernetes zarządza setkami spakowanych aplikacji działających na dziesiątkach serwerów. Jedno bez drugiego ma sens - ale razem tworzą fundament nowoczesnej infrastruktury.
Dlatego pytanie „Docker czy Kubernetes" jest jak pytanie „pudełko czy firma logistyczna". W praktyce: najpierw uczysz się pakować (Docker), a dopiero potem - jeśli rosną Ci skala i wymagania - dyrygować flotą (Kubernetes). Docker jest też warunkiem wstępnym: Kubernetes orkiestruje właśnie kontenery, więc bez ich zrozumienia nie ma się czym dyrygować.
Sercem Dockera jest obraz (image) - przepis na kontener - i sam kontener, czyli uruchomiona instancja tego przepisu. Obraz budujesz na podstawie pliku Dockerfile, który opisuje krok po kroku, jak złożyć środowisko aplikacji. Oto realistyczny przykład dla prostej usługi w Pythonie:
# Bazujemy na lekkim obrazie z Pythonem
FROM python:3.12-slim
# Katalog roboczy wewnątrz kontenera
WORKDIR /app
# Najpierw zależności - ta warstwa cache'uje się, dopóki nie zmienisz requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Potem kod aplikacji
COPY . .
# Port, na którym nasłuchuje aplikacja
EXPOSE 8000
# Polecenie startowe kontenera
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
Mając taki plik, budujesz obraz i uruchamiasz kontener dwoma poleceniami. To wszystko dzieje się na jednej maszynie - Twoim laptopie albo pojedynczym serwerze:
# Zbuduj obraz i nadaj mu nazwę
user@host:~$ docker build -t sklep-api:1.0 .
[+] Building 12.3s (10/10) FINISHED
=> => naming to docker.io/library/sklep-api:1.0
# Uruchom kontener w tle, mapując port 8000 hosta na port kontenera
user@host:~$ docker run -d -p 8000:8000 --name sklep sklep-api:1.0
a3f9c1d4e8b2...
# Sprawdź, że działa
user@host:~$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
a3f9c1d4e8b2 sklep-api:1.0 Up 8 seconds 0.0.0.0:8000->8000/tcp sklep
Większość aplikacji nie jest jednak samotną wyspą - potrzebuje bazy danych, czasem cache'u czy kolejki. Tu wkracza docker compose: opisujesz wszystkie usługi w jednym pliku compose.yaml i podnosisz je razem jedną komendą. To wciąż jeden host, ale już kilka współpracujących kontenerów:
services:
api:
build: . # buduj z lokalnego Dockerfile
ports:
- "8000:8000"
environment:
DATABASE_URL: "postgresql://app:secret@db:5432/sklep"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: sklep
volumes:
- dbdata:/var/lib/postgresql/data # dane przeżywają restart
volumes:
dbdata:
Komenda docker compose up -d uruchamia obie usługi, tworzy między nimi prywatną sieć (dzięki czemu api łączy się z bazą po nazwie db) i pilnuje wolumenu z danymi. Dla ogromnej liczby projektów to wystarcza w zupełności. Masz konteneryzację, powtarzalne środowisko i wdrożenie jedną komendą. Pytanie brzmi: czego tu jeszcze brakuje?
Cała ta układanka żyje na jednym hoście. A teraz wyobraź sobie typowe wymagania produkcyjne, których sam Docker nie spełni:
docker compose up ubije stary kontener i postawi nowy - przez chwilę usługa jest niedostępna.To nie wady Dockera - to po prostu nie jego zadanie. Dokładnie te problemy rozwiązuje warstwa orkiestracji (orchestration), a jej najpopularniejszym przedstawicielem jest Kubernetes.
Kubernetes (w skrócie K8s) to system orkiestracji kontenerów działający na klastrze wielu maszyn. Maszyny te dzielą się na węzły robocze (worker nodes), gdzie faktycznie biegną kontenery, oraz warstwę sterującą (control plane), która podejmuje decyzje. Najmniejszą jednostką, którą zarządza Kubernetes, jest pod - opakowanie wokół jednego lub kilku ściśle powiązanych kontenerów.
Kluczowa różnica filozoficzna: w Dockerze mówisz jak coś uruchomić (krok po kroku). W Kubernetesie deklarujesz jaki ma być efekt końcowy, a klaster sam do tego stanu dąży i go pilnuje. Opisujesz to w pliku YAML. Oto Deployment - obiekt, który mówi: „utrzymuj trzy działające kopie tej aplikacji":
apiVersion: apps/v1
kind: Deployment
metadata:
name: sklep-api
spec:
replicas: 3 # zawsze utrzymuj 3 instancje
selector:
matchLabels: { app: sklep-api }
strategy:
type: RollingUpdate # aktualizacja bez przestoju
template:
metadata: { labels: { app: sklep-api } }
spec:
containers:
- name: api
image: registry.example.com/sklep-api:1.0
ports:
- containerPort: 8000
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
livenessProbe: # jak sprawdzić, czy aplikacja żyje
httpGet: { path: /healthz, port: 8000 }
initialDelaySeconds: 5
periodSeconds: 10
Trzy repliki to fundament odporności, ale potrzebny jest jeszcze jeden element: stały adres, pod którym aplikacja jest dostępna, oraz rozkładanie ruchu między te repliki. Robi to obiekt Service:
apiVersion: v1
kind: Service
metadata:
name: sklep-api
spec:
selector:
app: sklep-api # kieruj ruch do podów z tą etykietą
ports:
- port: 80 # port usługi w klastrze
targetPort: 8000 # port kontenera
type: ClusterIP # wewnętrzny równoważnik obciążenia
Te dwa pliki wdrażasz na klaster narzędziem kubectl - i od tej chwili Kubernetes sam dba o resztę. Zobacz, jak wygląda samonaprawa (self-healing) w praktyce: gdy ręcznie usuniesz jeden z podów (symulując awarię), klaster natychmiast tworzy nowy, żeby wrócić do zadeklarowanych trzech replik:
devops@k8s:~$ kubectl apply -f sklep-api.yaml
deployment.apps/sklep-api created
service/sklep-api created
devops@k8s:~$ kubectl get pods -l app=sklep-api
NAME READY STATUS RESTARTS AGE
sklep-api-6c8d4f9b7-2xk4p 1/1 Running 0 40s
sklep-api-6c8d4f9b7-9qm7t 1/1 Running 0 40s
sklep-api-6c8d4f9b7-h5rng 1/1 Running 0 40s
# Symulujemy awarię - kasujemy jeden pod
devops@k8s:~$ kubectl delete pod sklep-api-6c8d4f9b7-2xk4p
pod "sklep-api-6c8d4f9b7-2xk4p" deleted
# Kilka sekund później - Kubernetes już dostawił nowy pod
devops@k8s:~$ kubectl get pods -l app=sklep-api
NAME READY STATUS RESTARTS AGE
sklep-api-6c8d4f9b7-9qm7t 1/1 Running 0 70s
sklep-api-6c8d4f9b7-h5rng 1/1 Running 0 70s
sklep-api-6c8d4f9b7-tn4wz 1/1 Running 0 6s
To samo deklaratywne podejście daje resztę „supermocy". Skalowanie do dziesięciu instancji to jedna komenda, a podmiana wersji obrazu uruchamia aktualizację bez przestoju (rolling update) - Kubernetes wymienia pody po kolei, cały czas utrzymując usługę dostępną:
# Skaluj do 10 replik jedną komendą - klaster sam rozłoży je po węzłach
devops@k8s:~$ kubectl scale deploy/sklep-api --replicas=10
deployment.apps/sklep-api scaled
# Wdróż nową wersję bez przerwy w działaniu (rolling update)
devops@k8s:~$ kubectl set image deploy/sklep-api api=registry.example.com/sklep-api:1.1
deployment.apps/sklep-api image updated
devops@k8s:~$ kubectl rollout status deploy/sklep-api
Waiting for rollout: 6 of 10 updated replicas are available...
deployment "sklep-api" successfully rolled out
Tego sam Docker nie zrobi - i właśnie dlatego Kubernetes powstał. Skalowanie pod ruch, samonaprawa, aktualizacje bez przestoju i rozkładanie obciążenia (load balancing) między repliki to jego rdzeń.
Najprościej zobaczyć tę różnicę obrazowo. Po lewej Docker: jeden host, na nim Twoje kontenery. Po prawej Kubernetes: warstwa sterująca dyrygująca wieloma węzłami, a na każdym z nich pody rozmieszczane i przenoszone automatycznie.
Zestawienie na sucho, żeby uporządkować, co należy do której warstwy:
| Cecha / zadanie | Sam Docker | Kubernetes |
|---|---|---|
| Pakowanie aplikacji w kontener | ✅ to jego rola | ❌ korzysta z gotowych obrazów |
| Uruchomienie na jednym hoście | ✅ | ✅ (przesada dla 1 hosta) |
| Wiele usług na jednym hoście | ✅ docker compose | ✅ |
| Wiele węzłów (klaster) | ❌ | ✅ rdzeń działania |
| Automatyczne skalowanie pod ruch | ❌ | ✅ autoskalowanie podów |
| Samonaprawa (restart padłych instancji) | ❌ tylko polityka restartu na 1 hoście | ✅ na całym klastrze |
| Aktualizacja bez przestoju | ❌ | ✅ rolling update |
| Równoważenie ruchu (load balancing) | ❌ (wymaga osobnego narzędzia) | ✅ wbudowane przez Service |
| Próg wejścia i koszt utrzymania | ✅ niski | ❌ wysoki |
To najważniejsza i najczęściej pomijana sekcja, bo Kubernetes bywa wybierany z mody, a nie z potrzeby. W wielu sytuacjach sam Docker - najczęściej z docker compose - to optymalny, dojrzały wybór:
Są jednak sytuacje, w których ta złożoność zwraca się z nawiązką. Kubernetes ma sens, gdy:
Prosta zasada decyzyjna: jeśli czytasz tę listę i przy większości punktów myślisz „to nie o nas" - prawdopodobnie Kubernetes jest dla Ciebie przedwczesny. Jeśli przytakujesz przy kilku - czas zacząć się nim poważnie interesować.
Między „sam docker run" a „pełny Kubernetes" jest całe spektrum opcji o różnym progu złożoności. Warto je znać, zanim sięgniesz po najcięższe narzędzie:
Z perspektywy nauki i wdrażania kolejność jest niemal zawsze ta sama, bo każda warstwa opiera się na poprzedniej. Próba przeskoczenia od razu do Kubernetesa to najczęstszy powód frustracji - K8s zakłada, że rozumiesz już kontenery, sieci i obrazy.
docker build, docker run, wolumeny, sieci. Po tym etapie umiesz skonteneryzować dowolną aplikację.Dobra wiadomość jest taka, że ta droga jest powtarzalna i da się jej nauczyć w przewidywalnym czasie - pod warunkiem, że idziesz warstwami i ćwiczysz na prawdziwym sprzęcie, a nie tylko czytasz dokumentację. Lokalny klaster (na przykład w narzędziu typu kind albo minikube) postawisz na laptopie w minutę i przećwiczysz na nim wszystko, co opisaliśmy wyżej, nie płacąc ani złotówki za chmurę.
Docker i Kubernetes nie konkurują - uzupełniają się. Docker pakuje pojedynczą aplikację w przenośny kontener, który działa tak samo wszędzie. Kubernetes bierze wiele takich kontenerów i zarządza nimi na klastrze maszyn: skaluje, naprawia, aktualizuje bez przestoju i rozkłada ruch. Sam Docker (z docker compose) wystarcza zaskakująco często - na jednym czy dwóch hostach, przy przewidywalnym ruchu, w małym zespole. Kubernetes opłaca się dopiero przy wysokiej dostępności, zmiennym ruchu, mikroserwisach i obecności kogoś, kto klaster utrzyma. Wybieraj narzędzie do problemu, nie problem do modnego narzędzia - a naukę prowadź w kolejności: najpierw pudełko, potem flota.
Szkolenie Docker i Kubernetes od zera -->
Komentarze (0)
Brak komentarzy...