Interaktívny sprievodca

Kubernetes od základov

Ak vieš pracovať s Linuxom a niečo ti hovorí sieťovanie, toto je sprievodca pre teba. Vysvetľuje prečo veci fungujú tak ako fungujú — nie len príkazy.

15 kapitol 20+ labov Minikube Reálne projekty

🚀 Úvod do Kubernetes

Čo je Kubernetes?

Kubernetes (skrátene K8s) je open-source platforma na orchestráciu kontajnerov. Zjednodušene povedané: hovoríš mu "chcem bežať 3 kópie tejto aplikácie" a K8s sa postará o to, aby vždy bežali — aj keď server spadne, aj keď treba škálovať.

💡
Analógia z Linuxu: Systemd spravuje procesy na jednom stroji. Kubernetes robí to isté, ale pre stovky strojov a tisíce kontajnerov naraz. Je to ako systemd pre celé datacentrum.

Prečo práve Kubernetes?

🔁 Self-healing
Keď kontajner spadne, K8s ho automaticky reštartuje. Keď node zlyhá, presunie pody na iný.
📈 Škálovanie
Jeden príkaz a máš 10 kópií namiesto 1. HPA to robí automaticky podľa zaťaženia CPU.
🚀 Rolling updates
Nasadenie novej verzie bez výpadku — K8s postupne vymieňa staré pody za nové.
⚙️ Deklaratívny prístup
Nepíšeš ako niečo urobiť, píšeš čo chceš mať. K8s sa postará o zvyšok.

Kľúčové pojmy (slovník)

Pojem Čo to je Linux analógia
ClusterSada serverov (nodes) riadených K8sCelý server park
NodeJeden fyzický alebo VM serverJeden server
PodNajmenšia jednotka — 1+ kontajnerovProces (PID)
DeploymentDefinuje, koľko podov chcemesystemd service
ServiceStabilná IP/DNS pre skupinu podovload balancer / iptables
NamespaceLogické oddelenie zdrojov v clustriLinux namespaces
IngressHTTP(S) router z internetu do clusteromnginx reverse proxy
PVCŽiadosť o perzistentné úložiskoLVM logical volume

🏗️ Architektúra Kubernetes

Kubernetes cluster sa skladá z dvoch druhov serverov: Control Plane (mozog) a Worker Nodes (svaly). Ty komunikuješ vždy len s Control Plane cez kubectl.

Kubernetes Cluster
Control Plane (master)
🧠 kube-apiserver — vstupná brána
💾 etcd — databáza stavu
📋 scheduler — kde poď bežať?
🔄 controller-manager — hlídač stavu
Worker Node (×N)
🤝 kubelet — agent na node
🔀 kube-proxy — sieť / iptables
🐳 containerd — spúšťa kontajnery
Pod
Container A
Container B
$ kubectl apply HTTPS :6443 kube-apiserver

Čo robí každá komponenta?

kube-apiserver
Jediný vstupný bod do clustra. Každý príkaz kubectl hovorí práve s ním cez REST API. Overuje autentifikáciu, autorizáciu (RBAC) a validuje YAML manifesty pred ich uložením do etcd.
etcd
Distribuovaná key-value databáza kde je uložený celý stav clustra — každý pod, deployment, secret, konfigurácia. Ak stratíš etcd bez zálohy, stratíš cluster. Na produkciu vždy 3 alebo 5 etcd inštancií.
kube-scheduler
Sleduje nové pody bez priradeného nodu a rozhoduje, kde sa spustia na základe dostupných zdrojov (CPU, RAM), affinity pravidiel a resource requests.
controller-manager
Riadi control loop — neustále porovnáva požadovaný stav (čo chceš) so skutočným stavom (čo beží). Ak Deployment chce 3 pody a bežia len 2, controller vytvorí 3.
kubelet
Agent bežiaci na každom worker node. Dostáva inštrukcie od API servera a zabezpečuje, že kontajnery definované v pode skutočne bežia. Je to ako systemd na jednotlivom node.
kube-proxy
Spravuje sieťové pravidlá (iptables/ipvs) na každom node. Vďaka nemu funguje Service — keď príde request na ClusterIP, kube-proxy ho presmeruje na konkrétny pod.
Minikube zjednodušenie: V Minikube beží Control Plane aj Worker Node v jednom VM/kontajneri. Ideálne na učenie — nepotrebuješ 3 servery.

⚙️ Inštalácia prostredia

1. Docker (container runtime)

bash
# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Pridaj seba do skupiny docker (bez sudo)
sudo usermod -aG docker $USER
newgrp docker

# Overenie
docker run hello-world

2. kubectl

bash
# Stiahni najnovšiu stabilnú verziu
curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

# Nainštaluj
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

# Overenie
kubectl version --client

# Voliteľné: bash autocomplete
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
source ~/.bashrc

3. Minikube

bash
# Stiahni a nainštaluj
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

# Spusti cluster (s Docker driverom)
minikube start --driver=docker --cpus=2 --memory=4096

# Skontroluj stav
minikube status
kubectl get nodes
výstup
NAME       STATUS   ROLES           AGE   VERSION
minikube   Ready    control-plane   1m    v1.32.0
Užitočné minikube príkazy:
minikube stop — zastav cluster
minikube delete — zmaž cluster (clean slate)
minikube dashboard — otvor web UI
minikube addons enable ingress — zapni nginx ingress

🐳 Docker & Docker Compose

Kontajner vs. virtuálny stroj

🖥️ Virtuálny stroj
Celý OS (kernel + libs) — typicky 1–10 GB, štartuje minúty
🐳 Kontajner
Zdieľa kernel hostu, len app + libs — typicky 10–200 MB, štartuje sekundy

Dockerfile — recept na image

dockerfile
# Základný PHP-FPM image
FROM php:8.2-fpm-alpine

# Inštaluj systémové závislosti
RUN apk add --no-cache git curl

# Nastav pracovný adresár
WORKDIR /var/www/html

# Skopíruj composer.json (oddelene kvôli Docker cache vrstiev!)
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader

# Skopíruj zvyšok kódu
COPY . .

# Použi non-root používateľa (bezpečnosť)
RUN chown -R www-data:www-data /var/www/html
USER www-data

EXPOSE 9000
CMD ["php-fpm"]
💡
Vrstvy a cache: Docker stavia image vo vrstvách. Ak zmeníš kód, ale nie composer.json, vrstvy s composer install sa znovu nezostavujú — šetrí čas. Preto COPY závislostí vždy pred COPY kódu.

Základné Docker príkazy

bash
# Zostavenie image
docker build -t moja-app:1.0 .
docker build -t moja-app:latest -f Dockerfile.prod .

# Spustenie kontajnera
docker run -d -p 8080:80 --name web nginx:alpine
docker run -it --rm alpine sh           # interaktívny, automaticky zmaže po ukončení

# Správa
docker ps                                # bežiace kontajnery
docker ps -a                             # všetky vrátane zastavených
docker logs -f web                       # log streaming
docker exec -it web sh                   # shell do bežiaceho kontajnera
docker stop web && docker rm web

# Images
docker images
docker pull nginx:alpine
docker push registry.example.com/moja-app:1.0
docker rmi moja-app:1.0

# Čistenie
docker system prune -a                   # POZOR: zmaže všetko nepoužívané

Docker Compose — lokálne vývojové prostredie

Compose je nástroj pre spúšťanie viacerých kontajnerov naraz. V Kubernetes ho nepoužívaš priamo, ale je dôležitý na lokálny vývoj a pochopenie multi-container architektúry.

yaml
# docker-compose.yml
version: '3.9'
services:
  app:
    build: .
    ports:
      - "8080:80"
    environment:
      DATABASE_URL: mysql://user:pass@db:3306/myapp
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - .:/var/www/html          # live reload pri vývoji

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: myapp
      MYSQL_USER: user
      MYSQL_PASSWORD: pass
    volumes:
      - db_data:/var/lib/mysql   # perzistentné dáta
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db_data:
bash
docker compose up -d        # spusti na pozadí
docker compose logs -f app  # sleduj logy
docker compose down         # zastav a zmaž kontajnery (dáta ostanú vo volume)
docker compose down -v      # zastav aj zmaž volumes
⚠️
Compose vs Kubernetes: Compose je výborný na lokálny vývoj. Na produkciu (škálovanie, self-healing, rolling updates) potrebuješ Kubernetes. Mnohé koncepty sú podobné — service name = DNS meno, volumes = PVC.

💻 kubectl — ovládanie clustra

kubectl je CLI nástroj, ktorý komunikuje s kube-apiserver cez HTTPS. Všetko čo urobíš v Kubernetes, prejde cez neho.

Základná štruktúra príkazu

kubectl  [AKCIA]  [RESOURCE]  [MENO]  [-n NAMESPACE]  [FLAGS]

Najdôležitejšie príkazy

bash
# ── GET: čo beží? ──────────────────────────────────────
kubectl get pods                          # pody v default namespace
kubectl get pods -n kube-system           # pody v konkrétnom namespace
kubectl get pods -A                       # pody vo VŠETKÝCH namespacoch
kubectl get pods -o wide                  # + IP, node
kubectl get pods -o yaml                  # kompletný YAML
kubectl get all -n moj-namespace          # pody + services + deploymenty

kubectl get nodes                         # zoznam nodes
kubectl get namespaces                    # zoznam namespacov
kubectl get services                      # services
kubectl get deployments                   # deploymenty
kubectl get pvc                           # persistent volume claims

# ── DESCRIBE: detailné info ────────────────────────────
kubectl describe pod           # udalosti, stav, logy kontajnerov
kubectl describe node minikube
kubectl describe deployment nginx

# ── APPLY / CREATE: nasadenie ──────────────────────────
kubectl apply -f deployment.yaml          # vytvor/aktualizuj z YAML (ODPORÚČANÉ)
kubectl apply -f ./k8s/                   # aplikuj celý adresár
kubectl create namespace prod             # rýchle vytvorenie bez YAML

# ── DELETE: mazanie ───────────────────────────────────
kubectl delete -f deployment.yaml         # zmaž čo definuje YAML
kubectl delete pod <meno>                 # zmaž konkrétny pod (deployment ho znovu vytvorí!)
kubectl delete deployment nginx
kubectl delete namespace test             # zmaže VŠETKO v namespace

# ── LOGS: logy ────────────────────────────────────────
kubectl logs <pod>                        # logy
kubectl logs <pod> -f                     # streaming
kubectl logs <pod> -c <container>        # konkrétny kontajner v pode
kubectl logs <pod> --previous            # logy po crashi (predchádzajúci beh)

# ── EXEC: príkazy v kontajneri ────────────────────────
kubectl exec -it <pod> -- sh              # shell do kontajnera (alpine)
kubectl exec -it <pod> -- bash            # bash (ubuntu/debian)
kubectl exec <pod> -- ls /var/www         # jednorázový príkaz

# ── PORT-FORWARD: lokálny prístup ─────────────────────
kubectl port-forward pod/<meno> 8080:80   # localhost:8080 → pod:80
kubectl port-forward svc/nginx 8080:80    # cez service
LAB 01 Prvé kroky v clustri
  1. Spusti minikube: minikube start
  2. Pozri sa na nodes: kubectl get nodes
  3. Pozri systémové pody: kubectl get pods -n kube-system
  4. Spusti testovací pod: kubectl run test --image=nginx:alpine
  5. Skontroluj stav: kubectl get pods
  6. Pozri detaily: kubectl describe pod test
  7. Otvor shell: kubectl exec -it test -- sh
  8. Vyčisti: kubectl delete pod test

📦 Pody (Pods)

Pod je najmenšia nasaditeľná jednotka v Kubernetes. Obsahuje jeden alebo viac kontajnerov, ktoré zdieľajú sieťový namespace (rovnaká IP, môžu si písať cez localhost) a volumes.

⚠️
Pody sú dočasné! Keď pod spadne alebo ho zmažeš, všetky dáta v ňom sú preč. Preto sa pody priamo nepoužívajú — vždy cez Deployment, ktorý zabezpečí náhradu.

YAML definícia podu

yaml
apiVersion: v1
kind: Pod
metadata:
  name: moj-pod
  namespace: default
  labels:
    app: web               # labely — kľúčové pre Selektory a Services
    verzia: "1.0"
spec:
  containers:
    - name: nginx
      image: nginx:alpine
      ports:
        - containerPort: 80
      resources:
        requests:
          cpu: "100m"      # 0.1 CPU jadra (minimum garantované)
          memory: "64Mi"
        limits:
          cpu: "200m"      # maximum (pri prekročení: throttling pre CPU, kill pre RAM)
          memory: "128Mi"
      env:
        - name: APP_ENV
          value: "production"
      livenessProbe:       # K8s reštartuje kontajner ak toto zlyhá
        httpGet:
          path: /healthz
          port: 80
        initialDelaySeconds: 10
        periodSeconds: 15
      readinessProbe:      # K8s posiela traffic len ak toto prejde
        httpGet:
          path: /ready
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 5

Životný cyklus podu

Pending ContainerCreating Running Succeeded / Failed
Pending — scheduler hľadá vhodný node, alebo sa sťahuje image
Running — aspoň jeden kontajner beží
CrashLoopBackOff — kontajner padá a K8s ho reštartuje (exponenciálny backoff)
ImagePullBackOff — nedokáže stiahnuť image (zlý tag, súkromný registry bez credentials)

🔄 Deploymenty & ReplicaSety

Deployment je to čo naozaj používaš v praxi. Hovorí K8s: "Chcem 3 kópie tejto aplikácie, vždy." ReplicaSet (vytvorený automaticky) udržuje presný počet podov.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: moj-projekt
spec:
  replicas: 3                          # chceme 3 pody
  selector:
    matchLabels:
      app: web-app                     # riadi pody s týmto labelom
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                      # max +1 pod počas updatu
      maxUnavailable: 0                # vždy 3 pody k dispozícii (zero downtime)
  template:                            # šablóna pre pody
    metadata:
      labels:
        app: web-app                   # musí sa zhodovať so selector.matchLabels
    spec:
      containers:
        - name: app
          image: nginx:1.25            # zmenou tagu sa spustí rolling update
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"

Správa deploymentu

bash
# Nasadenie
kubectl apply -f deployment.yaml

# Škálovanie
kubectl scale deployment web-app --replicas=5

# Update image (spustí rolling update)
kubectl set image deployment/web-app app=nginx:1.27

# Sleduj priebeh updatu
kubectl rollout status deployment/web-app

# História
kubectl rollout history deployment/web-app

# Rollback na predchádzajúcu verziu
kubectl rollout undo deployment/web-app

# Rollback na konkrétnu revíziu
kubectl rollout undo deployment/web-app --to-revision=2

# Reštart podov (napr. po zmene ConfigMap)
kubectl rollout restart deployment/web-app
LAB 02 Rolling Update bez výpadku
  1. Ulož deployment YAML vyššie ako deployment.yaml
  2. Vytvor namespace: kubectl create namespace moj-projekt
  3. Nasaď: kubectl apply -f deployment.yaml
  4. Sleduj pody: kubectl get pods -n moj-projekt -w
  5. V druhom termináli zmeň image: kubectl set image deployment/web-app app=nginx:alpine -n moj-projekt
  6. Pozoruj rolling update — pody sa postupne nahrádzajú
  7. Vyskúšaj rollback: kubectl rollout undo deployment/web-app -n moj-projekt

🌐 Services & Networking

Pody majú dočasné IP adresy. Keď pod reštartuje, dostane novú IP. Service dáva stabilné DNS meno a IP, za ktorou môže byť X podov. Funguje ako load balancer.

💡
Analógia: Service je ako DNS záznam + iptables pravidlo. Keď voláš http://web-app, CoreDNS preloží na ClusterIP, a kube-proxy prepošle request na jeden z bežiacich podov.

Typy Services

ClusterIP — default
Dostupná len vnútri clustra. Ideálna pre komunikáciu medzi službami (napr. app → databáza).
NodePort
Otvorí port (30000–32767) na každom node. Dostupná z vonku cez NODE_IP:PORT. Používaná v Minikube na testovanie.
LoadBalancer
Vytvorí externý load balancer u cloud providera (AWS ELB, GCP LB). V Minikube: minikube tunnel.
Ingress — nie je Service
HTTP/HTTPS router — riadi traffic podľa hostname a path. Jeden Ingress namiesto mnohých LoadBalancerov.
yaml
# ClusterIP — interná komunikácia
apiVersion: v1
kind: Service
metadata:
  name: web-app
  namespace: moj-projekt
spec:
  type: ClusterIP           # default, možno vynechať
  selector:
    app: web-app            # vyberie pody s týmto labelom
  ports:
    - port: 80              # port SERVICE
      targetPort: 80        # port KONTAJNERA

---
# NodePort — prístup zvonku (Minikube)
apiVersion: v1
kind: Service
metadata:
  name: web-app-ext
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080       # voliteľné, inak K8s priradí náhodný

DNS v Kubernetes

CoreDNS automaticky vytvára DNS záznamy pre každú Service. Z akéhokoľvek podu v clustri môžeš volať:

# Rovnaký namespace
http://web-app
# Iný namespace (plný DNS záznam)
http://web-app.moj-projekt.svc.cluster.local
# Skrátený tvar (iný namespace)
http://web-app.moj-projekt
LAB 03 Expose app a testuj DNS
  1. Nasaď deployment z Lab 02
  2. Vytvor NodePort service: kubectl expose deployment web-app --type=NodePort --port=80 -n moj-projekt
  3. Zisti URL: minikube service web-app -n moj-projekt --url
  4. Otvor URL v prehliadači alebo: curl $(minikube service web-app -n moj-projekt --url)
  5. Testuj DNS zvnútra: kubectl run -it --rm dns-test --image=alpine --restart=Never -n moj-projekt -- sh
    potom vnútri: wget -O- http://web-app

🔧 ConfigMaps & Secrets

Konfigurácia patrí mimo Docker image. ConfigMap ukladá necitlivé dáta (URL, feature flags), Secret citlivé (heslá, tokeny, certifikáty — base64 encoded).

yaml
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: moj-projekt
data:
  APP_ENV: "production"
  APP_URL: "https://moja-app.sk"
  nginx.conf: |                        # môže obsahovať aj celé súbory
    server {
        listen 80;
        root /var/www/html/public;
    }

---
# Secret (hodnoty musia byť base64: echo -n 'heslo' | base64)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: moj-projekt
type: Opaque
data:
  DB_PASSWORD: aGVzbG8xMjM=           # "heslo123" v base64
  API_KEY: c2VjcmV0a2V5              # "secretkey" v base64

Použitie v Pode — ako env premenné

yaml
spec:
  containers:
    - name: app
      image: moja-app:latest
      env:
        # Jednotlivé hodnoty z ConfigMap/Secret
        - name: APP_ENV
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: APP_ENV
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DB_PASSWORD
      envFrom:
        # VŠETKY hodnoty z ConfigMap naraz (prefix voliteľný)
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets

Použitie ako volume (súbory)

yaml
spec:
  volumes:
    - name: config-vol
      configMap:
        name: app-config
  containers:
    - name: nginx
      volumeMounts:
        - name: config-vol
          mountPath: /etc/nginx/conf.d  # každý kľúč = jeden súbor
🔐
Secrets nie sú skutočne bezpečné! Base64 je kódovanie, nie šifrovanie. Každý kto má prístup do clustra môže Secret prečítať. Pre produkciu použi Sealed Secrets, Vault alebo External Secrets Operator.
bash
# Rýchle vytvorenie Secret z príkazového riadku (bez YAML)
kubectl create secret generic db-creds \
  --from-literal=DB_PASSWORD=moje-heslo \
  --from-literal=DB_USER=admin \
  -n moj-projekt

# Secret z súboru
kubectl create secret generic tls-cert \
  --from-file=tls.crt=./cert.pem \
  --from-file=tls.key=./key.pem

# Prečítaj Secret (dekóduj base64)
kubectl get secret db-creds -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

💾 Perzistentné dáta

Kontajnery sú dočasné — keď pod zmažeš, dáta sú preč. Pre databázy, upload súbory a iné trvalé dáta potrebuješ PersistentVolume (PV) a PersistentVolumeClaim (PVC).

Admin vytvorí PV Dev vytvorí PVC K8s spáruje PV↔PVC Pod mountuje PVC
V Minikube s dynamic provisioning (StorageClass) kroky 1 a 3 prebehnú automaticky.
yaml
# PersistentVolumeClaim — "žiadosť o úložisko"
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
  namespace: moj-projekt
spec:
  accessModes:
    - ReadWriteOnce            # RWO: jeden node môže čítať+písať
    # ReadOnlyMany (ROX): viac nodes môže čítať
    # ReadWriteMany (RWX): viac nodes môže čítať+písať (napr. NFS)
  storageClassName: standard   # Minikube default StorageClass
  resources:
    requests:
      storage: 5Gi

---
# Deployment s PVC
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: moj-projekt
spec:
  replicas: 1                  # Databázy typicky 1 replika (bez clustering riešenia)
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root-password
            - name: MYSQL_DATABASE
              value: "myapp"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-storage
              mountPath: /var/lib/mysql    # kde MySQL ukladá dáta
      volumes:
        - name: mysql-storage
          persistentVolumeClaim:
            claimName: mysql-data          # napojenie na PVC vyššie
LAB 04 MySQL s perzistentnými dátami
  1. Vytvor Secret: kubectl create secret generic mysql-secret --from-literal=root-password=Heslo123 -n moj-projekt
  2. Ulož PVC + Deployment YAML a aplikuj: kubectl apply -f mysql.yaml
  3. Počkaj na Running: kubectl get pods -n moj-projekt -w
  4. Pripoj sa k MySQL: kubectl exec -it deploy/mysql -n moj-projekt -- mysql -uroot -pHeslo123
  5. Vytvor tabuľku, vlož dáta
  6. Zmaž pod: kubectl delete pod -l app=mysql -n moj-projekt
  7. Po reštarte skontroluj — dáta musia ostať!

⚡ Resource Limits

Bez limitov môže jeden pod spotrebovať všetky zdroje a "vyhladovieť" ostatné. Kubernetes pracuje s requests (garantované minimum) a limits (maximum).

requests
Scheduler použije túto hodnotu pri výbere nodu. Node musí mať aspoň toľko voľného.
limits
Maximálna spotreba. CPU: throttling. RAM: OOMKill (kontajner sa reštartuje).

CPU jednotky

1 = 1 CPU jadro = 1000m
0.5 = 500m = pol jadra
100m = 0.1 jadra = 100 millicores
# Odporúčanie: requests=100m limits=500m pre typickú webovú app

LimitRange — defaults pre namespace

yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: moj-projekt
spec:
  limits:
    - type: Container
      default:               # limits ak kontajner nešpecifikuje
        cpu: "500m"
        memory: "256Mi"
      defaultRequest:        # requests ak kontajner nešpecifikuje
        cpu: "100m"
        memory: "64Mi"
      max:                   # nikto v namespace nemôže požiadať viac
        cpu: "2"
        memory: "2Gi"

ResourceQuota — limity pre celý namespace

yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: namespace-quota
  namespace: moj-projekt
spec:
  hard:
    requests.cpu: "4"         # celý namespace môže requestovať max 4 CPU
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "20"                # max 20 podov v namespace
    persistentvolumeclaims: "5"
bash
# Aktuálna spotreba zdrojov
kubectl top pods -n moj-projekt          # potrebuje metrics-server
kubectl top nodes

# Zapni metrics-server v Minikube
minikube addons enable metrics-server

⛵ Helm — package manager pre K8s

Helm je ako apt alebo pip pre Kubernetes. Namiesto manuálneho písania desiatich YAML súborov nainštaluješ celú aplikáciu jedným príkazom. Balíčky sa volajú charts.

💡
Prečo Helm? WordPress v K8s = ~8 YAML súborov, 300+ riadkov. Helm chart to zabalí do helm install wordpress bitnami/wordpress a cez values.yaml si nastavíš čo potrebuješ.

Inštalácia Helm

bash
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

Základné Helm príkazy

bash
# Repozitáre (zdroje chartov)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update                                  # aktualizuj zoznam

# Hľadanie
helm search repo wordpress
helm search hub nginx                             # hľadaj na Artifact Hub

# Inštalácia
helm install mojwordpress bitnami/wordpress \
  --namespace moj-projekt \
  --create-namespace \
  --set wordpressUsername=admin \
  --set wordpressPassword=SuperHeslo \
  --set mariadb.auth.rootPassword=RootHeslo

# Inštalácia s values súborom (odporúčané)
helm install mojwordpress bitnami/wordpress \
  -f values.yaml -n moj-projekt

# Zoznam nainštalovaných releasov
helm list -n moj-projekt
helm list -A                                      # všetky namespacey

# Update (nová verzia chartu alebo zmena values)
helm upgrade mojwordpress bitnami/wordpress -f values.yaml -n moj-projekt

# Rollback
helm rollback mojwordpress 1 -n moj-projekt       # číslo = revízia

# Odinštalovanie
helm uninstall mojwordpress -n moj-projekt

# Pozri čo Helm vygeneruje (bez nasadenia)
helm template mojwordpress bitnami/wordpress -f values.yaml

Vlastný Helm chart

bash
helm create moja-app
# Vytvorí štruktúru:
# moja-app/
#   Chart.yaml          - metadata chartu
#   values.yaml         - default hodnoty (prepisovateľné pri inštalácii)
#   templates/          - YAML šablóny s Go template syntaxou
#     deployment.yaml
#     service.yaml
#     ingress.yaml
#     _helpers.tpl      - pomocné funkcie
yaml
# values.yaml
replicaCount: 2
image:
  repository: nginx
  tag: "alpine"
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80
ingress:
  enabled: false
resources:
  requests:
    cpu: 100m
    memory: 64Mi
  limits:
    cpu: 500m
    memory: 128Mi
yaml
# templates/deployment.yaml (Go template)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "moja-app.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources: {{ toYaml .Values.resources | nindent 12 }}
LAB 05 Nainštaluj WordPress cez Helm
  1. helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update
  2. helm install wp bitnami/wordpress --set wordpressPassword=Heslo123 -n wordpress --create-namespace
  3. kubectl get pods -n wordpress -w (čakaj kým Running)
  4. minikube service wp-wordpress -n wordpress --url
  5. Otvor URL a prihlásy sa (admin / Heslo123)
  6. Vyčisti: helm uninstall wp -n wordpress

🔒 Ingress & Certifikáty

Ingress je HTTP/HTTPS router. Namiesto samostatného LoadBalancera pre každú aplikáciu, jeden Ingress Controller prijíma všetok HTTP traffic a podľa hostname/path ho smeruje na správnu Service.

Internet → Ingress Controller → routing podľa host/path
app.sk/api → service/api-backend :80
app.sk/ → service/frontend :80
admin.sk → service/admin :80

Ingress Controller v Minikube

bash
minikube addons enable ingress
kubectl get pods -n ingress-nginx       # počkaj kým Running

Ingress Resource

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: moj-projekt
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.local
      secretName: app-tls-secret       # Secret s certifikátom
  rules:
    - host: app.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80

Self-signed certifikát (lokálny vývoj)

bash
# Generuj self-signed cert pre app.local
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key \
  -out tls.crt \
  -subj "/CN=app.local/O=Dev" \
  -addext "subjectAltName=DNS:app.local"

# Vytvor TLS Secret v K8s
kubectl create secret tls app-tls-secret \
  --cert=tls.crt \
  --key=tls.key \
  -n moj-projekt

# Pridaj do /etc/hosts (na hostiteli)
echo "$(minikube ip) app.local" | sudo tee -a /etc/hosts

Let's Encrypt s cert-manager

Na produkčnom clustri s reálnou doménou cert-manager automaticky získava a obnovuje Let's Encrypt certifikáty.

bash
# Inštalácia cert-manager cez Helm
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true
yaml
# ClusterIssuer — Let's Encrypt produkcia
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@tvoja-domena.sk       # tvoj email
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx               # ACME HTTP-01 challenge cez Ingress

---
# Ingress s automatickým cert-manager certifikátom
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"   # magická annotation!
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - moja-app.sk
      secretName: moja-app-tls         # cert-manager sem uloží certifikát
  rules:
    - host: moja-app.sk
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80
Ako to funguje: cert-manager sleduje Ingress s annotáciou. Zaregistruje ACME challenge, Let's Encrypt overí vlastníctvo domény cez HTTP, vydá certifikát, uloží ho ako Secret. Pred vypršaním ho automaticky obnoví.

🌿 Reálny projekt: Drupal + MySQL

Celý postup nasadenia Drupal CMS s MySQL databázou. Obsahuje: PVC pre databázu, Secrets pre heslá, Services pre komunikáciu, Ingress pre HTTP prístup.

Architektúra

Internet → Ingress :80/:443
Service/drupal ClusterIP:80
Pod/drupal (php-fpm + apache)
Service/mysql ClusterIP:3306
Pod/mysql
PVC/mysql-data 10Gi
yaml
# drupal-all.yaml — nasadenie v jednom súbore (oddeľ s ---)

# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: drupal

---
# 2. Secret pre MySQL heslá
apiVersion: v1
kind: Secret
metadata:
  name: mysql-creds
  namespace: drupal
type: Opaque
stringData:                            # stringData = automatický base64
  MYSQL_ROOT_PASSWORD: "RootHeslo123"
  MYSQL_DATABASE: "drupal"
  MYSQL_USER: "drupal"
  MYSQL_PASSWORD: "DrupalHeslo456"

---
# 3. PVC pre MySQL dáta
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
  namespace: drupal
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 10Gi

---
# 4. PVC pre Drupal súbory (upload, public files)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: drupal-files
  namespace: drupal
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 5Gi

---
# 5. MySQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: drupal
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          envFrom:
            - secretRef:
                name: mysql-creds
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi
          livenessProbe:
            exec:
              command: ["mysqladmin", "ping", "-h", "localhost"]
            initialDelaySeconds: 30
            periodSeconds: 10
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: mysql-data

---
# 6. MySQL Service (ClusterIP — len interné)
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: drupal
spec:
  selector:
    app: mysql
  ports:
    - port: 3306
      targetPort: 3306

---
# 7. Drupal Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: drupal
  namespace: drupal
spec:
  replicas: 1
  selector:
    matchLabels:
      app: drupal
  template:
    metadata:
      labels:
        app: drupal
    spec:
      initContainers:
        # Počkaj kým MySQL je ready
        - name: wait-for-mysql
          image: busybox
          command: ['sh', '-c', 'until nc -z mysql 3306; do echo waiting; sleep 2; done']
      containers:
        - name: drupal
          image: drupal:10-apache
          ports:
            - containerPort: 80
          env:
            - name: DRUPAL_DATABASE_HOST
              value: "mysql"           # DNS meno MySQL service
            - name: DRUPAL_DATABASE_NAME
              value: "drupal"
            - name: DRUPAL_DATABASE_USER
              value: "drupal"
            - name: DRUPAL_DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-creds
                  key: MYSQL_PASSWORD
          volumeMounts:
            - name: drupal-files
              mountPath: /var/www/html/sites/default/files
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 1000m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 30
            periodSeconds: 10
      volumes:
        - name: drupal-files
          persistentVolumeClaim:
            claimName: drupal-files

---
# 8. Drupal Service
apiVersion: v1
kind: Service
metadata:
  name: drupal
  namespace: drupal
spec:
  type: NodePort
  selector:
    app: drupal
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
bash
# Nasadenie
kubectl apply -f drupal-all.yaml

# Sleduj stav
kubectl get pods -n drupal -w

# Zisti URL (Minikube)
minikube service drupal -n drupal --url

# Logy Drupal podu
kubectl logs -f deploy/drupal -n drupal
Inštalácia Drupal: Po otvorení URL v prehliadači prejdi inštalátorom. Hostiteľ databázy nastav na mysql (meno Service). Drupal sa pripojí cez ClusterIP Service automaticky.

🎵 Reálny projekt: Symfony + PostgreSQL

Symfony aplikácia vyžaduje špecifické kroky: database migrations pri nasadení, správne environment premenné a voliteľne queue worker (Messenger). Ukážeme aj Job pre jednorazové príkazy.

yaml
# symfony-all.yaml

# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: symfony-app

---
# 2. Secrets
apiVersion: v1
kind: Secret
metadata:
  name: symfony-secrets
  namespace: symfony-app
type: Opaque
stringData:
  DATABASE_URL: "postgresql://symfony:heslo@postgres:5432/symfony_db?serverVersion=15"
  APP_SECRET: "vygeneruj-nahodny-32-znakovy-retazec"
  MAILER_DSN: "smtp://localhost:25"

---
# 3. ConfigMap (necitlivé nastavenia)
apiVersion: v1
kind: ConfigMap
metadata:
  name: symfony-config
  namespace: symfony-app
data:
  APP_ENV: "prod"
  APP_DEBUG: "0"
  TRUSTED_PROXIES: "127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"

---
# 4. PostgreSQL PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: symfony-app
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 5Gi

---
# 5. PostgreSQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: symfony-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15-alpine
          env:
            - name: POSTGRES_DB
              value: "symfony_db"
            - name: POSTGRES_USER
              value: "symfony"
            - name: POSTGRES_PASSWORD
              value: "heslo"
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
              subPath: postgres             # subPath aby sa nested dir nevytváral
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "symfony", "-d", "symfony_db"]
            initialDelaySeconds: 10
            periodSeconds: 5
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: postgres-data

---
# 6. PostgreSQL Service
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: symfony-app
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

---
# 7. Migrations Job (spustí sa raz pri každom nasadení)
apiVersion: batch/v1
kind: Job
metadata:
  name: symfony-migrations-v1        # zmeň verziu pri každom nasadení
  namespace: symfony-app
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migrations
          image: moja-symfony-app:latest
          command: ["php", "bin/console", "doctrine:migrations:migrate", "--no-interaction"]
          envFrom:
            - configMapRef:
                name: symfony-config
            - secretRef:
                name: symfony-secrets

---
# 8. Symfony App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: symfony-app
  namespace: symfony-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: symfony-app
  template:
    metadata:
      labels:
        app: symfony-app
    spec:
      containers:
        - name: app
          image: moja-symfony-app:latest   # tvoj image z registry
          ports:
            - containerPort: 80
          envFrom:
            - configMapRef:
                name: symfony-config
            - secretRef:
                name: symfony-secrets
          resources:
            requests:
              cpu: 200m
              memory: 128Mi
            limits:
              cpu: 1000m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 80
            initialDelaySeconds: 10
            periodSeconds: 5

---
# 9. Symfony Queue Worker (Messenger)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: symfony-worker
  namespace: symfony-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: symfony-worker
  template:
    metadata:
      labels:
        app: symfony-worker
    spec:
      containers:
        - name: worker
          image: moja-symfony-app:latest
          command: ["php", "bin/console", "messenger:consume", "async", "--time-limit=3600"]
          envFrom:
            - configMapRef:
                name: symfony-config
            - secretRef:
                name: symfony-secrets
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

---
# 10. Symfony Service + Ingress
apiVersion: v1
kind: Service
metadata:
  name: symfony-app
  namespace: symfony-app
spec:
  selector:
    app: symfony-app
  ports:
    - port: 80
      targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: symfony-ingress
  namespace: symfony-app
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - moja-symfony-app.sk
      secretName: symfony-tls
  rules:
    - host: moja-symfony-app.sk
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: symfony-app
                port:
                  number: 80
💡
Dockerfile pre Symfony: Použi multi-stage build — composer install v build stage, skopíruj len výsledok do runtime image. Image bude 2–3× menší.
docker build -t moja-symfony-app:latest . && minikube image load moja-symfony-app:latest

Postup nasadenia

bash
# 1. Nasaď infraštruktúru
kubectl apply -f symfony-all.yaml

# 2. Počkaj na PostgreSQL
kubectl wait --for=condition=ready pod -l app=postgres -n symfony-app --timeout=60s

# 3. Spusti migrácie
kubectl apply -f symfony-all.yaml    # Job sa spustí automaticky

# 4. Sleduj priebeh migrácií
kubectl logs job/symfony-migrations-v1 -n symfony-app -f

# 5. Skontroluj všetko
kubectl get all -n symfony-app

# 6. Port-forward na lokálne testovanie
kubectl port-forward svc/symfony-app 8080:80 -n symfony-app
# Otvor: http://localhost:8080

🎉 Gratulujeme!

Prešiel si celým sprievodcom. Tu je quick reference na každodenné použitie:

Stav clustra
kubectl get all -A
kubectl top nodes
kubectl get events -n <ns>
Debugging
kubectl describe pod <p>
kubectl logs <pod> -f
kubectl exec -it <p> -- sh
Nasadenie
kubectl apply -f .
kubectl rollout restart deploy/<n>
kubectl rollout undo deploy/<n>
Minikube
minikube start/stop/delete
minikube dashboard
minikube image load <img>