Programowanie Full-Stack w Chmurze Obliczeniowej
dr inż. Sławomir Wojciech Przyłucki
Termin zajęć:
środa, godz. 11:30,
Imię i nazwisko:
Paweł Pieczykolan,
II rok studiów magisterskich, WOiSI 2.3.
Celem było przygotowanie kompletnego klastra Kubernetes w oparciu o Minikube, który miał składać się z jednego węzła głównego oraz trzech węzłów roboczych oznaczonych jako A, B i C. Klaster miał zostać uruchomiony z wykorzystaniem sterownika Docker oraz pluginu sieciowego CNI Calico, co zapewniało możliwość późniejszej konfiguracji polityk sieciowych.
Pierwszym krokiem było utworzenie zestawu plików manifestów YAML, które opisywały obiekty środowiska Kubernetes zgodnie z przyjętymi założeniami. W przestrzeni nazw frontend należało przygotować Deployment o nazwie frontend, bazujący na obrazie nginx i posiadający trzy repliki. Pod-y tego Deploymentu miały być uruchamiane na dowolnym węźle, z wyjątkiem węzłów, na których działały komponenty backend oraz baza danych MySQL. W przestrzeni nazw backend należało przygotować Deployment o nazwie backend, również oparty na obrazie nginx, ale posiadający tylko jedną replikę.
Kolejnym wymaganiem było utworzenie Poda my-sql w przestrzeni nazw backend, korzystającego z obrazu MySQL. Dla każdego z komponentów należało przygotować odpowiednie obiekty Service: dla frontend typu NodePort, a dla backendu i MySQL typu ClusterIP. Dzięki temu możliwe było zapewnienie właściwej komunikacji pomiędzy warstwami aplikacji oraz wystawienie frontendu na zewnątrz klastra.
Istotnym elementem zadania było opracowanie polityki sieciowej (NetworkPolicy), która miała ograniczyć komunikację pomiędzy komponentami. Zgodnie z założeniami, z Podów frontendu można było łączyć się wyłącznie z backendem na porcie 80, natomiast backend miał możliwość komunikacji z bazą danych MySQL, ale tylko na porcie 3306. W ten sposób zapewniono kontrolę nad przepływem danych i bezpieczeństwo aplikacji. Dodatkowo należało wprowadzić ograniczenia zasobów dla przestrzeni nazw – w frontend maksymalnie 10 Podów, 1 CPU i 1,5 Gi pamięci RAM, a w backend maksymalnie 3 Pody, 1 CPU i 1,0 Gi pamięci RAM.
Ostatnim wymaganiem było utworzenie autoskalera HPA dla Deploymentu frontend, który miał umożliwiać skalowanie liczby replik w zależności od obciążenia, ale jednocześnie nie przekraczać limitów zasobów przypisanych do przestrzeni nazw. Wszystkie parametry, które nie zostały jednoznacznie określone w treści zadania, należało dobrać samodzielnie i uzasadnić ich wybór. Na końcu należało zaproponować test, który potwierdzi poprawność konfiguracji przy obciążeniu Deploymentu frontend, a wyniki działania poleceń i konfiguracji przedstawić w sprawozdaniu.
Zadanie zostało zrealizowane w środowisku Windows 11 PRO z wykorzystaniem WSL. W ramach WSL uruchomiono system Ubuntu 24.04.1 LTS, który stanowił bazę do instalacji i konfiguracji Minikube, klastra Kubernetes oraz wszystkich komponentów opisanych w zadaniu.
Pierwszym krokiem w realizacji zadania było całkowite usunięcie poprzedniego klastra Minikube. W terminalu uruchomiono polecenie minikube delete, które usunęło kontener klastra, pliki konfiguracyjne oraz wpisy z Docker Engine. Dzięki temu możliwe było rozpoczęcie konfiguracji od zera, bez ryzyka konfliktów z wcześniejszymi ustawieniami.
Następnie uruchomiono nowy klaster Minikube z czterema węzłami, wykorzystując sterownik Docker oraz plugin sieciowy Calico. Polecenie:
minikube start --nodes=4 --cni=calico --driver=docker
zainicjowało proces tworzenia klastra. W pierwszej kolejności utworzony został węzeł główny, a następnie trzy węzły robocze: minikube-m02, minikube-m03 i minikube-m04. Każdy z nich został skonfigurowany z 2 CPU i 3072 MB pamięci RAM.
Rys. 1. Usunięcie poprzedniego klastra i utworzenie nowego
Po zakończeniu procesu tworzenia kontenerów, Minikube automatycznie skonfigurował komponenty Kubernetes w wersji 1.34.0. Polecenie
kubectl get nodes
potwierdziło, że wszystkie cztery węzły są aktywne i mają status Ready.
Rys. 2. Weryfikacja aktywności węzłów
W kolejnym kroku nadano aliasy węzłom roboczym, aby ułatwić przypisywanie zasobów. Węzłowi minikube-m02 przypisano etykietę node=A, minikube-m03 otrzymał node=B, a minikube-m04 node=C. Dzięki temu możliwe było precyzyjne sterowanie rozmieszczeniem Podów w dalszych etapach konfiguracji. Dodatkowo utworzono dwie przestrzenie nazw: frontend i backend, które odpowiadały za logiczne rozdzielenie komponentów aplikacji.
Rys. 3. Nadanie aliasów węzłom oraz utworzenie wymaganych przestrzeni nazw
W drugim etapie zadania skonfigurowano ResourceQuota dla przestrzeni nazw frontend oraz backend. Celem było ograniczenie liczby Podów oraz dostępnych zasobów CPU i pamięci RAM, aby zapewnić kontrolę nad wykorzystaniem klastra i uniknąć sytuacji, w której jeden komponent aplikacji zużywałby wszystkie dostępne zasoby. Dzięki temu możliwe było zachowanie równowagi pomiędzy warstwami aplikacji i zagwarantowanie stabilności działania całego środowiska.
Rys. 4. Zawartość pliku front-quota.yaml
Objaśnienie:
name: frontend-quota – nazwa zasobu dla przestrzeni frontend.
namespace: frontend – wskazuje, że quota dotyczy przestrzeni nazw frontend.
hard: – oznacza twarde limity, których nie można przekroczyć.
pods: "10" – maksymalna liczba Podów w przestrzeni frontend to 10.
requests.cpu: "1000m" – łączna ilość CPU dostępna dla Podów to 1 pełny rdzeń.
requests.memory: 1.5Gi – maksymalna ilość pamięci RAM dostępna dla Podów to 1,5 GiB.
Rys. 5. Zawartość pliku back-quota.yaml
Objaśnienie:
name: backend-quota – nazwa zasobu dla przestrzeni backend.
namespace: backend – quota dotyczy przestrzeni nazw backend.
hard: – twarde limity zasobów.
pods: "3" – maksymalna liczba Podów w przestrzeni backend to 3.
requests.cpu: "1000m" – łączna ilość CPU dostępna dla Podów to 1 rdzeń.
requests.memory: 1Gi – maksymalna ilość pamięci RAM to 1 GiB.
W konfiguracji użyto sekcji hard, ponieważ w tym zadaniu celem było ustalenie nieprzekraczalnych granic dla liczby Podów oraz zasobów CPU i pamięci. Twarde limity są najprostszym i najbardziej skutecznym sposobem kontroli – jeśli aplikacja spróbuje uruchomić więcej Podów lub zażąda większej ilości zasobów niż przewidziano, Kubernetes odrzuci takie żądanie. Dzięki temu administrator ma pewność, że przestrzeń nazw nie przekroczy przydzielonych zasobów i nie wpłynie negatywnie na inne komponenty klastra.
Rys. 6. Utworzenie obiektów typu ResourceQuota
Dodatkowo, w przestrzeni nazw backend utworzono obiekt typu LimitRange, który definiuje domyślne wartości zasobów dla kontenerów uruchamianych w tej przestrzeni. Celem było zapewnienie, że każdy nowy kontener będzie miał przypisane minimalne wartości CPU i pamięci, nawet jeśli nie zostały one jawnie określone w jego definicji. Dzięki temu uniknięto sytuacji, w której kontenery uruchamiane byłyby bez żadnych ograniczeń, co mogłoby prowadzić do niekontrolowanego zużycia zasobów.
Rys. 7. Zawartość pliku limit.yaml
Objaśnienie:
name: backend-limits – nazwa obiektu LimitRange.
namespace: backend – przestrzeń nazw, której dotyczy limit.
limits: – lista limitów zasobów.
type: Container – limit dotyczy pojedynczych kontenerów.
defaultRequest: – domyślne wartości zasobów, które zostaną przypisane kontenerowi, jeśli nie określono ich w jego definicji.
cpu: 100m – domyślny przydział CPU to 0.1 rdzenia.
memory: 128Mi – domyślny przydział pamięci RAM to 128 MiB.
Rys. 8. Utworzenie obiektu LimitRange
Dzięki zastosowaniu LimitRange, każdy kontener w przestrzeni backend ma zagwarantowane minimalne zasoby, co pozwala lepiej planować wykorzystanie klastra i zapobiegać przeciążeniom. LimitRange działa komplementarnie z ResourceQuota – quota ogranicza sumaryczne zużycie, a limit ustala wartości domyślne dla pojedynczych kontenerów.
Na tym etapie zbudowano trzy kluczowe elementy aplikacji: Pod bazy danych MySQL (w przestrzeni nazw backend), Deployment backend (1 replika na węźle B) oraz Deployment frontend (3 repliki na węźle A). Celem było rozdzielenie warstw aplikacji na różne węzły, utrzymanie spójnej komunikacji i zgodności z ograniczeniami zasobów oraz politykami sieciowymi. Rozmieszczenie Podów wymuszono etykietami węzłów (A, B, C) i nodeSelector, a dostępność sprawdzono poprzez proste healthchecki na głównym endpointcie serwera.
Wdrożenie Poda MySQL w backendzie pozwoliło zrealizować warstwę danych, a wybór Deploymentów dla frontend i backend zapewnił deklaratywne zarządzanie replikami i aktualizacjami. Readiness i liveness na ścieżce “/”: w tym scenariuszu wystarczało potwierdzenie odpowiedzi serwera HTTP. Wymagane requests CPU/RAM wpisują się w zdefiniowane ResourceQuota i LimitRange, gwarantując poprawne przyjęcie obiektów przez API oraz przewidywalne planowanie.
Rys. 9. Zawartość pliku mysql.yaml
Objaśnienie:name: my-sql – nazwa Poda, używana do identyfikacji.
namespace: backend – przestrzeń nazw, w której Pod ma zostać utworzony.
labels: – etykiety służące do selekcji i grupowania obiektów.
app: my-sql – etykieta logicznie identyfikująca aplikację (warstwę danych).
nodeSelector: – wymuszenie umieszczenia Poda na węźle o wskazanej etykiecie.
node: C – węzeł klastra oznaczony etykietą C, na którym Pod ma działać.
name: mysql – nazwa kontenera wewnątrz Poda.
image: mysql:8.0 – obraz kontenera MySQL w wersji 8.0.
env: – zmienne środowiskowe przekazywane do kontenera.
name: MYSQL_ROOT_PASSWORD – nazwa zmiennej definiującej hasło użytkownika root.
value: root – wartość hasła dla użytkownika root (na potrzeby testów).
containerPort: 3306 – port MySQL dostępny w kontenerze.
resources: – deklaracja zapotrzebowania na zasoby przez kontener.
requests: – minimalne gwarantowane zasoby, wymagane przez ResourceQuota.
cpu: 200m – minimalny przydział CPU to 0.2 rdzenia.
memory: 256Mi – minimalny przydział pamięci RAM to 256 MiB.
Rys. 10. Utworzenie Poda my-sql w przestrzeni nazw Backend
W PodzieMySQL dodano zmienną środowiskową MYSQL_ROOT_PASSWORD z wartością root, aby umożliwić uruchomienie serwera bazy danych z podstawową konfiguracją. Obraz MySQLcode> wymaga ustawienia hasła dla użytkownika administracyjnego, w przeciwnym razie kontener nie wystartuje. Wprowadzenie tej zmiennej pozwoliło na szybkie uruchomienie bazy w środowisku testowym i sprawdzenie poprawności komunikacji między komponentami. Wartość została dobrana prosto i czytelnie, ponieważ celem było testowanie polityk sieciowych, a nie produkcyjne zabezpieczenie danych.
Rys. 11. Zawartość pliku backend-dep.yaml
Objaśnienie:
name: backend – nazwa Deploymentu.
namespace: backend – przestrzeń nazw dla tego Deploymentu.
replicas: 1 – liczba replik (Podów) uruchamianych przez Deployment.
selector: – reguły selekcji Podów zarządzanych przez ten Deployment.
matchLabels: – etykiety, które muszą mieć Pody, aby były zarządzane.
app: backend – wartość etykiety dopasowującej Pody.
labels: – etykiety nadawane Podom z tego szablonu.
app: backend – etykieta Podów zgodna z selektorem.
nodeSelector: – wymuszenie umieszczania Podów na wybranym węźle.
node: B – węzeł klastra oznaczony etykietą B dla backendu.
name: nginx – nazwa kontenera aplikacyjnego.
image: nginx – obraz kontenera z serwerem Nginx.
containerPort: 80 – port HTTP serwera backendu.
Rys. 12. Utworzenie obiektu Deployment o nazwie Backend w przestrzeni nazw Backend
Rys. 13. Zawartość pliku front-dep.yaml
Objaśnienie:name: frontend – nazwa Deploymentu frontendu.
namespace: frontend – przestrzeń nazw dla frontendu.
replicas: 3 – liczba replik Podów frontendu.
selector: – reguły selekcyjne dla Podów zarządzanych przez Deployment.
matchLabels: – etykiety, które muszą mieć Pody.
app: frontend – wartość etykiety dopasowującej Pody.
labels: – etykiety przypisane Podom z szablonu.
app: frontend – etykieta Podów zgodna z selektorem.
nodeSelector: – wymuszenie umieszczania Podów frontendu na wskazanym węźle.
node: A – węzeł klastra oznaczony etykietą A dla frontendu.
affinity: – reguły rozmieszczania Podów względem innych Podów.
podAntiAffinity: – zakaz współlokacji z wybranymi Podami na tym samym hoście.
requiredDuringSchedulingIgnoredDuringExecution: – twardy zakaz przy planowaniu (ignorowany po uruchomieniu).
labelSelector: – selektor wskazujący Pody, względem których obowiązuje antyafinity.
matchLabels: – etykiety definiujące grupę docelowych Podów.
app: backend – antyafinity dotyczy Podów oznaczonych jako backend.
topologyKey: kubernetes.io/hostname – klucz topologii: odseparuj na poziomie hosta.
name: nginx – nazwa kontenera serwującego frontend.
image: nginx – obraz kontenera Nginx.
ports: – lista portów wystawianych przez kontener.
containerPort: 80 – port HTTP frontendu.
requests: – minimalne gwarantowane zasoby wymagane przez Quota.
cpu: 100m – minimalny przydział CPU to 0.1 rdzenia.
memory: 128Mi – minimalny przydział pamięci RAM to 128 MiB.
readinessProbe: – sonda gotowości (czy Pod może przyjmować ruch).
httpGet: – sprawdzenie przez żądanie HTTP GET.
path: / – ścieżka sprawdzająca stronę główną serwera.
port: 80 – port HTTP, na który kierowane jest sprawdzenie.
livenessProbe: – sonda żywotności (czy proces żyje).
httpGet: – sprawdzenie przez żądanie HTTP GET.
path: / – ścieżka główna serwera.
port: 80 – port HTTP sondy żywotności.
Rys. 14. Utworzenie obiektu Deployment o nazwie Frontend w przestrzeni nazw Frontend
Reguły affinity zostały wprowadzone, aby uniknąć pomyłki przy rozmieszczaniu Podów w klastrze i zapewnić separację warstw aplikacji. W szczególności zastosowano podAntiAffinity, które wymusza, aby Pody frontendu nie były uruchamiane na tym samym hoście co Pody backendu. Dzięki temu uniknięto sytuacji, w której oba komponenty działałyby na jednym węźle, co mogłoby prowadzić do przeciążenia zasobów i utrudniać testowanie polityk sieciowych. Rozdzielenie warstw zwiększa stabilność systemu i ułatwia zarządzanie ruchem w aplikacji.
Rys. 15. Healthcheck obiektu Frontend typu Deployment
Objaśnienie:readinessProbe.httpGet.scheme: HTTP – protokół sondy gotowości.
readinessProbe.initialDelaySeconds: 5 – opóźnienie startu sondy gotowości.
readinessProbe.periodSeconds: 10 – częstotliwość sprawdzania gotowości.
readinessProbe.timeoutSeconds: 2 – maksymalny czas oczekiwania na odpowiedź.
readinessProbe.successThreshold: 1 – liczba wymaganych sukcesów pod rząd.
readinessProbe.failureThreshold: 3 – liczba tolerowanych porażek zanim uzna NotReady.
livenessProbe.httpGet.scheme: HTTP – protokół sondy żywotności.
livenessProbe.initialDelaySeconds: 15 – opóźnienie startu sondy żywotności.
livenessProbe.periodSeconds: 20 – częstotliwość sprawdzania żywotności.
livenessProbe.timeoutSeconds: 2 – maksymalny czas oczekiwania na odpowiedź.
livenessProbe.successThreshold: 1 – liczba wymaganych sukcesów.
livenessProbe.failureThreshold: 3 – liczba porażek skutkująca restartem kontenera.
Sondy readinessProbe i livenessProbe zostały dodane, aby Kubernetes mógł automatycznie monitorować stan Podów. ReadinessProbe sprawdza, czy Pod jest gotowy do obsługi ruchu i dopiero wtedy oznacza go jako Ready, co zapobiega kierowaniu żądań do niedziałających kontenerów. LivenessProbe natomiast kontroluje, czy proces w kontenerze działa poprawnie – w przypadku awarii kubelet automatycznie restartuje Pod. Dzięki tym mechanizmom aplikacja jest bardziej odporna na błędy i zapewnia ciągłą dostępność usług.
W celu zapewnienia komunikacji pomiędzy komponentami aplikacji oraz umożliwienia dostępu do nich z zewnątrz lub wewnątrz klastra, utworzono trzy obiekty typu Service. Każdy z nich odpowiadał za ekspozycję konkretnego komponentu: frontend, backend oraz MySQL. Zastosowano dwa typy usług – NodePort dla frontendu, aby umożliwić dostęp spoza klastra, oraz ClusterIP dla backendu i bazy danych, ponieważ te komponenty miały komunikować się wyłącznie wewnętrznie.
Rys. 16. Zawartość pliku backend-svc.yaml
Objaśnienie:
name: backend-svc – nazwa usługi dla backendu.
namespace: backend – przestrzeń nazw, w której działa backend.
type: ClusterIP – typ usługi dostępnej tylko wewnątrz klastra.
selector: – etykieta wskazująca, do których Podów kierować ruch.
app: backend – etykieta Podów backendu.
ports: – lista portów obsługiwanych przez usługę.
port: 80 – port, na którym usługa nasłuchuje.
targetPort: 80 – port w kontenerze, do którego przekazywany jest ruch.
Rys. 17. Utworzenie obiektu Service dla Frontendu
Rys. 18. Zawartość pliku mysql-svc.yaml
Objaśnienie:
name: mysql-svc – nazwa usługi dla bazy danych.
namespace: backend – przestrzeń nazw, w której działa MySQL.
type: ClusterIP – usługa dostępna tylko wewnątrz klastra.
selector: – etykieta wskazująca na Pody MySQL.
app: my-sql – etykieta Poda bazy danych.
ports: – lista portów obsługiwanych przez usługę.
port: 3306 – port, na którym usługa nasłuchuje.
targetPort: 3306 – port w kontenerze MySQL, do którego kierowany jest ruch.
Rys. 19. Utworzenie obiektu Service dla Mysql
Rys. 20. Zawartość pliku frontend-svc.yaml
Objaśnienie:
name: frontend-svc – nazwa usługi dla frontendu.
namespace: frontend – przestrzeń nazw, w której działa frontend.
type: NodePort – typ usługi umożliwiający dostęp spoza klastra.
app: frontend – etykieta Podów frontendu.
ports: – lista portów obsługiwanych przez usługę.
port: 80 – port, na którym usługa nasłuchuje.
targetPort: 80 – port w kontenerze, do którego kierowany jest ruch.
nodePort: 30080 – port na węźle, przez który dostępna jest aplikacja z zewnątrz.
Rys. 21. Utworzenie obiektu Service dla Frontendu
W celu kontrolowania komunikacji pomiędzy komponentami aplikacji, utworzono zestaw polityk sieciowych w Kubernetes. Zamiast definiować każdą politykę w osobnym pliku, zastosowano podejście bardziej zorganizowane — wszystkie trzy polityki zostały zapisane w jednym pliku YAML. Dzięki temu możliwe było jednoczesne wdrożenie reguł dla frontendu, backendu oraz bazy danych MySQL, co uprościło zarządzanie konfiguracją i zapewniło spójność reguł.
Polityki zostały zaprojektowane zgodnie z wymaganiami zadania:
→ Frontend może komunikować się tylko z backendem na porcie 80.
→ Backend może komunikować się z frontendem na porcie 80 oraz z MySQL na porcie 3306.
→ MySQL akceptuje połączenia tylko z backendu na porcie 3306.
Rys. 22. Fragment dla Frontend-egress-policy z pliku networkpol.yaml
Objaśnienie:
name: frontend-egress-policy – nazwa polityki dla frontendu.
namespace: frontend – przestrzeń nazw, w której działa frontend.
app: frontend – polityka dotyczy Podów z etykietą app=frontend.
policyTypes: – lista typów ruchu kontrolowanych przez politykę.
Egress – polityka dotyczy ruchu wychodzącego.
egress: – reguły dla ruchu wychodzącego.
to: – dozwolone cele ruchu.
namespaceSelector: – selekcja przestrzeni nazw celu.
kubernetes.io/metadata.name: backend – docelowy namespace to backend.
podSelector: – selekcja Podów w docelowym namespace.
matchLabels: – dopasowanie po etykietach.
app: backend – docelowe Pody to backend.
ports: – lista dozwolonych portów.
protocol: TCP – dozwolony protokół.
port: 80 – dozwolony port HTTP.
Frontend pełni rolę warstwy prezentacji i powinien komunikować się wyłącznie z backendem.
▶ Egress: wybrano tylko ruch wychodzący, ponieważ frontend nie musi przyjmować połączeń z innych Podów – jego zadaniem jest wysyłanie żądań do backendu.
▶ Port 80/TCP: to standardowy port HTTP, na którym backend (nginx) nasłuchuje. Dzięki temu frontend może przesyłać żądania do backendu w sposób zgodny z architekturą aplikacji.
▶ NamespaceSelector + PodSelector: ograniczenie do Podów backendu w przestrzeni backend zapewnia, że frontend nie wyśle ruchu do innych usług w klastrze, co zwiększa bezpieczeństwo i izolację.
Rys. 23. Fragment dla Backend-ingress-egress-policy pliku networkpol.yaml
Objaśnienie:
name: backend-ingress-egress-policy – nazwa polityki dla backendu.
namespace: backend – przestrzeń nazw, w której działa backend.
app: backend – polityka dotyczy Podów z etykietą app=backend.
policyTypes: – lista typów ruchu kontrolowanych przez politykę.
Ingress – polityka dotyczy ruchu przychodzącego.
Egress – polityka dotyczy ruchu wychodzącego.
ingress: – reguły dla ruchu przychodzącego.
from: – dozwolone źródła ruchu.
namespaceSelector: – selekcja przestrzeni nazw źródła.
kubernetes.io/metadata.name: frontend – źródłowy namespace to frontend.
podSelector: – selekcja Podów w źródłowym namespace.
matchLabels: – dopasowanie po etykietach.
app: frontend – dozwolone źródłowe Pody to frontend.
ports: – lista dozwolonych portów.
protocol: TCP – dozwolony protokół.
port: 80 – dozwolony port HTTP.
egress: – reguły dla ruchu wychodzącego.
to: – dozwolone cele ruchu.
app: my-sql – docelowe Pody to MySQL.
ports: – lista dozwolonych portów.
protocol: TCP – dozwolony protokół.
port: 3306 – dozwolony port MySQL.
to: – dodatkowy cel ruchu.
namespaceSelector: – selekcja przestrzeni nazw celu.
kubernetes.io/metadata.name: frontend – docelowy namespace to frontend.
app: frontend – docelowe Pody to frontend.
ports: – lista dozwolonych portów.
protocol: TCP – dozwolony protokół.
port: 80 – dozwolony port HTTP.
Backend jest warstwą logiki aplikacji, która musi przyjmować żądania od frontendu i jednocześnie komunikować się z bazą danych oraz z frontendem.
▶ Ingress: konieczne, aby backend mógł odbierać ruch od frontendu. Bez tego frontend nie miałby możliwości przesyłania żądań.
▶ Egress: backend musi wysyłać zapytania do bazy danych MySQL (port 3306) oraz w niektórych scenariuszach odpowiadać do frontendu (port 80).
▶ Port 80/TCP (Ingress): backend nasłuchuje na porcie HTTP, aby obsługiwać żądania z frontendu.
▶ Port 3306/TCP (Egress): to standardowy port MySQL, wymagany do komunikacji z bazą danych.
▶ NamespaceSelector + PodSelector: reguły precyzyjnie wskazują, że backend może rozmawiać tylko z frontendem i MySQL, co eliminuje ryzyko nieautoryzowanych połączeń z innymi Podami.
Rys. 24. Fragment dla Mysql-ingress-policy pliku networkpol.yaml
Objaśnienie:
name: mysql-ingress-policy – nazwa polityki dla MySQL.
namespace: backend – przestrzeń nazw, w której działa MySQL.
app: my-sql – polityka dotyczy Podów z etykietą app=my-sql.
policyTypes: – lista typów ruchu kontrolowanych przez politykę.
Ingress – polityka dotyczy ruchu przychodzącego.
ingress: – reguły dla ruchu przychodzącego.
from: – dozwolone źródła ruchu.
app: backend– dozwolone źródłowe Pody to backend.
ports: – lista dozwolonych portów.
protocol: TCP – dozwolony protokół.
port: 3306 – dozwolony port bazy MySQL.
MySQL jest warstwą danych i powinien być dostępny tylko dla backendu.
▶ Ingress: wybrano tylko ruch przychodzący, ponieważ baza danych nie powinna sama inicjować połączeń – jej rolą jest odpowiadanie na zapytania.
▶ Port 3306/TCP: to domyślny port MySQL, na którym serwer nasłuchuje. Ograniczenie do tego portu zapewnia, że backend może korzystać z bazy, ale nie ma możliwości używania innych portów.
▶ PodSelector (app=backend): tylko Pody backendu mogą łączyć się z bazą. Dzięki temu frontend ani inne komponenty nie mają bezpośredniego dostępu do danych, co jest zgodne z zasadą separacji warstw i zwiększa bezpieczeństwo.
Rys. 25. Utworzenie obiektów Networkpolicy
Każda polityka została dobrana zgodnie z rolą komponentu w architekturze aplikacji:
▶ Frontend → tylko wysyła ruch do backendu (Egress, port 80).
▶ Backend → odbiera ruch od frontendu i wysyła do MySQL (Ingress + Egress, porty 80 i 3306).
▶ MySQL → przyjmuje ruch tylko od backendu (Ingress, port 3306).
Takie podejście zapewnia minimalne, ale wystarczające reguły komunikacji, zgodne z zasadą least privilege – każdy komponent ma dostęp tylko do tego, czego potrzebuje.
Rys. 26. Nadanie odpowiednich etykiet obiektom bez etykiet
Na zakończenie konfiguracji, aby zapewnić pełną spójność i poprawne działanie polityk sieciowych oraz usług, wszystkim obiektom nadano odpowiednie labele. Dzięki temu selektory w plikach YAML mogły jednoznacznie wskazywać właściwe Pody, Deploymenty i Serwisy. Przykładowo, Deployment frontend otrzymał etykietę app=frontend, backend app=backend, a baza danych MySQL app=my-sql. Analogiczne etykiety zostały przypisane usługom sieciowym. Takie podejście gwarantowało, że reguły NetworkPolicy działały zgodnie z założeniami i nie dopuszczały nieautoryzowanego ruchu między komponentami.
Ostatnim etapem konfiguracji klastra było wdrożenie mechanizmu automatycznego skalowania replik frontendu w zależności od obciążenia zasobów. W tym celu utworzono obiekt typu HorizontalPodAutoscaler (HPA), który monitoruje zużycie CPU i pamięci przez Pody frontendu i dynamicznie dostosowuje ich liczbę w zakresie od 3 do 10 replik. Dzięki temu aplikacja może reagować na zmienne obciążenie – zwiększając liczbę instancji w czasie wzmożonego ruchu i zmniejszając ją, gdy zapotrzebowanie spada.
Objaśnienie:
name: frontend-hpa – nazwa autoskalera dla frontendu.
namespace: frontend – przestrzeń nazw, w której działa Deployment frontend.
minReplicas: 3 – minimalna liczba replik, poniżej której autoskaler nie schodzi.
maxReplicas: 10 – maksymalna liczba replik, której autoskaler nie przekracza.
scaleTargetRef: – odniesienie do obiektu, który ma być skalowany.
name: frontend – nazwa Deploymentu, który ma być skalowany.
behavior: – konfiguracja zachowania skalowania.
scaleUp.stabilizationWindowSeconds: 60 – okno czasowe przed zwiększeniem liczby replik (60 sekund).
scaleDown.stabilizationWindowSeconds: 60 – okno czasowe przed zmniejszeniem liczby replik (60 sekund).
metrics: – lista metryk, które będą monitorowane.
type: Resource< – typ metryki oparty na zasobach.
resource.name: cpu – monitorowanie zużycia CPU.
target.type: Utilization – typ celu: procentowe wykorzystanie.
averageUtilization: 70 – próg skalowania: 70% średniego wykorzystania CPU.
resource.name: memory – monitorowanie zużycia pamięci RAM.
averageUtilization: 70 – próg skalowania: 70% średniego wykorzystania pamięci.
Rys. 27. Utworzenie obiektu typu HorizontalPodAutoscaler
Wdrożenie HPA pozwala na dynamiczne dostosowanie liczby replik frontendu do aktualnego obciążenia.
▶ minReplicas = 3 – zapewnia minimalną dostępność aplikacji nawet przy niskim ruchu.
▶ maxReplicas = 10 – ogranicza nadmierne skalowanie, chroniąc zasoby klastra.
▶ CPU i RAM jako metryki – wybór obu zasobów pozwala reagować zarówno na intensywne przetwarzanie danych, jak i na wzrost zapotrzebowania na pamięć (np. przy dużych żądaniach HTTP).
▶ StabilizationWindow – zapobiega zbyt częstym zmianom liczby replik, co chroni aplikację przed fluktuacjami i zapewnia płynność działania.
Po wdrożeniu wszystkich komponentów aplikacji konieczne było przeprowadzenie testów weryfikujących poprawność konfiguracji. W tym celu użyto poleceń kubectl exec, które pozwalają uruchomić komendy wewnątrz działających Podów. Testy polegały na próbie zestawienia połączenia TCP z odpowiednimi adresami IP i portami usług. Wynik testu jednoznacznie wskazywał, czy komunikacja jest dozwolona (OK), czy też zablokowana (BLOCKED) przez polityki sieciowe.
Przed rozpoczęciem testów wykonano polecenia
kubectl get pods -A
kubectl get svc -A
oraz
kubectl get deploy -A
aby uzyskać listę wszystkich obiektów w klastrze wraz z przypisanymi adresami IP i portami. Dzięki temu możliwe było przygotowanie dokładnych testów łączności pomiędzy komponentami.
Rys. 28. Wyświetlenie przygotowanych obiektów na klastrze
Wyniki:
▶ Frontend-svc (NodePort) – IP: 10.102.84.81, port: 80:30080/TCP
▶ Backend-svc (ClusterIP) – IP: 10.104.250.133, port: 80/TCP
▶ MySQL-svc (ClusterIP) – IP: 10.107.4.201, port: 3306/TCP
kubectl exec -n frontend deploy/frontend -it -- bash -c \
'timeout 3 bash -c "exec 3<>/dev/tcp/10.104.250.133/80" && echo "Frontend → Backend:80 OK" || echo "Frontend → Backend:80 BLOCKED"'
Wynik: Frontend → Backend:80 OK. Potwierdzono, że frontend może komunikować się z backendem na porcie HTTP 80, zgodnie z polityką sieciową.
kubectl exec -n frontend deploy/frontend -it -- bash -c \
'timeout 3 bash -c "exec 3<>/dev/tcp/10.107.4.201/3306" && echo "Frontend → MySQL:3306 OK" || echo "Frontend → MySQL:3306 BLOCKED"'
Frontend → MySQL:3306 BLOCKED. Próba połączenia została zablokowana. To zgodne z założeniami – frontend nie powinien mieć bezpośredniego dostępu do bazy danych.
kubectl exec -n backend deploy/backend -it -- bash -c \
'timeout 3 bash -c "exec 3<>/dev/tcp/10.107.4.201/3306" && echo "Backend → MySQL:3306 OK" || echo "Backend → MySQL:3306 BLOCKED"'
Backend → MySQL:3306 OK. Backend ma dostęp do bazy danych na porcie 3306, co jest wymagane do obsługi logiki aplikacji.
kubectl exec -n backend deploy/backend -it -- bash -c \
'timeout 3 bash -c "exec 3<>/dev/tcp/10.102.84.81/80" && echo "Backend → Frontend:80 OK" || echo "Backend → Frontend:80 BLOCKED"'
Backend → Frontend:80 OK. Backend może komunikować się z frontendem na porcie 80, co potwierdza poprawne działanie serwisu i zgodność z polityką sieciową.
Rys. 29. Wykonane testy łączności
Weryfikacja działania aplikacji pokazała, że komunikacja w klastrze Kubernetes została poprawnie ograniczona i dopuszczona zgodnie z założeniami. Próba połączenia z bazą danych MySQL z zewnątrz przez port 3306 nie powiedzie się, ponieważ usługa została wystawiona jako ClusterIP, co oznacza, że jest dostępna wyłącznie wewnątrz klastra. Dodatkowo polityka sieciowa dla MySQL zezwala na ruch tylko z Podów backendu, dlatego nawet jeśli ktoś zna adres IP i port, próba połączenia z hosta lub z frontendu zostanie zablokowana. To rozwiązanie chroni bazę danych przed nieautoryzowanym dostępem i wymusza korzystanie z niej wyłącznie przez warstwę logiki aplikacji.
Podobnie nie uda się połączyć z usługami bez odpowiednich etykiet. W Kubernetes selektory w usługach i politykach sieciowych bazują na etykietach, dlatego tylko Pody oznaczone właściwymi wartościami (app=frontend, app=backend, app=my-sql) są objęte regułami komunikacji. Jeśli Pod nie ma przypisanej etykiety, nie zostanie rozpoznany przez usługę ani politykę i nie będzie mógł nawiązać połączenia. To mechanizm bezpieczeństwa, który eliminuje ryzyko przypadkowych lub nieautoryzowanych Podów wchodzących w interakcję z aplikacją.
Połączenia działają tam, gdzie zostały jawnie dozwolone. Frontend może komunikować się z backendem na porcie 80, ponieważ polityka frontend-egress-policy oraz usługa backend-svc dopuszczają taki ruch. Backend ma dostęp zarówno do frontendu, jak i do bazy MySQL, co zostało przewidziane w polityce backend-ingress-egress-policy. Dzięki temu backend może odbierać żądania od frontendu i jednocześnie wykonywać zapytania do bazy danych. MySQL przyjmuje połączenia wyłącznie od backendu na porcie 3306, co zostało wymuszone przez politykę mysql-ingress-policy. Frontend nie ma bezpośredniego dostępu do bazy, co jest zgodne z zasadą separacji warstw – frontend komunikuje się z backendem, a backend z bazą danych.
Aby wykazać, że konfiguracja frontendu (Service, HPA, polityki sieciowe) działa poprawnie pod obciążeniem, najlepszym podejściem jest połączenie realnego ruchu HTTP, ciągłej obserwacji autoskalera oraz weryfikacji dostępności aplikacji podczas skalowania. W tym celu najpierw uzyskano adres dostępowy usługi za pomocą polecenia:
minikube service frontend-svc -n frontend --url
W środowisku WSL ruch sieciowy do NodePort bywa niewidoczny bezpośrednio z hosta Windows, a to polecenie tworzy lokalny tunel do usługi i zwraca adres w formie http://127.0.0.1:XXXXX. Dzięki temu testy można wykonywać z hosta bez komplikacji sieciowych i bezpośredniego otwierania portów na węzłach, co w WSL jest bardziej niezawodne.
Rys. 30. Uzyskanie lokalnego adresu usługi dla frontendu
Do generowania kontrolowanego obciążenia użyto narzędzia wrk:
wrk -t2 -c20 -d300s http://127.0.0.1:37595/
Parametry zostały dobrane tak, by wiarygodnie pobudzić mechanizmy autoskalera: dwa wątki (t2) i 20 równoległych połączeń (c20) zwykle wystarczają, by podnieść wykorzystanie CPU w kontenerach nginx i wprowadzić HPA w tryb skalowania, a jednocześnie nie przeciążają klastra ani nie prowadzą do sztucznego zatykania stosu sieciowego. Dłuższy czas trwania testu (d300s, czyli 5 minut) jest kluczowy: HPA ma zdefiniowane okna stabilizacji dla scaleUp i scaleDown (60 sekund), więc potrzebujemy wielominutowego okna, by zaobserwować pełny cykl reakcji — wzrost replik powyżej wartości minimalnej i późniejsze stabilne utrzymanie albo powolne zejście, jeśli ruch spadnie. Krótsze testy mogą w ogóle nie przekroczyć progu (70% CPU/70% RAM) albo nie zdołają „przebić się” przez okna stabilizacji, dając mylący obraz braku skalowania.
Rys. 31. Rozpoczęcie testu obciążeniowego
W trakcie testu obciążeniowego kluczowe jest równoległe monitorowanie autoskalera i replik. Polecenie:
kubectl get hpa -n frontend -w
pokazuje w czasie rzeczywistym bieżącą średnią wartość CPU i RAM (np. cpu: 3%/70%) oraz aktualną liczbę replik, więc można bezpośrednio obserwować, czy HPA reaguje na ruch zgodnie z konfiguracją (minReplicas=3, maxReplicas=10, target 70%).
Użycie --url w minikube jest decyzją praktyczną wynikającą z ograniczeń WSL — eliminuje zmienne środowiskowe, które mogłyby zafałszować wynik (np. brak dostępu do NodePort z hosta). wrk generuje realistyczny ruch HTTP, który w przypadku nginx obciąża głównie CPU; to spójne z konfiguracją HPA, gdzie targetem jest średnie wykorzystanie CPU na poziomie 70%. Dodanie metryki pamięci w HPA jest poprawne i kompletną praktyką, ale test z wrk nie jest pamięciochłonny — jeśli celem byłaby weryfikacja również progu dla RAM, test należałoby rozszerzyć o scenariusze generujące większe zapotrzebowanie pamięci (np. serwowanie dużych plików lub testy aplikacji z intensywną alokacją). W tym scenariuszu skupiamy się na dowiedzeniu, że autoskalowanie frontendu zgodnie z progiem CPU i RAMu działa, a pod obciążeniem liczba replik rośnie z 3 w kierunku limitu, stale utrzymując dostępność.
Rys. 32. Obciążenie obiektu Deploymentu dla Frontendu w czasie
Ostatecznym dowodem poprawności konfiguracji jest korelacja trzech obserwacji: po pierwsze, stabilny dostęp do frontendu przez adres z --url przez cały czas trwania testu, co udowadnia poprawną ekspozycję usługi i brak przerw podczas skalowania. Po drugie, wzrost i późniejszy spadek liczby replik obserwowany przez polecenie:
kubectl get hpa -w
oraz w stanie Deploymentu, co potwierdza reakcję HPA na realny wzrost i spadek wykorzystania zasobów. Po trzecie, utrzymanie zasad komunikacji sieciowej niezależnie od skali (frontend nadal ma łączność z backendem, brak bezpośredniego dostępu do MySQL), co pokazują wcześniejsze testy TCP — skalowanie nie pomija NetworkPolicy ani nie zmienia selektorów Service. Konfiguracja jest poprawna: ruch jest obsługiwany, autoskalowanie reaguje, a bezpieczeństwo i separacja warstw są zachowane również pod obciążeniem.
1. Czy możliwe jest dokonanie aktualizacji aplikacji frontend (np. wersji obrazu kontenera) gdy aplikacja jest pod kontrolą opracowanego autoskalera HPA?
TAK. Horizontal Pod Autoscaler nie blokuje procesu aktualizacji Deploymentu – HPA działa równolegle z mechanizmami kontrolera Deployment i reaguje na bieżące obciążenie. Oznacza to, że można bez przeszkód zaktualizować obraz kontenera w Deploymentcie, a HPA nadal będzie monitorował metryki i dostosowywał liczbę replik. Dokumentacja Kubernetes potwierdza, że HPA współpracuje z Deploymentami i ReplicaSetami, nie ograniczając ich aktualizacji: Kubernetes Docs – Horizontal Pod Autoscaler
.
Aby zagwarantować ciągłość działania aplikacji i jednocześnie nie przekroczyć limitów zasobów zdefiniowanych w przestrzeni nazw frontend, należy odpowiednio dobrać parametry strategii rollingUpdate w Deploymentcie. Przykładowa konfiguracja:
yaml
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
a) Zawsze aktywne 2 Pody: Przy minimalnej liczbie replik ustawionej na 3 (zgodnie z HPA), parametr maxUnavailable: 1 gwarantuje, że w trakcie aktualizacji nigdy nie zostanie wyłączonych więcej niż jeden Pod naraz. Oznacza to, że co najmniej 2 Pody pozostaną aktywne i będą obsługiwać ruch.
b) Nieprzekroczenie limitów zasobów: Parametr maxSurge: 0 nie pozwala na utworzenie żadnego dodatkowego Poda ponad zdefiniowaną liczbę replik. Dzięki temu w trakcie aktualizacji liczba Podów nie wzrośnie gwałtownie i nie przekroczy limitów CPU, RAM ani maksymalnej liczby replik określonych w ResourceQuota dla namespace frontend.
c) Korelacja z ustawieniami HPA: dla frontendu zostało skonfigurowane z zakresem od 3 do 10 replik. Strategia rollingUpdate działa w tym zakresie bez konieczności zmian – autoskaler nadal może zwiększać liczbę replik w odpowiedzi na obciążenie. Jeśli jednak w namespace frontend obowiązują bardzo restrykcyjne limity (np. maksymalnie 4 Pody), należałoby dostosować maxReplicas w HPA do tego limitu, aby uniknąć konfliktu między autoskalerem a ResourceQuota. W przeciwnym razie HPA mógłby próbować utworzyć więcej Podów niż dopuszczają limity, co skutkowałoby błędami przy skalowaniu.
Podsumowanie: Aktualizacja aplikacji frontend jest możliwa nawet pod kontrolą HPA, ponieważ autoskaler nie blokuje procesu Deploymentu. Odpowiednio dobrane parametry rollingUpdate (maxUnavailable: 1, maxSurge: 0) zapewniają, że zawsze pozostaną aktywne co najmniej 2 Pody, a liczba replik nie przekroczy limitów zasobów. W razie potrzeby należy skorelować ustawienia HPA z ResourceQuota, aby uniknąć konfliktów podczas skalowania i aktualizacji.
Całe zadanie polegało na zbudowaniu kompletnej aplikacji w Kubernetes złożonej z frontendu, backendu i bazy MySQL, wraz z pełną konfiguracją zasobów, usług, polityk sieciowych oraz mechanizmów autoskalowania. Utworzono przestrzenie nazw z kwotami i limitami, wdrożono Deploymenty i Serwisy, a następnie zabezpieczono komunikację za pomocą NetworkPolicy. Testy TCP potwierdziły, że ruch odbywa się wyłącznie w dozwolonych kierunkach: frontend → backend, backend → MySQL, przy jednoczesnym blokowaniu niedozwolonych połączeń.
Dodatkowo wdrożono Horizontal Pod Autoscaler dla frontendu, który dynamicznie zwiększa liczbę replik w odpowiedzi na obciążenie. Testy obciążeniowe z użyciem wrk wykazały poprawne działanie HPA i utrzymanie dostępności aplikacji. Całość została zaprojektowana zgodnie z zasadą minimalnych uprawnień – każdy komponent ma dostęp tylko do niezbędnych zasobów, co zapewnia bezpieczeństwo, stabilność i skalowalność aplikacji.
































