7 - Cilium¶
K3s Homelab — Sesja 07¶
Data: 2026-03-06
Środowisko: 3x HP T630, k3s v1.34.4, Flux v2.8.1
Co zbudowaliśmy¶
- Migracja CNI: Flannel → Cilium v1.19.1 (eBPF)
- Cilium zarządzany przez Flux jako HelmRelease
- Klaster w pełni operacyjny po migracji
Czego się nauczyłem¶
1. Problem z flannel po twardym wyłączeniu¶
Objaw: Wszystkie pody w stanie ContainerCreating / Unknown po restarcie klastra przez przycisk zasilania.
Przyczyna: Flannel trzyma subnet.env w /run/flannel/ który jest tmpfs (RAM). Po twardym wyłączeniu ten plik znika. Flannel nie może zainicjalizować sieci bez tego pliku.
Tymczasowe rozwiązanie:
ssh master "sudo mkdir -p /run/flannel && printf 'FLANNEL_NETWORK=10.42.0.0/16\nFLANNEL_SUBNET=10.42.0.1/24\nFLANNEL_MTU=1450\nFLANNEL_IPMASQ=true\n' | sudo tee /run/flannel/subnet.env"
ssh worker1 "sudo mkdir -p /run/flannel && printf 'FLANNEL_NETWORK=10.42.0.0/16\nFLANNEL_SUBNET=10.42.1.1/24\nFLANNEL_MTU=1450\nFLANNEL_IPMASQ=true\n' | sudo tee /run/flannel/subnet.env"
ssh worker2 "sudo mkdir -p /run/flannel && printf 'FLANNEL_NETWORK=10.42.0.0/16\nFLANNEL_SUBNET=10.42.2.1/24\nFLANNEL_MTU=1450\nFLANNEL_IPMASQ=true\n' | sudo tee /run/flannel/subnet.env"
Prawdziwe rozwiązanie: Migracja na Cilium — nie ma tego problemu.
Wniosek: Zawsze używaj Ansible graceful shutdown playbooka zamiast przycisku zasilania!
2. Migracja CNI: Flannel → Cilium¶
Dlaczego Cilium jest lepszy:
- eBPF zamiast iptables — wyższa wydajność, niższy overhead
- Zastępuje zarówno flannel jak i kube-proxy
- NetworkPolicy out of the box
- Hubble — wbudowana observability sieci
- Standard produkcyjny w enterprise
Plan migracji (krok po kroku):
1. Backup etcd przed migracją
2. Wyłącz k3s na wszystkich nodach
3. Wyczyść pozostałości flannel (CNI dirs, network interfaces)
4. Dodaj flagi do k3s.service: --flannel-backend=none --disable-network-policy
5. Uruchom master (poczekaj na etcd quorum)
6. Uruchom workery
7. Zainstaluj Cilium przez Helm z bezpośrednim IP
8. Przenieś do Flux jako HelmRelease
Krok 1 — Backup etcd:
Krok 2 — Stop k3s:
Krok 3 — Czyszczenie flannel:
ansible all -m shell -a "
rm -rf /run/flannel
rm -rf /var/lib/cni
rm -rf /etc/cni/net.d/*
ip link delete flannel.1 2>/dev/null || true
ip link delete cni0 2>/dev/null || true
" -b
Krok 4 — Flagi k3s.service (edytuj przez sudo vi na każdym nodzie):
ExecStart=/usr/local/bin/k3s \
server \
'--cluster-init' \
'--flannel-backend=none' \
'--disable-network-policy' \
'--tls-san' \
'cluster.kcn333.com' \
Na workerach dodaj te same flagi do ich ExecStart.
Krok 5+6 — Start klastra (HA wymaga quorum!):
# Najpierw master
ssh master "sudo systemctl daemon-reload && sudo systemctl start k3s"
# Gdy etcd czeka na quorum — uruchom workery równocześnie
ssh worker1 "sudo systemctl daemon-reload && sudo systemctl start k3s" &
ssh worker2 "sudo systemctl daemon-reload && sudo systemctl start k3s" &
⚠️ Ważne: W HA klastrze etcd potrzebuje quorum (⅔ nodów). Nie czekaj z workerami zbyt długo!
Krok 7 — Instalacja Cilium przez Helm:
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium \
--version 1.19.1 \
--namespace kube-system \
--set k8sServiceHost=192.168.55.10 \ # bezpośrednie IP, nie hostname!
--set k8sServicePort=6443 \
--set operator.replicas=1
Dlaczego IP zamiast hostname? Cilium podczas bootstrapu nie może użyć DNS bo:
- DNS działa przez CoreDNS
- CoreDNS to pod w klastrze
- Pod nie może wstać bez CNI
- CNI (Cilium) nie może się połączyć bez DNS → Deadlock! Bezpośredni IP omija ten problem.
Krok 8 — Flux HelmRelease:
# apps/base/cilium/helmrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: cilium
namespace: flux-system
spec:
interval: 1h
url: https://helm.cilium.io
# apps/base/cilium/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cilium
namespace: kube-system
annotations:
meta.helm.sh/release-name: cilium
meta.helm.sh/release-namespace: kube-system
spec:
interval: 30m
chart:
spec:
chart: cilium
version: "1.19.1"
sourceRef:
kind: HelmRepository
name: cilium
namespace: flux-system
interval: 12h
install:
createNamespace: false
upgrade:
remediation:
retries: 3
values:
k8sServiceHost: 192.168.55.10
k8sServicePort: 6443
operator:
replicas: 1
3. Problemy które napotkaliśmy¶
Problem 1: Cilium crashuje i zabiera sieć
Cilium DaemonSet crashował w pętli (nie mógł połączyć się z API) i przy tym uszkadzał interfejsy sieciowe. Master i worker1 przestały odpowiadać nawet na ping.
Rozwiązanie — rescue przez worker2:
# Na worker2 uruchom pętlę która złapie okno gdy API wstanie:
while true; do
kubectl delete daemonset cilium cilium-envoy -n kube-system --force --grace-period=0 2>/dev/null && echo "DONE!" && break
sleep 2
done
# Następnie fizycznie zrestartuj zawieszony node
Lekcja: W klastrze HA zawsze masz worker2 jako koło ratunkowe. Nigdy nie rób niebezpiecznych operacji na wszystkich nodach jednocześnie.
Problem 2: Namespace cilium-secrets w stanie Terminating
Namespace nie chciał się usunąć przez finalizers.
Rozwiązanie:
kubectl get namespace cilium-secrets -o json | \
python3 -c "import sys,json; d=json.load(sys.stdin); d['spec']['finalizers']=[]; print(json.dumps(d))" | \
kubectl replace --raw /api/v1/namespaces/cilium-secrets/finalize -f -
Problem 3: HA etcd wymaga quorum
Przy starcie tylko mastera — etcd czekał na pozostałe nody i k3s nie przechodził w stan active.
Rozwiązanie: Uruchamiaj workery równolegle z masterem gdy widzisz że etcd loguje prober detected unhealthy status.
4. Weryfikacja Cilium¶
# Status
cilium status
# Test łączności (bez NetworkPolicy)
cilium connectivity test --test no-policies
# Sprawdź ile podów zarządza Cilium
cilium status | grep "Cluster Pods"
Oczekiwany output:
5. Ratowanie klastra — procedury awaryjne¶
Wymuś usunięcie podów:
kubectl delete pods -n <namespace> --field-selector status.phase=Unknown
kubectl delete pods -n <namespace> --field-selector status.phase=Failed
kubectl delete pods -n <namespace> --all --force --grace-period=0
Wymuś usunięcie namespace:
kubectl get namespace <nazwa> -o json | \
python3 -c "import sys,json; d=json.load(sys.stdin); d['spec']['finalizers']=[]; print(json.dumps(d))" | \
kubectl replace --raw /api/v1/namespaces/<nazwa>/finalize -f -
Sprawdź logi k3s:
etcd snapshot przed ryzykowną operacją:
ssh master "sudo k3s etcd-snapshot save --name <nazwa-snapshotu>"
ssh master "sudo k3s etcd-snapshot ls"
6. Dlaczego graceful shutdown jest kluczowy¶
Twardy restart (przycisk zasilania) może spowodować:
- Utratę
tmpfsdanych (np. flannel subnet.env) - Pody w stanie
Unknownwymagające ręcznego czyszczenia - Problemy z etcd jeśli lider był w trakcie operacji
Zawsze używaj:
Lub ręcznie:
Finalna struktura repo¶
k3s-homelab/
├── README.md
├── apps/
│ ├── base/
│ │ ├── kustomization.yaml
│ │ ├── cilium/ # ← NOWE
│ │ │ ├── helmrelease.yaml
│ │ │ ├── helmrepository.yaml
│ │ │ └── kustomization.yaml
│ │ ├── longhorn/
│ │ ├── nginx/
│ │ ├── sealed-secrets/
│ │ └── traefik-dashboard/
│ └── kustomization.yaml
└── clusters/
└── k3s-homelab/
├── apps.yaml
├── helmchartconfig-traefik.yaml
├── image-update-automation.yaml
└── flux-system/
Backlog (do zrobienia)¶
- Prometheus + Grafana monitoring ← następna sesja
- Hubble UI — Cilium network observability
- NetworkPolicy — izolacja między podami
- Własna aplikacja (Spring Boot) + CI/CD pipeline
- HashiCorp Vault
- External-dns
- Progressive delivery (staging/production)
- RBAC
Przydatne komendy¶
# Cilium
cilium status
cilium status --wait
cilium connectivity test --test no-policies
# Flux HelmReleases
flux get helmreleases -A
flux reconcile helmrelease cilium -n kube-system
# etcd snapshots
ssh master "sudo k3s etcd-snapshot save --name <nazwa>"
ssh master "sudo k3s etcd-snapshot ls"
# Ratowanie namespace
kubectl get namespace <nazwa> -o json | \
python3 -c "import sys,json; d=json.load(sys.stdin); d['spec']['finalizers']=[]; print(json.dumps(d))" | \
kubectl replace --raw /api/v1/namespaces/<nazwa>/finalize -f -
# Czyszczenie starych podów
kubectl delete pods -n <ns> --field-selector status.phase=Unknown --force
kubectl delete pods -n <ns> --field-selector status.phase=Failed --force