K8s

Dokumenty k osobnímu Kubernetes clusteru (3 nody na vpsfree.cz LXC).

Stránky

  • Analyze — investigační log: jaké příkazy byly volány, co vrátily a co z toho plyne pro každou oblast (cluster, síť, ingress, storage).
  • Findings — souhrn aktuálního stavu a klíčové implikace seřazené podle naléhavosti.
  • Upgrade plan — plán plynulého přechodu z k8s 1.26 na 1.31, migrace na NFS PVC a poznámky k bezpečnosti NFS multi-mount.

Analýza

projects/k8s/analyze collapse

K8s Cluster Analyze — investigační log

Záznam průzkumu osobního k8s clusteru. Pro každou oblast: použitý příkaz, klíčový výstup a okamžité pozorování.


1. Setup a verze

Příkazy

ls -la ~/.kube/
which kubectl
kubectl version --client
kubectl get nodes

Nález

  • Kubeconfig na nestandardním místě: ~/.kube/kube.yml (ne ~/.kube/config).
  • kubectl v1.35.4 (klient).
  • Cluster přístupný, vrací 3 nody Ready.

Důsledky

  • kubectl funguje (přes KUBECONFIG, alias nebo symlink).
  • Klient o ~9 minor verzí napřed před serverem — funguje díky tomu, že kubectl je obvykle backwards-compatible v širokém rozpětí, ale přesnější skew tolerance je ±1 minor; můžou se objevit drobné nekompatibility u nových API.

2. Cluster status

Příkazy

kubectl get nodes -o wide
kubectl get componentstatuses
kubectl cluster-info
kubectl top nodes

Nález

  • 3 nody (k8s-master, k8s-1, k8s-2), všechny Ready, k8s v1.26.0, Ubuntu 20.04.6, kernel 6.12.79, containerd 1.7.27.
  • Uptime nodů ~4 roky 191 dní.
  • Control plane endpoint: https://172.16.16.1:6443 (≠ InternalIP masteru 172.19.9.10) — to je WireGuard interface.
  • etcd, controller-manager: Healthy.
  • scheduler: hlášený jako Unhealthy, ale jen kvůli starému health endpoint check (port 10251 vs 10259).
  • kubectl top nodes: Metrics API not available (metrics-server nenainstalovaný).

Důsledky

  • k8s 1.26 je EOL (oficiální podpora skončila únor 2024). Žádné security patche.
  • API server schovaný za WireGuard = dobrá bezpečnostní praxe.
  • Bez metrics-server nefunguje HPA, kubectl top, sledování zátěže.
  • componentstatuses warning na schedulera je deprecated check, ne reálný problém.

3. Ingress

Příkazy

kubectl get ingressclass
kubectl get pods -A | grep -iE 'ingress|traefik|nginx|haproxy'
kubectl get ingress -A
kubectl -n ingress-nginx get svc
kubectl get ingress -A -o jsonpath='{...class-annotation...ingressClassName...}'

Nález

  • Žádný IngressClass resource neexistuje (No resources found).
  • 1× ingress-nginx-controller pod (1/1 Ready, 50 restartů za 230 dní).
  • 20 ingressů napříč 9 namespacy, všechny:
    • apiVersion: networking.k8s.io/v1 (modern)
    • kubernetes.io/ingress.class: nginx anotace (deprecated od 1.18)
    • spec.ingressClassName prázdný
  • Service ingress-nginx-controller: LoadBalancer, externí IP 37.205.11.119 (přidělená MetalLB), porty 80/443.

Důsledky

  • Funguje, protože ingress-nginx pořád respektuje deprecated anotaci jako fallback.
  • Při budoucím upgrade ingress-nginx (verze, která tu anotaci přestane podporovat) hrozí výpadek všech ingressů najednou.
  • Stačí vytvořit jeden IngressClass resource — existující ingressy budou fungovat dál.

4. Storage

Příkazy

kubectl get storageclass
kubectl get pv
kubectl get pvc -A
kubectl get pv nfs-pv -o yaml
kubectl get pods -A -o jsonpath='{...volumes...hostPath.path...nfs.server...}'

Nález

  • 1× StorageClass: example-nfs (provisioner vpsfree.cz, Reclaim Delete, AllowVolumeExpansion: false).
  • 1× PV: nfs-pv (200Mi, RWX, NFS 172.16.129.146:/nas/4721/shared).
  • 1× PVC: default/example-nfs — používaný jen testovacím podem nfs-nginx.
  • Všechny ostatní stateful workloady mají hostPath:
    • krcmar/web → /k8s/cityhry
    • nextcloud → /k8s/nextcloud
    • sellapp/uvasinu-test → /k8s/sellapp/uvasinu-test
    • openldap → /k8s/ldap/{conf,data}
    • pgadmin → /k8s/kubernetes-pgadmin
    • postgres → /k8s/kubernetes-postgresql
    • wireguard → /etc/wireguard, /var/lib/wgui

Důsledky

  • Data jsou přilepená na konkrétní node (kde je adresář). Bez nodeSelectoru hrozí, že se pod přeplánuje a data nenajde.
  • Žádné k8s-řízené zálohy. Vše musí být zálohováno na úrovni filesystému.
  • Postgres a OpenLDAP na hostPath = single point of failure pro kritická data.
  • Existující NFS infrastruktura (1 SC) je nedostatečně využívaná.

5. Síť

Příkazy

kubectl get nodes -o jsonpath='{...PodCIDR...}'
kubectl -n kube-system get pods -l app=flannel -o wide
kubectl -n kube-system get cm kube-flannel-cfg -o jsonpath='{.data.net-conf\.json}'
kubectl -n kube-system get cm kubeadm-config
kubectl -n kube-system get cm kube-proxy
kubectl -n metallb-system get cm config
kubectl get networkpolicy -A
kubectl exec ... ip route
kubectl exec ... ping ...
kubectl exec ... ip -d link show flannel.1
kubectl exec ... bridge fdb show dev flannel.1

Nález

  • CNI: Flannel, backend vxlan (VNI 1, UDP port 8472), MTU 1450.
  • Pod CIDR: 10.244.0.0/16 (per-node /24).
  • Service CIDR: 10.243.0.0/16.
  • DNS: cluster.local, kube-dns na 10.243.0.10.
  • kube-proxy: mode "" = iptables (default).
  • MetalLB: Layer2, advertizuje 37.205.11.119/32. Konfigurace přes starý ConfigMap (před v0.13 CRD-based).
  • NetworkPolicies: žádné.
  • Underlay: nody na venet0 (LXC/OpenVZ interface), segment 172.19.9.10-12.
  • Routy na masteru:
    • default přes venet0
    • 10.244.{1,2}.0/24 přes flannel.1 (VXLAN tunel)
    • 172.16.16.0/24, 192.168.0.0/24, 192.168.1.0/24 přes wg0 (WireGuard)
    • tap0/tap1 (OpenVPN)
    • docker0 (linkdown, leftover)
  • Latence:
    • master → 10.244.1.1 (pod gateway): 0.23 ms
    • master → 10.244.2.1: 0.22 ms
    • master → 172.19.9.11 (underlay): 0.15 ms
  • VXLAN FDB má korektní MAC→IP mappingy pro všechny peery.

Důsledky

  • Síť je funkční a low-latency, VXLAN overhead ~70-100 µs (zanedbatelné).
  • VXLAN je v LXC prostředí jediná rozumná volba (host-gw nelze, není kontrola nad routováním na hostu).
  • Bez NetworkPolicies = jakýkoli pod si může povídat s jakýmkoli jiným. Pro osobní cluster ok, ale citlivé namespacy (postgres, openldap) by mohly mít aspoň základní izolaci.
  • MetalLB v ConfigMap konfiguraci = velmi stará verze (před v0.13). Funguje, ale neaktualizovaná. Při upgrade k8s je třeba migrace na CRD-based config.
  • MetalLB Layer2 = single point of forwarding pro public IP (vždy přes node, který IP zrovna ARP-uje). Failover ~10s. Pro tento scale ok.

Findings

projects/k8s/findings collapse

K8s Cluster — Findings

Syntéza zjištění z analýzy clusteru. Stav k 2026-05-07.


Aktuální stav v kostce

OblastVerze / configStav
Kubernetesv1.26.0❌ EOL od února 2024
Hostitelské OSUbuntu 20.04.6, kernel 6.12.79✅ aktuální kernel
Container runtimecontainerd 1.7.27✅ ok
Nody3 (1 master + 2 workers) Ready
CNIFlannel VXLAN✅ funguje, low-latency
kube-proxyiptables mode✅ ok pro tento scale
Load balancerMetalLB Layer2 (ConfigMap config)⚠️ pre-CRD verze (před v0.13)
Ingressingress-nginx, 20 ingressů, deprecated anotace⚠️ chybí IngressClass resource
Storage1 nepoužitá NFS SC, vše ostatní hostPath⚠️ data přilepená na nody
NetworkPoliciesžádné⚠️ flat L3, žádná izolace
metrics-servernenainstalovaný⚠️ kubectl top nefunguje
etcdhealthy
API serverza WireGuard (172.16.16.1:6443)✅ dobrá praxe
cert-manager, dashboardběží✅ funkční (verze stará)

Co funguje dobře

  • Inter-node konektivita je stabilní a rychlá (sub-ms latency v overlay).
  • VXLAN backend je správná volba pro LXC prostředí — alternativy (host-gw, native routing) nejsou v LXC dostupné.
  • WireGuard pro API server access schovává control plane mimo veřejnou síť.
  • MetalLB Layer2 s jednou public IP funguje a je jednoduchý.
  • etcd, controller-manager, scheduler běží zdravě (despite deprecated componentstatuses warning).

Co tikne (priorita podle naléhavosti)

🔴 Vysoká: k8s 1.26 EOL

  • Žádné security patche od února 2024.
  • Cca 1.5 roku za aktuální stable větev.
  • Akce: postupný upgrade 1.26 → 1.31 (5 hopů, plán viz upgrade-plan).

🔴 Vysoká: Postgres na hostPath bez zálohy a replikace

  • Data v /k8s/kubernetes-postgresql na konkrétním nodu.
  • Při ztrátě nodu = ztráta DB.
  • Akce: minimálně automatizovaný pg_dump cron + off-site copy. Dlouhodobě úvaha o CloudNativePG s replikami.

🟡 Střední: chybí IngressClass resource

  • 20 ingressů spoléhá na deprecated anotaci kubernetes.io/ingress.class: nginx.
  • ingress-nginx ji aktuálně pořád respektuje, ale není to garantováno do budoucna.
  • Akce: jeden YAML — vytvořit IngressClass nginx jako default. Existující ingressy fungují dál.

🟡 Střední: MetalLB v pre-CRD verzi

  • Konfigurace přes ConfigMap (před v0.13).
  • Verze nedostává security updates.
  • Migrace na CRD-based config znamená krátký výpadek public IP (~30s).
  • Akce: udělat samostatně, mimo k8s upgrade hopy.

🟡 Střední: hostPath všude

  • Data většiny stateful workloadů jsou přilepená na nody (/k8s/*).
  • Bez NFS PVC (které je nasazené, ale nepoužívané kromě testu).
  • Akce: postupná migrace nekritických workloadů (krcmar/media, blog, sellapp, pgadmin, wireguard) na NFS PVC. Postgres a OpenLDAP záměrně zůstávají na hostPath.

🟢 Nízká: chybí NetworkPolicies

  • Flat L3 — jakýkoli pod si může povídat s jakýmkoli.
  • Pro osobní cluster ok, ale citlivé namespacy (postgres, openldap) by mohly mít izolaci.
  • Akce: postupně přidávat per-namespace policy podle potřeby.

🟢 Nízká: chybí metrics-server

  • kubectl top nefunguje, žádné HPA.
  • Pro homelab obvykle nekritické.
  • Akce: jeden kubectl apply na deploy manifest.

🟢 Nízká: cleanup

  • docker0 interface na masteru (Docker daemon nainstalovaný, nepoužívaný).
  • Test workload nfs-nginx v default namespace (pravděpodobně už nepotřebný).
  • 50 restartů ingress-nginx-controller za 230 dní (sledovat, jestli se trend nezhoršuje).

Souvislost s plánovanými změnami

  • Nextcloud bude zrušen a nahrazen Rust aplikací na Turris routeru. K8s pro tu službu bude jen public proxy přes Service+Endpoints+Ingress. Nextcloud workload tedy v migračních plánech vynechat.
  • Postgres zůstává na fixed nodu s hostPath i během upgradu. Migrace na CloudNativePG (s replikami) je samostatný projekt na později.
  • Migrace nekritických workloadů na NFS PVC se hodí udělat před k8s upgrade — méně proměnných najednou.

Co je vůbec mimo dosah v tomto prostředí

vpsfree.cz LXC kontejnery brání použití některých „enterprise" řešení:

  • Ceph / Rook — kernel moduly, block devices, privileged ops.
  • Longhorn — iSCSI (kernel moduly).
  • OpenEBS Mayastor / cStor — hugepages, NVMe features.
  • DRBD / LINSTOR — kernel modul.

Jediná reálně dostupná HA storage v LXC = NFS (přes vpsfree nebo vlastní NFS server) + aplikační replikace pro databáze. Pro plnou HA distribuovanou storage by byl potřeba přechod z LXC na KVM.


Upgrade plan

projects/k8s/upgrade-plan collapse

K8s Upgrade & Storage Migration Plan

Cluster: 3 nody (k8s-master, k8s-1, k8s-2) na vpsfree.cz LXC, k8s v1.26.0, Flannel VXLAN, MetalLB L2, ingress-nginx.


Část 1: NFS multi-mount safety

Co NFS poskytuje

  • Více klientů může současně mountovat stejný export v RWX (ReadWriteMany).
  • POSIX-like sémantika přes síť (read, write, rename, mkdir).
  • NLM / NFSv4 file locking — ale jen pokud ho aplikace explicitně používá (fcntl, flock).
  • Atributový cache (mtime, size) je eventually consistent s konfigurovatelným TTL.

Bezpečnost podle scénáře

ScénářBezpečné?
Dva pody zapisují různé soubory✅ Ano
Read-only shared assety, jeden writer✅ Ano
Aplikace s embedded DB (SQLite, BerkeleyDB)❌ Spolehlivě rozbije DB
Postgres / MySQL data dir❌ Nepodporováno
Dvě repliky stejné aplikace zapisující do stejných souborů bez flock❌ Race conditions, korupce
Append-only logy z více writerů do stejného souboru⚠️ Krátké zápisy < PIPE_BUF většinou ok, dlouhé se interleavují
Aplikace navržené pro shared FS (Nextcloud, file storage apps)✅ Ano
Cron joby z různých nodů manipulující stejné soubory⚠️ Pouze s explicit flock

Praktická pravidla

  1. Postgres nikdy na NFS (zůstává hostPath).
  2. SQLite nikdy na NFS — locking přes NFS je nespolehlivý.
  3. „Multiple writers" je bezpečné, jen pokud:
    • Každý writer píše do vlastního adresáře/souboru, nebo
    • Aplikace vědomě používá NFS-aware locking, nebo
    • Read-mostly scénář s explicit lockingem.
  4. Pro současné workloady (krcmar/media, blog, sellapp) — typicky 1 replika nebo read-only, multi-writer není reálně využíván → bezpečné pásmo.
  5. Při budoucím škálování replicas: > 1 pro app, která zapisuje do shared volume, vždy ověřit concurrent-write chování.

Část 2: NFS PVC plán

Současný stav

  • 1× StorageClass example-nfs (provisioner vpsfree.cz).
  • 1× PV/PVC, používaný jen testovacím podem.
  • NFS export: 172.16.129.146:/nas/4721/shared.

Doporučený provisioner

Nasadit nfs-subdir-external-provisioner (upstream, well-maintained):

helm install nfs-subdir nfs-subdir-external-provisioner \
  --namespace storage --create-namespace \
  --set nfs.server=172.16.129.146 \
  --set nfs.path=/nas/4721/shared \
  --set storageClass.name=nfs \
  --set storageClass.defaultClass=false \
  --set storageClass.reclaimPolicy=Retain \
  --set storageClass.allowVolumeExpansion=true \
  --set nfs.mountOptions="{soft,timeo=100,retrans=3,noatime}"

Důvody pro soft,timeo=100: jinak při výpadku NFS pody zatuhnou v D stavu (uninterruptible sleep). hard mount je v k8s past.

Postup migrace

FázeWorkloadVelikost změnyRiziko
1services/blog (media)malánízké, čistý start
2krcmar/cityhry (/k8s/cityhry)střední, kopírovánínízké (read-mostly)
3sellapp/uvasinu-testmalánízké
4services/wireguard configmalástřední (config-critical)
5services/pgadmin datamalánízké
6services/openldapdle latence NFS— (LDAP má rád low-latency I/O)
services/postgreszůstává hostPath, fixed node

Per-workload migrace recept

1. kubectl scale deployment X --replicas=0
2. rsync -av /k8s/X/ /mnt/new-pvc/X/   (na nodu, kde data jsou)
3. Upravit deployment: hostPath → PVC
4. kubectl apply
5. kubectl scale deployment X --replicas=1
6. Ověřit, že běží + má data
7. Po týdnu: smazat /k8s/X na původním nodu

Část 3: K8s upgrade plán (1.26 → 1.31)

Klíčový fakt: skew policy

K8s nepovoluje skok přes více než 1 minor verzi v jednom kroku.

1.26 → 1.27 → 1.28 → 1.29 → 1.30 → 1.31 (5 hopů)

Každý hop = ~30-60 min práce + buffer na ověření.

Phase 0: Před prvním upgradem

  1. etcd backup automation — cron na masteru:

    ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
      --cacert=/etc/kubernetes/pki/etcd/ca.crt \
      --cert=/etc/kubernetes/pki/etcd/server.crt \
      --key=/etc/kubernetes/pki/etcd/server.key \
      snapshot save /backup/etcd-$(date +%F).db
    

    Daily, retention 14 dní, off-site copy přes rsync.

  2. Add IngressClass resource — preventivně:

    apiVersion: networking.k8s.io/v1
    kind: IngressClass
    metadata:
      name: nginx
      annotations:
        ingressclass.kubernetes.io/is-default-class: "true"
    spec:
      controller: k8s.io/ingress-nginx
    
  3. Deploy NFS provisioner (Část 2).

  4. Migrace 2-3 nekritických workloadů na PVC — méně proměnných najednou v upgrade fázi.

  5. Dokumentace current state:

    • kubectl version
    • kubectl -n ingress-nginx get deploy ingress-nginx-controller -o yaml | grep image:
    • Stejně pro flannel, metallb, cert-manager, dashboard.
    • Snapshot kubectl get all -A -o yaml > backup.yaml.

Per-hop checklist (opakovat pro každý hop)

1. Přečti CHANGELOG / breaking changes:
   https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.XX.md
2. etcd snapshot
3. Na masteru:
   apt update
   apt-mark unhold kubeadm
   apt install kubeadm=1.XX.x-*
   apt-mark hold kubeadm
   kubeadm upgrade plan
   kubeadm upgrade apply v1.XX.x
4. Drain master, upgrade kubelet+kubectl, uncordon:
   kubectl drain k8s-master --ignore-daemonsets --delete-emptydir-data
   apt install kubelet=1.XX.x-* kubectl=1.XX.x-*
   systemctl daemon-reload && systemctl restart kubelet
   kubectl uncordon k8s-master
5. Pro každý worker (k8s-1, pak k8s-2):
   kubectl drain k8s-1 --ignore-daemonsets --delete-emptydir-data
   # na nodu:
   kubeadm upgrade node
   apt install kubelet=1.XX.x-* kubectl=1.XX.x-*
   systemctl daemon-reload && systemctl restart kubelet
   kubectl uncordon k8s-1
6. Ověření:
   kubectl get nodes               # všechny Ready, správná verze
   kubectl get pods -A             # žádný CrashLoopBackOff
   smoke test: curl jeden ingress endpoint
7. Počkat min. 24-48h před dalším hopem, sledovat

Breaking changes per hop

  • 1.26 → 1.27: PSP definitivně pryč (nepoužíváme). Některé --feature-gate flagy odstraněny.
  • 1.27 → 1.28: seccompDefault GA. Některé legacy auth mechanizmy deprecated.
  • 1.28 → 1.29: In-tree cloud providers odstraněny (nepoužíváme). Legacy ServiceAccount token secrets už se negenerují by default.
  • 1.29 → 1.30: ValidatingAdmissionPolicy GA. Některé kubelet flagy odstraněny.
  • 1.30 → 1.31: AppArmor GA, drobné změny PV/CSI.

Pro tento cluster (žádné cloud provider integrace, žádné PSP, standardní flannel/metallb) by žádný hop neměl být dramatický.

Komponenty mimo k8s

KomponentaKdy upgradovatJak
FlannelPo 1.27 nebo 1.28kubectl apply -f nový manifest. Drop-in upgrade, VXLAN config zůstane.
MetalLBPo 1.28 (samostatně)Větší práce: ConfigMap → CRD migrace. Install nová verze, vyrobit IPAddressPool + L2Advertisement CRD, smazat starý ConfigMap. Krátký výpadek (~30s) public IP.
ingress-nginxSouběžně s 1.28 nebo 1.29helm upgrade nebo apply nového manifestu. Verze kompatibilní s aktuální k8s.
cert-managerSouběžně s ingress-nginxHelm upgrade, CRDs se updatují automaticky.
kubernetes-dashboardKdykoli (low-priority)v7+ je Helm-only s odlišnou architekturou. Smazat current, nainstalovat čerstvě.
CoreDNSAutomaticky s kubeadm upgradeNení potřeba ručně.

Doporučená timeline

TýdenCo
1etcd backup automation, IngressClass resource, NFS provisioner deployment
2Migrace 2-3 nekritických workloadů na NFS PVC, validate
3Upgrade 1.26 → 1.27, ověřit
4Upgrade 1.27 → 1.28, ověřit
5Upgrade Flannel + ingress-nginx + cert-manager (spolu)
6Upgrade 1.28 → 1.29
7MetalLB ConfigMap → CRD migrace (samostatně)
8Upgrade 1.29 → 1.30
9Upgrade 1.30 → 1.31
10Migrace zbylých nekritických workloadů na NFS PVC, redeploy dashboard, cleanup

Cca 10 týdnů na klidné plynulé tempo. Lze zkrátit minimálně na 5 víkendů (jeden hop za víkend).

Postgres během upgradu

Postgres na hostPath na fixed nodu je v pohodě i během upgradu, jen:

  • Před každým drainem nodu, kde Postgres běží, manuálně pg_dump a uložit mimo cluster.
  • nodeSelector na pod, aby se nepřemístil.
  • Při upgradu konkrétního nodu: scale Postgres na 0, drain, upgrade, scale zpět. Krátký výpadek (~5-10 min). Aplikace musí na to být připravené.

Zero-downtime Postgres = migrace na CloudNativePG s replikami. Samostatný projekt, nedělat během k8s upgradu.

Souvisí s Nextcloud replacement

Nextcloud je v plánu na zrušení (replacement = Rust app na Turris, k8s jen jako public proxy). Pro Nextcloud workload tedy:

  • Nemigrovat na NFS PVC (zbytečná práce).
  • V plánu počítat s tím, že někde v týdnu 5-7 se Nextcloud namespace smaže a nahradí Service+Endpoints+Ingress směrem na Turris.
  • /k8s/nextcloud adresář na nodu po vyřazení smazat.