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.