Provisionierung: Kubernetes auf Bare Metal
Dieses Dokument beschreibt die vollständige Provisionierung von Kubernetes auf Bare-Metal-Umgebungen. Es führt dich von der Hardwarevorbereitung über Betriebssystem, Container Runtime, Cluster-Bootstrap, Netzwerk, Load Balancing, Storage, Add-ons, Validierung, Troubleshooting, Upgrade und Rückbau.
Die Datei ist bewusst praxisnah formuliert. Du kannst sie im Workshop als Leitfaden, als Checkliste oder als Basis für eigene Automatisierung mit Bash, Ansible, PXE, cloud-init, Talos, k3s, RKE2 oder k0s verwenden.
Wichtig: Bare Metal ist kein einzelnes Setup. Du triffst Architekturentscheidungen über Hardware, Firmware, Netzwerk, Storage, Load Balancer, Betriebssystem, Kubernetes-Distribution, CNI, CSI, Ingress, Zertifikate, Backup und Betriebsmodell. Dieses Dokument macht diese Entscheidungen explizit.
1. Zweck
Diese Datei dokumentiert, wie du Kubernetes reproduzierbar auf physischer Hardware bereitstellst. Sie ersetzt keine projektspezifische Betriebsanweisung, liefert aber eine belastbare Referenz für Planung, Provisionierung und Validierung.
Du nutzt dieses Dokument, wenn du:
- einen Workshop-Cluster auf Bare Metal aufbauen willst,
- mehrere physische Server in einem Labor oder Rechenzentrum vorbereiten willst,
- einen produktionsnahen
kubeadm-Cluster mit Control-Plane-Hochverfügbarkeit erstellen willst, - eine Edge-Umgebung mit k3s, RKE2, k0s oder Talos bewerten willst,
- LoadBalancer-Services ohne Cloud Provider bereitstellen willst,
- Storage und Netzwerk bewusst für Bare Metal auswählen willst,
- Air-Gap- oder eingeschränkt verbundene Umgebungen vorbereiten willst,
- ein Troubleshooting- und Rückbauverfahren brauchst.
1.1. Zielbild
Am Ende hast du nicht nur einen laufenden Cluster, sondern eine nachvollziehbare Plattformgrundlage:
flowchart TD
Hardware[Physische Server] --> Firmware[Firmware, BIOS, RAID, BMC]
Firmware --> OS[Betriebssystem oder Kubernetes-OS]
OS --> Runtime[Container Runtime]
Runtime --> Bootstrap[Cluster-Bootstrap]
Bootstrap --> Network[CNI, Service-Netz, DNS]
Network --> LB[Bare-Metal-LoadBalancer]
LB --> Storage[Storage und CSI]
Storage --> Addons[Ingress, Gateway, Certs, Observability]
Addons --> Validate[Validierung]
Validate --> Operate[Betrieb, Upgrade, Backup]
1.2. Abgrenzung
Dieses Dokument beschreibt die Provisionierung der Kubernetes-Plattform. Es beschreibt nicht im Detail:
- Applikationsarchitektur einzelner Workloads,
- tiefes Security-Monitoring auf SOC-Niveau,
- vollständige Netzwerkrouter-Konfiguration für jedes Herstellergerät,
- vollständige Storage-Array-Konfiguration für jeden Hersteller,
- produktive Compliance-Dokumentation.
Diese Themen werden hier nur so weit behandelt, wie sie für Bare-Metal-Provisionierung notwendig sind.
2. Grundentscheidungen
Bare-Metal-Kubernetes ist stärker von Umgebung, Netzwerk und Storage abhängig als ein Managed-Kubernetes-Angebot. Vor der Installation legst du deshalb die Zielarchitektur fest.
2.1. Entscheidungsmatrix
| Szenario | Geeigneter Ansatz | Typische Größe | Vorteil | Nachteil |
|---|---|---|---|---|
| Lokaler Workshop | Single-Node kubeadm, k3s oder k0s |
1 Node | Schnell, transparent, günstig | Keine echte HA |
| Mehrknoten-Lab | kubeadm mit 1 Control Plane und mehreren Workern |
2 bis 5 Nodes | Kubernetes-nah, gut erklärbar | Control Plane ist Single Point of Failure |
| Produktionsnahes Lab | kubeadm HA mit gestapeltem etcd |
3 Control-Plane-Nodes + Worker | Standardnah, gut lernbar | Load Balancer nötig |
| Rechenzentrum | kubeadm HA mit externem etcd oder RKE2 |
3+3+N Nodes | klare Rollen, robust | mehr Betriebsaufwand |
| Edge / kleine Hardware | k3s | 1 bis 5 Nodes | geringer Ressourcenbedarf | Distribution abstrahiert Details |
| Security-fokussiert | RKE2 oder Talos | 3+ Nodes | gehärtete Betriebsmodelle | andere Bedienkonzepte |
| Immutable OS | Talos Linux | 1 bis N Nodes | deklarative Maschinenkonfiguration | kein SSH, andere Fehleranalyse |
| Vollautomatisierung | PXE, MAAS, Matchbox, Metal3, Ansible | N Nodes | reproduzierbar, skalierbar | initial höhere Komplexität |
| Air-Gap | kubeadm, RKE2, k3s oder Talos mit lokaler Registry |
abhängig | kontrolliert, offlinefähig | Artefaktmanagement kritisch |
2.2. Leitregel für Workshops
Für einen Workshop ist kubeadm didaktisch sehr wertvoll, weil du die Kubernetes-Bausteine sichtbar installierst: Betriebssystem, Runtime, kubelet, kubeadm, kubectl, CNI, Control Plane, Worker Join und Add-ons.
Für Edge- und Appliance-nahe Umgebungen sind k3s, RKE2, k0s oder Talos oft passender, weil sie Cluster-Lifecycle und Host-Betrieb stärker bündeln.
2.3. Entscheidungsdiagramm
flowchart TD
Start[Du brauchst Kubernetes auf Bare Metal] --> NeedHA{Brauchst du Control-Plane-HA?}
NeedHA -- Nein --> Small{Kleine Edge- oder Lab-Hardware?}
Small -- Ja --> K3s[k3s oder k0s]
Small -- Nein --> SingleKubeadm[Single-Node oder Multi-Node kubeadm]
NeedHA -- Ja --> Ops{Willst du Kubernetes-Komponenten nah am Upstream lernen?}
Ops -- Ja --> HAkubeadm[kubeadm HA]
Ops -- Nein --> Dist{Brauchst du gehärtete Distribution?}
Dist -- Ja --> RKE2Talos[RKE2 oder Talos]
Dist -- Nein --> K0s[k0s oder Kubespray]
HAkubeadm --> LB{Hast du externen Load Balancer?}
LB -- Ja --> ExternalLB[HAProxy, F5, A10, NetScaler, NGINX, Router]
LB -- Nein --> KubeVIP[kube-vip oder HAProxy plus Keepalived]
3. Zielumgebungen
Dieses Dokument unterscheidet mehrere Bare-Metal-Umgebungen. Du kannst einzelne Kapitel kombinieren, aber du solltest nicht mehrere Cluster-Bootstrap-Methoden unkontrolliert mischen.
3.1. Native Linux-Server mit kubeadm
Das ist der transparenteste Pfad:
- Ubuntu, Debian, RHEL, Rocky Linux, AlmaLinux, Fedora Server, SLES oder ähnliche Distribution,
containerdoder CRI-O als Runtime,kubeadmfür Bootstrap,- CNI nach Wahl,
- MetalLB oder kube-vip für
LoadBalancer-Services, - CSI-Storage nach Infrastruktur.
Dieser Pfad eignet sich besonders für Lernumgebungen, Plattformteams und produktionsnahe Workshops.
3.2. General-Purpose-Linux mit Distribution
Hier nutzt du eine Kubernetes-Distribution, die Installation und Komponenten stärker bündelt:
- k3s: leichtgewichtig, gut für Edge, kleine Labs, ARM und kompakte Cluster.
- RKE2: stärker auf Security und Enterprise-Betrieb ausgerichtet.
- k0s: schlanke CNCF-konforme Distribution mit einfachem Controller-/Worker-Modell.
- Kubespray: Ansible-basierter Aufbau von Kubernetes auf vorhandenen Maschinen.
3.3. Kubernetes-optimiertes Betriebssystem
Hier ist das Betriebssystem selbst auf Kubernetes-Betrieb zugeschnitten:
- Talos Linux: API-gesteuert, kein SSH, keine klassische Paketverwaltung, deklarative Maschinenkonfiguration.
- Flatcar Container Linux: unveränderliches Hostmodell, oft mit Ignition/Butane und automatisierten Updates.
Diese Umgebungen sind im Betrieb sehr stark, aber sie verlangen ein anderes Debugging-Modell.
3.4. Vollautomatisierte Bare-Metal-Provisionierung
Wenn du viele Server bereitstellst, willst du nicht manuell von ISO installieren. Typische Bausteine sind:
- BMC/IPMI/Redfish für Power- und Boot-Steuerung,
- DHCP, TFTP, HTTP und iPXE,
- Ubuntu Autoinstall, Debian Preseed, Kickstart oder Ignition,
- MAAS, Foreman, Digital Rebar, Matchbox, Cobbler oder Metal3,
- Ansible oder Terraform als Orchestrierung,
- GitOps für Cluster-Add-ons.
4. Referenzarchitekturen
4.1. Single-Node-Lab
Ein einzelner Node trägt Control Plane und Workloads.
flowchart TD
Dev[Dein Client] --> API[kube-apiserver auf Node 1]
API --> ETCD[etcd auf Node 1]
API --> Kubelet[kubelet auf Node 1]
Kubelet --> Runtime[containerd oder CRI-O]
Runtime --> Pods[Workshop-Pods]
Pods --> CNI[CNI]
Pods --> Storage[local-path, hostPath oder lokales PV]
Geeignet für:
- Kubernetes-Grundlagen,
- YAML,
kubectl, Deployments, Services, - einfache CNI- und Storage-Demos,
- lokale Hardwaretests.
Nicht geeignet für:
- echte Hochverfügbarkeit,
- Produktionsbetrieb,
- belastbare Upgrade-Tests mit Node-Drain,
- Mandantentrennung.
4.2. Multi-Node-Lab mit einer Control Plane
Ein Control-Plane-Node und mehrere Worker-Nodes.
flowchart LR
Client[kubectl] --> CP[Control Plane Node]
CP --> W1[Worker Node 1]
CP --> W2[Worker Node 2]
CP --> W3[Worker Node 3]
W1 --> Pods1[Pods]
W2 --> Pods2[Pods]
W3 --> Pods3[Pods]
Geeignet für:
- Scheduling,
- DaemonSets,
- CNI über mehrere Nodes,
- Services und EndpointSlices,
- Rolling Updates,
- Node-Ausfall-Simulation ohne Control-Plane-Ausfall.
Risiko:
- Der einzige Control-Plane-Node bleibt ein Single Point of Failure.
4.3. HA mit gestapeltem etcd
Bei gestapeltem etcd läuft etcd auf den Control-Plane-Nodes.
flowchart TD
Client[kubectl / API Clients] --> VIP[Control-Plane-Endpunkt]
VIP --> CP1[Control Plane 1 + etcd]
VIP --> CP2[Control Plane 2 + etcd]
VIP --> CP3[Control Plane 3 + etcd]
CP1 <--> CP2
CP2 <--> CP3
CP1 <--> CP3
CP1 --> W1[Worker 1]
CP2 --> W2[Worker 2]
CP3 --> W3[Worker 3]
Empfehlung:
- mindestens 3 Control-Plane-Nodes,
- ungerade Anzahl von etcd-Mitgliedern,
- dedizierter stabiler Control-Plane-Endpunkt,
- saubere Zeitsynchronisation,
- schnelle und zuverlässige Netzwerkverbindung zwischen Control-Plane-Nodes,
- regelmäßige etcd-Backups und Restore-Tests.
4.4. HA mit externem etcd
Bei externem etcd sind die etcd-Nodes von den Control-Plane-Nodes getrennt.
flowchart TD
Client[kubectl] --> LB[API Load Balancer]
LB --> CP1[Control Plane 1]
LB --> CP2[Control Plane 2]
LB --> CP3[Control Plane 3]
CP1 --> E1[etcd 1]
CP1 --> E2[etcd 2]
CP1 --> E3[etcd 3]
CP2 --> E1
CP2 --> E2
CP2 --> E3
CP3 --> E1
CP3 --> E2
CP3 --> E3
CP1 --> Workers[Worker Nodes]
Vorteil:
- klare Trennung von API/Controller/Scheduler und etcd,
- besser kontrollierbare etcd-Ressourcen,
- sauberer für große produktive Umgebungen.
Nachteil:
- mehr Maschinen,
- mehr Zertifikats- und Backup-Aufwand,
- mehr Netzwerk- und Betriebsabhängigkeiten.
4.5. Edge-Cluster mit k3s
flowchart TD
Server1[k3s Server 1] <--> Server2[k3s Server 2]
Server2 <--> Server3[k3s Server 3]
Server1 <--> Server3
Server1 --> Agent1[k3s Agent 1]
Server2 --> Agent2[k3s Agent 2]
Server3 --> Agent3[k3s Agent 3]
Client[kubectl] --> Fixed[Stabile Server-Adresse oder LB]
Fixed --> Server1
Fixed --> Server2
Fixed --> Server3
k3s eignet sich für kleine Standorte, Edge-Use-Cases, Homelabs und Hardware mit begrenzten Ressourcen. Du musst aber wissen, welche Komponenten k3s bereits mitbringt und welche du ersetzen willst.
4.6. Talos-Cluster
flowchart TD
Admin[talosctl auf deinem Client] --> TalosAPI[Talos API]
TalosAPI --> CP1[Talos Control Plane]
TalosAPI --> CP2[Talos Control Plane]
TalosAPI --> CP3[Talos Control Plane]
CP1 --> K8s[Kubernetes]
CP2 --> K8s
CP3 --> K8s
K8s --> Workers[Talos Worker]
Admin --> Kubeconfig[kubectl kubeconfig]
Kubeconfig --> K8s
Talos ist anders als klassische Linux-Hosts:
- kein SSH,
- keine manuelle Paketinstallation,
- deklarative Maschinenkonfiguration,
- Betrieb über
talosctl, - stark reduzierter Host-Footprint.
5. Planungscheckliste
Bevor du installierst, beantwortest du die folgenden Fragen schriftlich.
5.1. Clusteridentität
| Frage | Beispiel |
|---|---|
| Wie heißt der Cluster? | bm-lab-01 |
| Welche Kubernetes-Version verwendest du? | v1.36.x oder projektweit festgelegte Minor-Version |
| Welche Bootstrap-Methode nutzt du? | kubeadm, k3s, RKE2, k0s, Talos |
| Welche Umgebung ist es? | Workshop, Lab, Edge, Produktion, Air-Gap |
| Wo liegt der Zielzustand? | Git-Repository, Ansible, Terraform, Dokumentation |
| Wie erfolgt der Rückbau? | dokumentiertes Reset- und Datenlöschverfahren |
5.2. Node-Plan
| Rolle | Mindestanzahl | Empfehlung | Hinweise |
|---|---|---|---|
| Control Plane ohne HA | 1 | 1 | nur Lab |
| Control Plane HA | 3 | 3 oder 5 | ungerade etcd-Anzahl |
| Externes etcd | 3 | 3 oder 5 | dedizierte schnelle Disks |
| Worker | 1 | 3+ | abhängig von Workloads |
| Load Balancer | 1 | 2 | HAProxy/Keepalived, Hardware-LB oder kube-vip |
| Storage Nodes | 1 | 3+ | bei Ceph/Longhorn/OpenEBS relevant |
5.3. Namensschema
Nutze eindeutige Hostnames. Beispiel:
| Node | Rolle | IP | Hinweise |
|---|---|---|---|
k8s-cp-01 |
Control Plane | 10.10.10.11 |
erster Bootstrap-Node |
k8s-cp-02 |
Control Plane | 10.10.10.12 |
HA Join |
k8s-cp-03 |
Control Plane | 10.10.10.13 |
HA Join |
k8s-w-01 |
Worker | 10.10.10.21 |
Workloads |
k8s-w-02 |
Worker | 10.10.10.22 |
Workloads |
k8s-lb-01 |
Load Balancer | 10.10.10.5 |
optional |
k8s-api.lab.local |
API-Endpunkt | 10.10.10.10 |
VIP oder LB |
5.4. CIDR-Plan
Lege die Netze vor kubeadm init fest. Eine spätere Änderung ist aufwendig.
| Netz | Beispiel | Zweck |
|---|---|---|
| Management-Netz | 10.10.10.0/24 |
SSH, BMC, Adminzugriff |
| Node-Netz | 10.10.20.0/24 |
Kubernetes Node-IP, kubelet, API |
| Pod-Netz | 10.244.0.0/16 oder 10.42.0.0/16 |
Pod-IP-Adressen |
| Service-Netz | 10.96.0.0/12 |
ClusterIP-Services |
| LoadBalancer-Pool | 10.10.20.200-10.10.20.240 |
MetalLB oder kube-vip |
| Storage-Netz | 10.10.30.0/24 |
Ceph, iSCSI, NFS, Replikation |
| BMC-Netz | 10.10.99.0/24 |
IPMI, iDRAC, iLO, Redfish |
Achte darauf, dass Pod- und Service-CIDRs nicht mit Unternehmensnetzen, VPNs oder lokalen Netzen kollidieren.
5.5. Port-Plan
| Verbindung | Port | Richtung | Zweck |
|---|---|---|---|
| Client zu API | TCP 6443 | Client → Control Plane | Kubernetes API |
| kubelet API | TCP 10250 | Control Plane → Nodes | Logs, Exec, Metrics |
| etcd Client | TCP 2379 | API → etcd | etcd Zugriff |
| etcd Peer | TCP 2380 | etcd ↔ etcd | etcd Replikation |
| kube-scheduler | TCP 10259 | lokal/Control Plane | HTTPS |
| kube-controller-manager | TCP 10257 | lokal/Control Plane | HTTPS |
| NodePort | TCP/UDP 30000-32767 | extern → Nodes | Services vom Typ NodePort |
| CNI spezifisch | variabel | Nodes ↔ Nodes | VXLAN, Geneve, BGP, WireGuard |
| MetalLB L2 | TCP/UDP 7946 | Nodes ↔ Nodes | Memberlist, wenn L2-Modus |
| BGP | TCP 179 | Nodes ↔ Router | MetalLB/Cilium BGP |
| SSH | TCP 22 | Admin → Nodes | klassische Linux-Hosts |
| BMC | variabel | Admin → BMC | Out-of-Band-Management |
6. Hardware- und Firmware-Vorbereitung
Bare-Metal-Provisionierung beginnt vor dem Betriebssystem.
6.1. Hardware-Mindestwerte
| Rolle | CPU | RAM | Disk | Netzwerk |
|---|---|---|---|---|
| Single-Node-Lab | 2 vCPU/Kerne | 4 GB | 40 GB SSD | 1 Gbit/s |
| Control Plane | 2+ Kerne | 4 bis 8 GB | 60+ GB SSD | 1 Gbit/s, besser 10 Gbit/s |
| Worker klein | 2+ Kerne | 4+ GB | 80+ GB SSD | 1 Gbit/s |
| Worker produktionsnah | 4+ Kerne | 16+ GB | 200+ GB SSD/NVMe | 10 Gbit/s |
| Storage Node | 4+ Kerne | 16+ GB | dedizierte Daten-Disks | 10 Gbit/s empfohlen |
| etcd dediziert | 2+ Kerne | 8+ GB | schnelle, latenzarme SSD | stabiles Netz |
6.2. Firmware-Checkliste
Prüfe auf jedem Server:
- aktueller Firmwarestand für BIOS/UEFI, NIC, RAID/HBA, BMC,
- eindeutige Seriennummern und UUIDs,
- aktivierte Virtualisierung nur falls für Runtime-Features oder Nested Labs nötig,
- korrekte Bootreihenfolge,
- UEFI bevorzugt, Legacy nur bei Bedarf,
- Secure Boot nur aktiv, wenn deine Distribution und Runtime sauber damit umgehen,
- NTP/RTC korrekt,
- BMC/IPMI/Redfish erreichbar,
- RAID/HBA-Modus passend zum Storage-Design,
- keine versteckten Hersteller-Partitionen mit kritischer Automatik.
6.3. Disk-Layout
Für Lab-Cluster reicht oft eine einzelne SSD. Für produktionsnahe Bare-Metal-Cluster trennst du besser:
| Mount | Zweck | Empfehlung |
|---|---|---|
/ |
Betriebssystem | SSD, nicht zu klein |
/var/lib/containerd |
Images und Containerdaten | eigene Partition oder LV |
/var/lib/kubelet |
Pod-Daten, Volume-Mounts | eigene Partition oder LV |
/var/lib/etcd |
etcd-Daten | schnelle dedizierte SSD bei Control Plane |
/var/log |
Logs | eigene Begrenzung oder Rotation |
| Storage-Disks | PV/CSI/Ceph/Longhorn/OpenEBS | nicht mit OS-Disk vermischen |
6.4. BIOS- und Energieprofil
Setze ein konsistentes Energieprofil:
- Performance- oder Balanced-Profil statt aggressivem Powersave,
- CPU C-States nur dann stark begrenzen, wenn Latenz kritisch ist,
- NUMA und Hyperthreading bewusst bewerten,
- NIC-Offloading nur ändern, wenn du konkrete Netzwerkprobleme misst,
- MTU konsistent über Switch, Host, CNI und Overlay planen.
7. Netzwerkdesign
Kubernetes auf Bare Metal scheitert häufig nicht an Kubernetes selbst, sondern an unklaren Layer-2/Layer-3-Annahmen.
7.1. Netzwerkmodell
flowchart TD
Admin[Admin-Netz] --> API[API-Endpunkt 10.10.20.10:6443]
API --> CP[Control-Plane-Nodes]
CP --> NodeNet[Node-Netz 10.10.20.0/24]
NodeNet --> W1[Worker 1]
NodeNet --> W2[Worker 2]
NodeNet --> W3[Worker 3]
W1 <--> PodNet[Pod-Netz 10.244.0.0/16]
W2 <--> PodNet
W3 <--> PodNet
Users[Benutzer / Clients] --> LBPool[LoadBalancer-Pool 10.10.20.200-240]
LBPool --> Services[Kubernetes Services]
StorageNet[Storage-Netz 10.10.30.0/24] --> W1
StorageNet --> W2
StorageNet --> W3
7.2. Grundregeln
- Jeder Node braucht eine stabile Node-IP.
- Jeder Node muss jeden anderen Node erreichen können.
- Control-Plane-Nodes müssen stabil über den API-Endpunkt erreichbar sein.
- Pod-CIDR und Service-CIDR dürfen sich nicht mit bestehenden Netzen überschneiden.
- CNI-Overlay-Ports müssen zwischen Nodes erlaubt sein.
- DNS und NTP müssen zuverlässig funktionieren.
- Firewall-Regeln dürfen
kubelet, CNI, etcd und API nicht blockieren. - MTU muss zum CNI-Modus passen.
7.3. Statische IPs oder DHCP
Beides ist möglich. Für Bare-Metal-Kubernetes ist eine stabile Adressierung entscheidend.
| Methode | Geeignet für | Empfehlung |
|---|---|---|
| Statische OS-IP | Rechenzentrum, kleine Labs | gut nachvollziehbar |
| DHCP Reservation | PXE, MAAS, große Labs | gut automatisierbar |
| Dynamisches DHCP ohne Reservation | Tests | nicht für stabile Cluster |
| BGP-announced VIP | produktionsnah | gut mit MetalLB/Cilium/kube-vip |
| VRRP VIP | kleine HA-Cluster | einfach, aber Layer-2-abhängig |
7.4. Mehrere Netzwerkkarten
Wenn ein Host mehrere NICs oder Default-Routen hat, musst du die Node-IP explizit kontrollieren.
Beispiel für kubeadm:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.10.20.11
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: 10.10.20.11
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controlPlaneEndpoint: k8s-api.lab.local:6443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
7.5. DNS-Plan
Mindestens diese Namen sollten auflösbar sein:
| Name | Ziel |
|---|---|
k8s-api.lab.local |
API-VIP oder Load Balancer |
k8s-cp-01.lab.local |
Control Plane 1 |
k8s-cp-02.lab.local |
Control Plane 2 |
k8s-cp-03.lab.local |
Control Plane 3 |
registry.lab.local |
interne Registry |
*.apps.lab.local |
Ingress/Gateway Wildcard, falls genutzt |
8. Betriebssystem-Baseline
8.1. Gemeinsame Linux-Vorbereitung
Diese Schritte gelten für klassische Linux-Hosts mit kubeadm, k3s, RKE2 oder k0s.
sudo hostnamectl set-hostname <NODE_NAME>
sudo timedatectl set-ntp true
sudo systemctl enable --now systemd-timesyncd || true
ip addr
ip route
getent hosts k8s-api.lab.local || true
Prüfe eindeutige Hostidentität:
hostname -f || hostname
ip link
sudo cat /sys/class/dmi/id/product_uuid || true
8.2. Swap
Für klassische kubeadm-Installationen deaktivierst du Swap, sofern du nicht bewusst eine Swap-Konfiguration für den kubelet nutzt.
sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
free -h
8.3. Kernel-Module und sysctl
cat <<'EOF' | sudo tee /etc/modules-load.d/kubernetes.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Validierung:
lsmod | grep -E 'overlay|br_netfilter'
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward
8.4. Paketbasis
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release \
apt-transport-https \
jq \
yq \
git \
vim \
bash-completion \
conntrack \
socat \
ebtables \
ethtool \
iproute2 \
iptables \
nftables \
nfs-common \
open-iscsi
RHEL/Rocky/Alma/Fedora:
sudo dnf install -y \
ca-certificates \
curl \
gnupg2 \
jq \
git \
vim \
bash-completion \
conntrack-tools \
socat \
ebtables \
ethtool \
iproute \
iptables \
nftables \
nfs-utils \
iscsi-initiator-utils
SLES/openSUSE:
sudo zypper refresh
sudo zypper install -y \
ca-certificates \
curl \
gpg2 \
jq \
git \
vim \
bash-completion \
conntrack-tools \
socat \
ebtables \
ethtool \
iproute2 \
iptables \
nftables \
nfs-client \
open-iscsi
8.5. SELinux und AppArmor
Auf RHEL-kompatiblen Systemen musst du SELinux bewusst behandeln. Für einfache kubeadm-Labs wird oft permissive verwendet. Für produktive Umgebungen solltest du SELinux nicht blind abschalten, sondern CNI, CSI und Runtime darauf abstimmen.
getenforce || true
sudo setenforce 0 || true
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config || true
Auf Ubuntu/Debian prüfst du AppArmor:
sudo aa-status || true
8.6. Firewall
Für Labs kann es sinnvoll sein, Host-Firewalls temporär zu deaktivieren. Für produktionsnahe Umgebungen dokumentierst du stattdessen erlaubte Ports.
Ubuntu mit UFW:
sudo ufw status verbose
# Nur im Lab, wenn du die Wirkung verstehst:
# sudo ufw disable
RHEL/Rocky mit firewalld:
sudo firewall-cmd --state || true
sudo firewall-cmd --list-all || true
Beispielhafte Freigaben für Control Plane:
sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --permanent --add-port=2379-2380/tcp
sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --permanent --add-port=10257/tcp
sudo firewall-cmd --permanent --add-port=10259/tcp
sudo firewall-cmd --reload
9. Container Runtime
Kubernetes spricht Container Runtimes über CRI an. Typische Bare-Metal-Optionen sind containerd und CRI-O.
9.1. Runtime-Entscheidung
| Runtime | Geeignet für | Hinweise |
|---|---|---|
| containerd | Standard-Labs, Produktion, k3s/RKE2 intern | weit verbreitet, schlank |
| CRI-O | OpenShift-nahe oder Red-Hat-nahe Umgebungen | stark Kubernetes-fokussiert |
| Docker Engine + cri-dockerd | Legacy-Szenarien | zusätzlicher Shim nötig, nicht bevorzugt |
9.2. containerd auf Ubuntu/Debian
Variante über Distribution:
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl enable --now containerd
sudo systemctl restart containerd
sudo systemctl status containerd --no-pager
Prüfung:
sudo ctr version
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock info || true
9.3. containerd auf RHEL/Rocky/Alma/Fedora
sudo dnf install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl enable --now containerd
sudo systemctl restart containerd
Wenn die Distribution kein passendes containerd bereitstellt, nutzt du eine freigegebene Paketquelle nach interner Vorgabe. Dokumentiere Quelle, Version und Upgrade-Pfad.
9.4. CRI-O Baseline
CRI-O-Versionen sollten zur Kubernetes-Minor-Version passen. Lege die Version zentral fest.
export KUBERNETES_MINOR="v1.36"
export CRIO_VERSION="v1.36"
Die konkrete Repository-Konfiguration hängt von Distribution und Projektstandard ab. Nach der Installation prüfst du:
sudo systemctl enable --now crio
sudo systemctl status crio --no-pager
sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock info
9.5. crictl konfigurieren
Für containerd:
cat <<'EOF' | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF
Für CRI-O:
cat <<'EOF' | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///var/run/crio/crio.sock
image-endpoint: unix:///var/run/crio/crio.sock
timeout: 10
debug: false
EOF
Prüfung:
sudo crictl info
sudo crictl images
9.6. Registry Mirrors und private Registries
Beispiel für containerd mit internem Mirror:
# /etc/containerd/config.toml, Ausschnitt
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://registry.lab.local:5000"]
[plugins."io.containerd.grpc.v1.cri".registry.configs."registry.lab.local:5000".tls]
ca_file = "/etc/containerd/certs.d/registry.lab.local/ca.crt"
Danach:
sudo systemctl restart containerd
sudo crictl pull registry.k8s.io/pause:3.10 || true
10. kubeadm installieren
10.1. Version festlegen
Lege die Kubernetes-Minor-Version zentral fest:
export KUBERNETES_MINOR="v1.36"
Ändere diesen Wert bewusst, wenn dein Workshop oder Projekt eine andere Version verwendet. Mische Minor-Versionen nicht ohne Upgrade-Plan.
10.2. Ubuntu/Debian mit pkgs.k8s.io
export KUBERNETES_MINOR="v1.36"
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/deb/Release.key" \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet
10.3. RHEL/Rocky/Alma/Fedora mit pkgs.k8s.io
export KUBERNETES_MINOR="v1.36"
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
sudo systemctl enable --now kubelet
Bei DNF5 kann --setopt=disable_excludes=kubernetes nötig sein.
10.4. Ohne Paketmanager
Für Distributionen ohne unterstützte Paketquellen nutzt du Binaries. Das ist fortgeschrittener und muss sauber versioniert werden.
RELEASE="$(curl -sSL https://dl.k8s.io/release/stable.txt)"
ARCH="amd64"
DOWNLOAD_DIR="/usr/local/bin"
sudo mkdir -p "${DOWNLOAD_DIR}"
cd "${DOWNLOAD_DIR}"
sudo curl -L --remote-name-all "https://dl.k8s.io/release/${RELEASE}/bin/linux/${ARCH}/{kubeadm,kubelet,kubectl}"
sudo chmod +x kubeadm kubelet kubectl
Du brauchst zusätzlich eine passende kubelet-Systemd-Unit. Für Labs ist der Paketweg deutlich einfacher.
10.5. Installation prüfen
kubeadm version
kubectl version --client
kubelet --version
sudo systemctl status kubelet --no-pager
Dass kubelet vor kubeadm init noch nicht dauerhaft gesund läuft, ist normal. Es wartet auf Konfiguration durch kubeadm.
11. kubeadm: Single-Node-Cluster
11.1. Konfigurationsdatei erstellen
Für ein reproduzierbares Setup nutzt du eine kubeadm-Konfigurationsdatei.
mkdir -p ~/kubeadm
cat <<'EOF' > ~/kubeadm/kubeadm-single-node.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.10.20.11
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: 10.10.20.11
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: stable
clusterName: bm-single
controlPlaneEndpoint: 10.10.20.11:6443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
dnsDomain: cluster.local
apiServer:
certSANs:
- 10.10.20.11
- bm-single.lab.local
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
Passe advertiseAddress, node-ip, clusterName, podSubnet, serviceSubnet und certSANs an deine Umgebung an.
11.2. Control Plane initialisieren
sudo kubeadm init --config ~/kubeadm/kubeadm-single-node.yaml
Kubeconfig einrichten:
mkdir -p "$HOME/.kube"
sudo cp -i /etc/kubernetes/admin.conf "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"
kubectl cluster-info
kubectl get nodes -o wide
11.3. Workloads auf Control Plane zulassen
Single-Node-Labs brauchen Workloads auf dem Control-Plane-Node. Entferne dafür den Taint bewusst:
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
11.4. CNI installieren
Ohne CNI bleiben Nodes typischerweise NotReady, und CoreDNS startet nicht vollständig.
Beispiel mit Flannel bei passendem Pod-CIDR:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
Beispiel mit Cilium CLI:
cilium install --version <CILIUM_VERSION>
cilium status --wait
Beispiel mit Calico:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/<CALICO_VERSION>/manifests/calico.yaml
Wähle genau ein primäres CNI-Plugin. Mehrere aktive CNIs ohne klares Chain-Konzept erzeugen schwer nachvollziehbare Fehler.
11.5. Validieren
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl -n kube-system get pods
kubectl run smoke --image=registry.k8s.io/e2e-test-images/agnhost:2.53 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/smoke --timeout=120s
kubectl delete pod smoke
12. kubeadm: Multi-Node-Cluster
12.1. Control Plane initialisieren
Verwende einen stabilen Control-Plane-Endpunkt, auch wenn du zunächst nur einen Control-Plane-Node hast. Damit hältst du dir einen späteren HA-Ausbau offen.
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.10.20.11
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: 10.10.20.11
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: stable
clusterName: bm-multi
controlPlaneEndpoint: k8s-api.lab.local:6443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
apiServer:
certSANs:
- k8s-api.lab.local
- 10.10.20.10
- 10.10.20.11
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
Initialisierung:
sudo kubeadm init --config kubeadm-multi.yaml
12.2. Join-Command erzeugen
Wenn du den Join-Befehl nicht gespeichert hast:
kubeadm token create --print-join-command
Beispiel:
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
12.3. Worker vorbereiten und beitreten lassen
Auf jedem Worker:
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
Danach auf deinem Admin-Client:
kubectl get nodes -o wide
kubectl get pods -A -o wide
12.4. Labels setzen
kubectl label node k8s-w-01 node-role.kubernetes.io/worker=worker
kubectl label node k8s-w-02 node-role.kubernetes.io/worker=worker
kubectl label node k8s-w-03 node-role.kubernetes.io/worker=worker
kubectl label node k8s-w-01 topology.kubernetes.io/zone=rack-a
kubectl label node k8s-w-02 topology.kubernetes.io/zone=rack-b
kubectl label node k8s-w-03 topology.kubernetes.io/zone=rack-c
12.5. Smoke-Test mit mehreren Nodes
kubectl create namespace smoke
kubectl create deployment web -n smoke --image=nginx:stable --replicas=3
kubectl expose deployment web -n smoke --port=80 --target-port=80
kubectl get pod -n smoke -o wide
kubectl run curl -n smoke --rm -it --image=curlimages/curl --restart=Never -- curl -sS http://web.smoke.svc.cluster.local
kubectl delete namespace smoke
13. kubeadm: Hochverfügbarkeit
13.1. Voraussetzungen
Für Control-Plane-HA brauchst du:
- mindestens drei Control-Plane-Nodes,
- stabilen API-Endpunkt vor den Control-Plane-Nodes,
- Load Balancer für TCP 6443,
- konsistente Zertifikats-SANs,
- funktionierende Namensauflösung,
- identische Kubernetes-Minor-Version auf allen Nodes,
- funktionierende Zeitsynchronisation,
- etcd-Backupstrategie.
13.2. Load-Balancer-Optionen
| Option | Vorteil | Nachteil | Typischer Einsatz |
|---|---|---|---|
| Hardware Load Balancer | robust, zentral gemanagt | abhängig von Netzwerkteam | Rechenzentrum |
| HAProxy + Keepalived | transparent, günstig | zwei zusätzliche Hosts oder auf CP-Nodes | Lab, Mittelstand |
| kube-vip | Kubernetes-nah, keine externe Appliance | muss sehr sauber geplant werden | kleine HA-Cluster |
| BGP-LB | skalierbar, elegant | Router/BGP-Know-how nötig | Netzwerknahe Plattformen |
| DNS-Round-Robin | einfach | kein echtes Health Checking | nur Tests |
13.3. HAProxy-Beispiel
/etc/haproxy/haproxy.cfg:
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 4096
defaults
log global
mode tcp
option tcplog
timeout connect 10s
timeout client 1m
timeout server 1m
frontend kubernetes_api
bind *:6443
default_backend kubernetes_control_plane
backend kubernetes_control_plane
balance roundrobin
option tcp-check
server cp1 10.10.20.11:6443 check
server cp2 10.10.20.12:6443 check
server cp3 10.10.20.13:6443 check
Start:
sudo systemctl enable --now haproxy
sudo systemctl status haproxy --no-pager
nc -vz k8s-api.lab.local 6443
13.4. Keepalived-Beispiel
/etc/keepalived/keepalived.conf auf dem primären LB:
vrrp_instance K8S_API {
state MASTER
interface eth0
virtual_router_id 51
priority 110
advert_int 1
authentication {
auth_type PASS
auth_pass CHANGE_ME
}
virtual_ipaddress {
10.10.20.10/24
}
}
Auf dem zweiten LB setzt du state BACKUP und eine niedrigere priority.
sudo systemctl enable --now keepalived
ip addr show eth0
13.5. kube-vip als Control-Plane-VIP
kube-vip kann eine virtuelle IP für die Kubernetes API bereitstellen. Das ist besonders nützlich in kleineren Bare-Metal-Clustern ohne externen Load Balancer.
Konzept:
flowchart LR
Client[kubectl] --> VIP[VIP 10.10.20.10]
VIP --> CP1[kube-vip auf cp1]
VIP --> CP2[kube-vip auf cp2]
VIP --> CP3[kube-vip auf cp3]
CP1 --> API1[API Server cp1]
CP2 --> API2[API Server cp2]
CP3 --> API3[API Server cp3]
Wichtig:
- Erzeuge das kube-vip-Manifest vor
kubeadm init, wenn kube-vip als statischer Pod laufen soll. - Prüfe, ob du ARP, BGP oder Routing-Modus verwenden willst.
- Stelle sicher, dass die VIP nicht bereits anderweitig vergeben ist.
13.6. HA kubeadm-Konfiguration
kubeadm-ha.yaml auf dem ersten Control-Plane-Node:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.10.20.11
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: 10.10.20.11
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: stable
clusterName: bm-ha
controlPlaneEndpoint: k8s-api.lab.local:6443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
apiServer:
certSANs:
- k8s-api.lab.local
- 10.10.20.10
- 10.10.20.11
- 10.10.20.12
- 10.10.20.13
controllerManager: {}
scheduler: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
Initialisierung:
sudo kubeadm init --config kubeadm-ha.yaml --upload-certs
Speichere die ausgegebenen Join-Befehle sicher. Der Zertifikatsschlüssel aus --upload-certs ist zeitlich begrenzt.
13.7. Weitere Control-Plane-Nodes joinen
Auf k8s-cp-02 und k8s-cp-03:
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--control-plane \
--certificate-key <CERTIFICATE_KEY>
Nach jedem Join prüfen:
kubectl get nodes -o wide
kubectl -n kube-system get pods -o wide
kubectl get --raw='/readyz?verbose'
13.8. Worker joinen
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
13.9. CoreDNS nach HA-Ausbau neu verteilen
Wenn du Control-Plane-Nodes nacheinander hinzufügst, können CoreDNS-Pods anfangs auf dem ersten Node konzentriert sein.
kubectl -n kube-system rollout restart deployment coredns
kubectl -n kube-system rollout status deployment coredns
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
14. Externes etcd
Externes etcd ist sinnvoll, wenn du etcd-Ressourcen und Control Plane getrennt betreiben willst.
14.1. Architektur
flowchart TD
LB[API Load Balancer] --> CP1[API Server 1]
LB --> CP2[API Server 2]
LB --> CP3[API Server 3]
CP1 --> ETCDLB[etcd Endpoints]
CP2 --> ETCDLB
CP3 --> ETCDLB
ETCDLB --> E1[etcd 1]
ETCDLB --> E2[etcd 2]
ETCDLB --> E3[etcd 3]
14.2. Anforderungen
- Drei oder fünf etcd-Mitglieder.
- Niedrige Latenz zwischen etcd-Mitgliedern.
- Schnelle SSDs mit stabiler I/O-Latenz.
- Gesicherte Peer- und Client-Zertifikate.
- Verschlüsselte Backups.
- Regelmäßige Restore-Tests.
14.3. kubeadm-Konfigurationsausschnitt
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: stable
controlPlaneEndpoint: k8s-api.lab.local:6443
etcd:
external:
endpoints:
- https://10.10.40.11:2379
- https://10.10.40.12:2379
- https://10.10.40.13:2379
caFile: /etc/kubernetes/pki/etcd/ca.crt
certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
15. CNI-Auswahl und Installation
CNI entscheidet über Pod-Netzwerk, NetworkPolicy, Routing, Overlay, eBPF, Verschlüsselung und teilweise Load Balancing.
15.1. Entscheidungsmatrix
| CNI | Stärken | Typische Nutzung | Hinweise |
|---|---|---|---|
| Flannel | einfach, schnell erklärbar | Workshops, kleine Labs | keine native NetworkPolicy Enforcement |
| Calico | NetworkPolicy, BGP, robust | Labs, Produktion | sehr verbreitet |
| Cilium | eBPF, NetworkPolicy, Observability, kube-proxy-Ersatz | moderne Plattformen | Kernel- und Modusanforderungen prüfen |
| Weave Net | historisch einfach | Legacy | Projektstatus prüfen |
| Canal | Flannel + Calico Policy | bestimmte Legacy-Setups | heute seltener |
15.2. Flannel
Passendes podSubnet meist 10.244.0.0/16.
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl -n kube-flannel get pods -o wide
15.3. Calico
Calico kann Overlay oder BGP verwenden. Für Bare Metal ist BGP interessant, braucht aber Router-/Netzwerkabstimmung.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/<CALICO_VERSION>/manifests/calico.yaml
kubectl -n calico-system get pods -o wide || kubectl -n kube-system get pods -o wide | grep calico
15.4. Cilium
Generischer Installationspfad:
cilium install --version <CILIUM_VERSION>
cilium status --wait
cilium connectivity test
Mit kube-proxy-Ersatz musst du den Cluster passend initialisieren, zum Beispiel mit --skip-phases=addon/kube-proxy oder einer passenden kubeadm-Konfiguration. Prüfe vorher Kernel, cgroup v2, Routing-Modus, LoadBalancer-Modus und Hubble-Anforderungen.
15.5. CNI-Validierung
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl -n kube-system get daemonsets
kubectl run net-a --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl run net-b --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/net-a --timeout=120s
kubectl wait --for=condition=Ready pod/net-b --timeout=120s
kubectl get pods -o wide
kubectl exec net-a -- ping -c 3 <POD_IP_VON_NET_B>
kubectl delete pod net-a net-b
16. Services, LoadBalancer und Ingress auf Bare Metal
In Cloud-Umgebungen stellt der Cloud Controller oft externe Load Balancer bereit. Auf Bare Metal musst du diese Funktion selbst bereitstellen.
16.1. Optionen
| Funktion | Option | Bemerkung |
|---|---|---|
Service LoadBalancer |
MetalLB L2 | einfach, Layer-2-nah |
Service LoadBalancer |
MetalLB BGP | besser für skalierbare Netzwerke |
| API-VIP und Services | kube-vip | gut für kleine bis mittlere Bare-Metal-Cluster |
| Edge Ingress | NodePort + externer Reverse Proxy | einfach, aber manuell |
| Produktionsingress | Hardware-/Software-LB + Ingress Controller | robust, etablierte Betriebsgrenzen |
16.2. MetalLB Layer 2
Installation mit Manifest oder Helm nach Projektstandard. Danach definierst du IP-Adresspool und Advertisement.
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lab-pool
namespace: metallb-system
spec:
addresses:
- 10.10.20.200-10.10.20.240
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: lab-l2
namespace: metallb-system
spec:
ipAddressPools:
- lab-pool
Validierung:
kubectl get pods -n metallb-system
kubectl apply -f metallb-pool.yaml
kubectl create deployment lb-test --image=nginx:stable
kubectl expose deployment lb-test --port=80 --type=LoadBalancer
kubectl get svc lb-test -w
curl http://<EXTERNAL_IP>
kubectl delete svc lb-test
kubectl delete deployment lb-test
16.3. MetalLB BGP
BGP ist für größere Bare-Metal-Umgebungen oft sauberer. Du brauchst dafür Router, ASN-Plan und abgestimmte Policies.
Beispielhafte Ressourcen:
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: router-a
namespace: metallb-system
spec:
myASN: 64512
peerASN: 64500
peerAddress: 10.10.20.1
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: bgp-pool
namespace: metallb-system
spec:
addresses:
- 10.10.50.0/24
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: bgp-adv
namespace: metallb-system
spec:
ipAddressPools:
- bgp-pool
16.4. Ingress Controller
Beispiel mit NGINX Ingress Controller über Helm:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer
Validierung:
kubectl -n ingress-nginx get svc,pods
kubectl get ingressclass
16.5. Gateway API
Für moderne Routingmodelle kannst du Gateway API verwenden. Du brauchst einen Controller, der Gateway API unterstützt, zum Beispiel Envoy Gateway, Cilium Gateway API oder andere Implementierungen.
Typischer Ablauf:
kubectl get gatewayclass
kubectl get gateways -A
kubectl get httproutes -A
17. Storage auf Bare Metal
Storage ist auf Bare Metal eine explizite Architekturentscheidung. Kubernetes stellt die API, aber nicht automatisch ein hochverfügbares Storage-Backend bereit.
17.1. Optionen
| Storage | Geeignet für | Vorteile | Risiken |
|---|---|---|---|
emptyDir |
temporäre Daten | einfach | Datenverlust bei Pod-Neustart/Node-Wechsel |
hostPath |
Labs, lokale Tests | schnell | unsicher, nodegebunden |
| Local Persistent Volumes | lokale Performance | transparent | Workload an Node gebunden |
| NFS CSI | einfache RWX-Shares | leicht integrierbar | Single Point ohne HA-NFS |
| Longhorn | kleine bis mittlere Bare-Metal-Cluster | einfaches repliziertes Blockstorage | Netzwerk- und Disk-I/O beachten |
| OpenEBS | lokaler oder replizierter Storage | flexibel | Modusabhängig |
| Rook/Ceph | robuste Plattformen | skalierbar, RBD/CephFS/S3 | komplexer Betrieb |
| Hersteller-CSI | SAN/NAS/Array | Enterprise-Features | Abhängigkeit vom Hersteller |
17.2. Minimaler StorageClass-Test
kubectl get storageclass
kubectl get csidrivers
kubectl get pv,pvc -A
PVC-Test:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-smoke
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: <STORAGE_CLASS>
---
apiVersion: v1
kind: Pod
metadata:
name: storage-smoke
namespace: default
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo ok > /data/test.txt && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: storage-smoke
Validierung:
kubectl apply -f storage-smoke.yaml
kubectl wait --for=condition=Ready pod/storage-smoke --timeout=180s
kubectl exec storage-smoke -- cat /data/test.txt
kubectl delete -f storage-smoke.yaml
17.3. Storage-Designfragen
- Brauchst du
ReadWriteOnce,ReadWriteManyoderReadOnlyMany? - Darf ein Workload bei Node-Ausfall automatisch auf einem anderen Node starten?
- Wie wird Volume-Replikation umgesetzt?
- Wie werden Snapshots erstellt?
- Wie testest du Restore?
- Welche Daten sind nur Cache und welche sind geschäftskritisch?
- Wer betreibt das Storage-Backend?
- Welche Latenz- und IOPS-Anforderungen gelten?
18. Add-ons nach der Basisinstallation
Installiere Add-ons erst, wenn API, CNI, Nodes und DNS gesund sind.
18.1. Reihenfolge
flowchart TD
API[API und Nodes Ready] --> CNI[CNI]
CNI --> DNS[CoreDNS gesund]
DNS --> Metrics[Metrics Server]
Metrics --> LB[MetalLB oder kube-vip Services]
LB --> Ingress[Ingress oder Gateway]
Ingress --> Certs[cert-manager]
Certs --> Storage[CSI / Storage]
Storage --> Obs[Observability]
Obs --> GitOps[GitOps Bootstrap]
GitOps --> Policies[Policies und Security]
18.2. Metrics Server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl -n kube-system rollout status deployment metrics-server
kubectl top nodes
kubectl top pods -A
In Bare-Metal-Labs mit selbstsignierten Kubelet-Zertifikaten kann zusätzliche Konfiguration nötig sein. Für Produktion solltest du Zertifikate korrekt lösen, nicht dauerhaft unsichere Workarounds übernehmen.
18.3. cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
kubectl -n cert-manager get pods
18.4. Observability-Basis
Für Workshops reicht oft:
- Metrics Server,
- kube-state-metrics,
- Prometheus,
- Grafana,
- Loki oder anderes Logsystem,
- optional Tempo oder OpenTelemetry Collector.
Prüfkommandos:
kubectl get pods -A | grep -E 'prometheus|grafana|loki|otel|tempo|metrics'
kubectl get servicemonitors -A 2>/dev/null || true
kubectl get podmonitors -A 2>/dev/null || true
18.5. GitOps-Bootstrap
Beispielhafte Reihenfolge:
- Cluster-Basis mit
kubeadmoder Distribution bereitstellen. - CNI installieren.
- LoadBalancer-Funktion bereitstellen.
- GitOps-Controller installieren.
- Add-on-Repositories anbinden.
- Policies und Applikationen aus Git synchronisieren.
Argo CD Beispiel:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd rollout status deployment/argocd-server
Flux Beispiel:
flux check --pre
flux bootstrap git \
--url=ssh://git@example.local/platform/kubernetes.git \
--branch=main \
--path=clusters/bm-lab-01
19. Security-Baseline
19.1. Host-Härtung
- Nur notwendige Pakete installieren.
- SSH auf Schlüssel beschränken.
- Root-Login per SSH deaktivieren.
- BMC-Netz trennen.
- NTP erzwingen.
- Automatische Sicherheitsupdates bewusst regeln.
- Audit-Logs aktivieren.
- Container- und kubelet-Verzeichnisse nicht unkontrolliert beschreibbar machen.
- Host-Firewall dokumentiert konfigurieren.
SSH-Beispiel:
sudo sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl reload ssh || sudo systemctl reload sshd
19.2. Kubernetes API schützen
admin.confnicht teilen.- Nutzerzugriff über eigene Zertifikate, OIDC oder externe Identität lösen.
- RBAC mit Least Privilege erstellen.
system:mastersnicht für Alltagszugriff verwenden.- Audit Logging einplanen.
- Admission Policies verwenden.
Prüfung:
kubectl auth can-i get pods -A
kubectl auth can-i '*' '*' --as system:serviceaccount:default:default
kubectl get clusterrolebinding -o wide
19.3. Pod Security Admission
Namespace-Labels setzen:
kubectl label namespace default pod-security.kubernetes.io/enforce=baseline --overwrite
kubectl label namespace default pod-security.kubernetes.io/audit=restricted --overwrite
kubectl label namespace default pod-security.kubernetes.io/warn=restricted --overwrite
Für produktionsnahe Workloads bevorzugst du restricted, musst aber Images, SecurityContext und Volumes darauf abstimmen.
19.4. Encryption at Rest
Secrets sind ohne zusätzliche Konfiguration nur als Kubernetes-Objekte gespeichert. Für produktionsnahe Cluster aktivierst du Encryption at Rest.
Beispiel einer EncryptionConfiguration:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <BASE64_32_BYTE_KEY>
- identity: {}
Du musst die API-Server-Konfiguration entsprechend anpassen und den Schlüssel sicher verwalten. Teste Rotation und Restore.
19.5. NetworkPolicies
Starte mit einem Default-Deny-Modell pro Namespace und erlaube gezielt benötigte Kommunikation.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: demo
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
DNS-Ausnahme:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: demo
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
20. Air-Gap- und Restricted-Umgebungen
Air-Gap bedeutet nicht nur „kein Internet“. Es bedeutet, dass jedes Paket, jedes Image, jedes Chart, jeder Schlüssel und jede Signatur kontrolliert bereitgestellt werden muss.
20.1. Artefaktgruppen
| Gruppe | Beispiele |
|---|---|
| OS-Pakete | containerd, kubelet, kubeadm, kubectl, cri-tools |
| Container Images | Control Plane, CoreDNS, kube-proxy, pause, CNI, Add-ons |
| Helm Charts | CNI, Ingress, cert-manager, Observability |
| Manifeste | MetalLB, Policies, Storage, GitOps |
| Signaturen/SBOMs | Cosign, Checksums, Provenance |
| Zertifikate | interne CA, Registry-CA, TLS-Zertifikate |
20.2. Ablauf
flowchart LR
Internet[Online Staging] --> Mirror[Artefakt-Mirror]
Mirror --> Scan[Scan, Signaturprüfung, Freigabe]
Scan --> Transfer[Kontrollierter Transfer]
Transfer --> Registry[Interne Registry]
Transfer --> Repo[Interne Paketquelle]
Registry --> Nodes[Bare-Metal-Nodes]
Repo --> Nodes
Nodes --> Cluster[Kubernetes Cluster]
20.3. Images für kubeadm anzeigen
kubeadm config images list --kubernetes-version <VERSION>
Images vorab pullen:
sudo kubeadm config images pull --kubernetes-version <VERSION> --cri-socket unix:///run/containerd/containerd.sock
Mit alternativer Registry:
sudo kubeadm config images pull \
--kubernetes-version <VERSION> \
--image-repository registry.lab.local/kubernetes
20.4. Image-Rewrite in kubeadm
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.0
imageRepository: registry.lab.local/kubernetes
20.5. Lokale Registry testen
curl -k https://registry.lab.local/v2/_catalog || true
sudo crictl pull registry.lab.local/kubernetes/pause:3.10
20.6. Air-Gap-Checkliste
- Alle OS-Pakete liegen in interner Paketquelle.
- Kubernetes-Minor-Version ist festgelegt.
- Alle Container Images sind gespiegelt.
- Alle Helm Charts sind intern verfügbar.
- Registry-CA ist auf allen Nodes installiert.
- Runtime nutzt Registry-Mirror.
- CNI-Manifeste referenzieren interne Images.
- Add-on-Manifeste referenzieren interne Images.
- Installation wurde ohne Internetzugriff getestet.
- Restore-Verfahren ist offlinefähig.
21. k3s auf Bare Metal
k3s ist eine leichtgewichtige Kubernetes-Distribution. Sie eignet sich gut für Edge, kleine Bare-Metal-Cluster, ARM-Systeme und Labs.
21.1. Single-Node k3s
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
sudo cat /etc/rancher/k3s/k3s.yaml
Kubeconfig für deinen Benutzer:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
kubectl get nodes
21.2. k3s mit festgelegten Optionen
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="\
--cluster-cidr=10.42.0.0/16 \
--service-cidr=10.43.0.0/16 \
--disable traefik \
--disable servicelb" sh -
Warum Add-ons deaktivieren?
- Du willst im Workshop eigene Ingress- und LoadBalancer-Varianten installieren.
- Du willst Verhalten transparent erklären.
- Du willst Add-ons per GitOps verwalten.
21.3. k3s HA mit embedded etcd
Erster Server:
curl -sfL https://get.k3s.io | K3S_TOKEN="<CLUSTER_SECRET>" sh -s - server \
--cluster-init \
--tls-san k3s-api.lab.local \
--disable traefik \
--disable servicelb
Weitere Server:
curl -sfL https://get.k3s.io | K3S_TOKEN="<CLUSTER_SECRET>" sh -s - server \
--server https://k3s-api.lab.local:6443 \
--tls-san k3s-api.lab.local \
--disable traefik \
--disable servicelb
Agent:
curl -sfL https://get.k3s.io | K3S_TOKEN="<CLUSTER_SECRET>" sh -s - agent \
--server https://k3s-api.lab.local:6443
21.4. k3s prüfen
kubectl get nodes -o wide
kubectl get pods -A -o wide
sudo systemctl status k3s --no-pager
sudo journalctl -u k3s -n 100 --no-pager
22. RKE2 auf Bare Metal
RKE2 ist eine Kubernetes-Distribution von Rancher/SUSE mit Fokus auf Security und Enterprise-Umgebungen.
22.1. Erster Server
curl -sfL https://get.rke2.io | sudo sh -
sudo mkdir -p /etc/rancher/rke2
cat <<'EOF' | sudo tee /etc/rancher/rke2/config.yaml
token: <CLUSTER_SECRET>
tls-san:
- rke2-api.lab.local
node-ip: 10.10.20.11
cni: cilium
EOF
sudo systemctl enable --now rke2-server
Kubeconfig:
mkdir -p ~/.kube
sudo cp /etc/rancher/rke2/rke2.yaml ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
export PATH=$PATH:/var/lib/rancher/rke2/bin
kubectl get nodes
22.2. Weitere Server
curl -sfL https://get.rke2.io | sudo sh -
sudo mkdir -p /etc/rancher/rke2
cat <<'EOF' | sudo tee /etc/rancher/rke2/config.yaml
server: https://rke2-api.lab.local:9345
token: <CLUSTER_SECRET>
tls-san:
- rke2-api.lab.local
node-ip: 10.10.20.12
cni: cilium
EOF
sudo systemctl enable --now rke2-server
22.3. Agent Nodes
curl -sfL https://get.rke2.io | sudo INSTALL_RKE2_TYPE="agent" sh -
sudo mkdir -p /etc/rancher/rke2
cat <<'EOF' | sudo tee /etc/rancher/rke2/config.yaml
server: https://rke2-api.lab.local:9345
token: <CLUSTER_SECRET>
node-ip: 10.10.20.21
EOF
sudo systemctl enable --now rke2-agent
22.4. RKE2 prüfen
kubectl get nodes -o wide
kubectl get pods -A -o wide
sudo journalctl -u rke2-server -n 100 --no-pager
sudo journalctl -u rke2-agent -n 100 --no-pager || true
23. k0s auf Bare Metal
k0s bietet ein einfaches Controller-/Worker-Modell und bringt viele Komponenten gebündelt mit.
23.1. Single-Node k0s
curl -sSfL https://get.k0s.sh | sudo sh
sudo k0s install controller --single
sudo k0s start
sudo k0s status
sudo k0s kubectl get nodes
Kubeconfig:
mkdir -p ~/.kube
sudo k0s kubeconfig admin > ~/.kube/config
chmod 600 ~/.kube/config
kubectl get nodes
23.2. Multi-Node k0s
Controller installieren:
sudo k0s install controller
sudo k0s start
sudo k0s token create --role=worker > worker-token
sudo k0s token create --role=controller > controller-token
Worker installieren:
curl -sSfL https://get.k0s.sh | sudo sh
sudo k0s install worker --token-file /path/to/worker-token
sudo k0s start
23.3. k0s prüfen
sudo k0s status
sudo k0s kubectl get nodes -o wide
sudo k0s kubectl get pods -A -o wide
24. Talos Linux auf Bare Metal
Talos ist ein spezialisiertes Kubernetes-Betriebssystem. Du verwaltest Nodes über die Talos API mit talosctl, nicht über SSH.
24.1. Ablauf
flowchart TD
ISO[Talos ISO booten] --> Maintenance[Node im Maintenance Mode]
Maintenance --> Generate[talosctl gen config]
Generate --> ApplyCP[Control-Plane-Konfiguration anwenden]
Generate --> ApplyWorker[Worker-Konfiguration anwenden]
ApplyCP --> Bootstrap[talosctl bootstrap einmalig]
Bootstrap --> Kubeconfig[talosctl kubeconfig]
Kubeconfig --> Validate[kubectl get nodes]
24.2. Konfiguration erzeugen
export CLUSTER_NAME="talos-bm-lab"
export CONTROL_PLANE_ENDPOINT="https://10.10.20.10:6443"
talosctl gen config "${CLUSTER_NAME}" "${CONTROL_PLANE_ENDPOINT}" --output-dir ./talos-out
24.3. Konfiguration anwenden
Control Plane:
talosctl apply-config \
--insecure \
--nodes 10.10.20.11 \
--file ./talos-out/controlplane.yaml
Worker:
talosctl apply-config \
--insecure \
--nodes 10.10.20.21 \
--file ./talos-out/worker.yaml
24.4. Bootstrap
Bootstrap wird genau einmal auf einem Control-Plane-Node ausgeführt:
talosctl bootstrap \
--nodes 10.10.20.11 \
--endpoints 10.10.20.11 \
--talosconfig ./talos-out/talosconfig
Kubeconfig holen:
talosctl kubeconfig \
--nodes 10.10.20.11 \
--endpoints 10.10.20.11 \
--talosconfig ./talos-out/talosconfig \
--force
kubectl get nodes -o wide
24.5. Talos-Debugging
talosctl health --nodes 10.10.20.11 --endpoints 10.10.20.11
talosctl service kubelet --nodes 10.10.20.11
talosctl logs kubelet --nodes 10.10.20.11
talosctl dmesg --nodes 10.10.20.11
talosctl disks --nodes 10.10.20.11
25. Automatisierung mit Ansible
Für wiederholbare Bare-Metal-Provisionierung solltest du manuelle Kommandos in Rollen überführen.
25.1. Inventar
[control_plane]
k8s-cp-01 ansible_host=10.10.20.11 node_ip=10.10.20.11
k8s-cp-02 ansible_host=10.10.20.12 node_ip=10.10.20.12
k8s-cp-03 ansible_host=10.10.20.13 node_ip=10.10.20.13
[workers]
k8s-w-01 ansible_host=10.10.20.21 node_ip=10.10.20.21
k8s-w-02 ansible_host=10.10.20.22 node_ip=10.10.20.22
k8s-w-03 ansible_host=10.10.20.23 node_ip=10.10.20.23
[kubernetes:children]
control_plane
workers
25.2. Rollenstruktur
ansible/
inventory.ini
group_vars/
all.yaml
playbooks/
00-preflight.yaml
10-os-baseline.yaml
20-container-runtime.yaml
30-kubernetes-packages.yaml
40-kubeadm-init.yaml
50-kubeadm-join.yaml
60-addons.yaml
90-validate.yaml
roles/
os_baseline/
containerd/
kubeadm/
cni/
metallb/
ingress/
observability/
25.3. Beispiel: OS-Baseline-Tasks
- name: Kubernetes Kernelmodule laden
ansible.builtin.copy:
dest: /etc/modules-load.d/kubernetes.conf
content: |
overlay
br_netfilter
- name: Module sofort laden
ansible.builtin.command: modprobe {{ item }}
loop:
- overlay
- br_netfilter
changed_when: false
- name: sysctl setzen
ansible.builtin.copy:
dest: /etc/sysctl.d/99-kubernetes-cri.conf
content: |
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
- name: sysctl anwenden
ansible.builtin.command: sysctl --system
changed_when: false
25.4. Beispiel: Join-Datei sicher behandeln
- Token nicht im Klartext in Git speichern.
- Join-Kommandos nur temporär generieren.
- Zertifikatsschlüssel aus
--upload-certsnur kurz verwenden. - Secrets in Ansible Vault, SOPS oder einem Secret Manager verwalten.
26. PXE, Autoinstall und Metal3
26.1. Provisionierungskette
sequenceDiagram
participant Admin
participant BMC as BMC/IPMI/Redfish
participant DHCP
participant PXE as PXE/iPXE
participant HTTP as HTTP Artifact Server
participant Node
participant Ansible
participant K8s as Kubernetes API
Admin->>BMC: Power cycle und Boot Device setzen
Node->>DHCP: Lease und Boot-Optionen anfragen
DHCP->>Node: iPXE/PXE-Informationen
Node->>PXE: Bootloader laden
Node->>HTTP: Kernel, Initrd, Autoinstall laden
Node->>Node: OS installieren
Ansible->>Node: Baseline, Runtime, kubeadm installieren
Ansible->>K8s: Cluster initialisieren und Nodes joinen
26.2. Ubuntu Autoinstall
Eine Ubuntu-Autoinstall-Datei definiert Benutzer, Netzwerk, Storage und Pakete. Beispielhaft:
# user-data, stark gekürzt
autoinstall:
version: 1
identity:
hostname: k8s-cp-01
username: ubuntu
password: "$6$REPLACE_WITH_HASH"
ssh:
install-server: true
authorized-keys:
- ssh-ed25519 AAAA... workshop
packages:
- curl
- git
- jq
- vim
late-commands:
- curtin in-target --target=/target -- systemctl enable ssh
26.3. Kickstart für RHEL-kompatible Systeme
text
reboot
network --bootproto=dhcp --device=link --activate
rootpw --iscrypted <HASH>
user --name=admin --groups=wheel --iscrypted --password=<HASH>
%packages
@^minimal-environment
curl
git
jq
vim
%end
%post
systemctl enable sshd
%end
26.4. Metal3 und Cluster API
Metal3 modelliert physische Server als Kubernetes-Ressourcen. Das ist sinnvoll, wenn du Bare-Metal-Cluster deklarativ und wiederholbar über Cluster API verwalten willst.
Typische Bausteine:
- Management-Cluster,
- BareMetalHost-Ressourcen,
- BMC-Zugangsdaten,
- Ironic,
- Image-Server,
- Cluster API Provider Metal3,
- GitOps für Cluster-Definitionen.
Das ist mächtig, aber für einen Einsteigerworkshop meist zu komplex. Für Plattformteams ist es ein gutes Vertiefungsmodul.
27. Beispiel-End-to-End: kubeadm HA auf Ubuntu Bare Metal
Dieses Kapitel fasst den empfohlenen Lernpfad als vollständigen Ablauf zusammen.
27.1. Annahmen
| Wert | Beispiel |
|---|---|
| Cluster | bm-ha |
| Kubernetes-Minor | v1.36 |
| Control Plane | 10.10.20.11-13 |
| Worker | 10.10.20.21-23 |
| API-VIP | 10.10.20.10 |
| API-DNS | k8s-api.lab.local |
| Pod-CIDR | 10.244.0.0/16 |
| Service-CIDR | 10.96.0.0/12 |
| Runtime | containerd |
| CNI | Flannel oder Cilium, je nach Lab-Ziel |
| LoadBalancer | MetalLB L2 |
| Ingress | ingress-nginx |
27.2. Auf allen Nodes: OS vorbereiten
sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
cat <<'EOF' | sudo tee /etc/modules-load.d/kubernetes.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
27.3. Auf allen Nodes: containerd installieren
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl enable --now containerd
sudo systemctl restart containerd
27.4. Auf allen Nodes: kubeadm installieren
export KUBERNETES_MINOR="v1.36"
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/deb/Release.key" \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${KUBERNETES_MINOR}/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet
27.5. Load Balancer vorbereiten
Stelle sicher, dass k8s-api.lab.local auf 10.10.20.10 zeigt und TCP 6443 an alle Control-Plane-Nodes weiterleitet.
getent hosts k8s-api.lab.local
nc -vz k8s-api.lab.local 6443 || true
Vor kubeadm init kann Port 6443 noch geschlossen sein. Nach Start des ersten API Servers muss der Check erfolgreich sein.
27.6. Ersten Control-Plane-Node initialisieren
cat <<'EOF' > kubeadm-ha.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.10.20.11
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: 10.10.20.11
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: stable
clusterName: bm-ha
controlPlaneEndpoint: k8s-api.lab.local:6443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
apiServer:
certSANs:
- k8s-api.lab.local
- 10.10.20.10
- 10.10.20.11
- 10.10.20.12
- 10.10.20.13
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
sudo kubeadm init --config kubeadm-ha.yaml --upload-certs
Kubeconfig:
mkdir -p "$HOME/.kube"
sudo cp -i /etc/kubernetes/admin.conf "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"
kubectl get nodes -o wide
27.7. CNI installieren
Flannel-Beispiel:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl -n kube-flannel get pods -w
Cilium-Beispiel:
cilium install --version <CILIUM_VERSION>
cilium status --wait
27.8. Weitere Control-Plane-Nodes joinen
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH> \
--control-plane \
--certificate-key <CERTIFICATE_KEY>
27.9. Worker joinen
sudo kubeadm join k8s-api.lab.local:6443 \
--token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
27.10. Cluster validieren
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl -n kube-system rollout status deployment coredns
kubectl get --raw='/readyz?verbose'
27.11. MetalLB installieren und testen
kubectl get ns metallb-system || true
# Installation nach Projektstandard: Helm, Kustomize oder Manifeste.
Pool:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lab-pool
namespace: metallb-system
spec:
addresses:
- 10.10.20.200-10.10.20.240
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: lab-l2
namespace: metallb-system
spec:
ipAddressPools:
- lab-pool
Test:
kubectl create deployment nginx --image=nginx:stable
kubectl expose deployment nginx --port=80 --type=LoadBalancer
kubectl get svc nginx -w
curl http://<EXTERNAL_IP>
kubectl delete svc nginx
kubectl delete deployment nginx
28. Validierungskatalog
28.1. Validierungsphasen
flowchart TD
V1[Host validieren] --> V2[Runtime validieren]
V2 --> V3[kubeadm validieren]
V3 --> V4[API validieren]
V4 --> V5[CNI validieren]
V5 --> V6[DNS validieren]
V6 --> V7[Service validieren]
V7 --> V8[LoadBalancer validieren]
V8 --> V9[Storage validieren]
V9 --> V10[Security validieren]
V10 --> V11[Upgrade- und Rückbaupfad validieren]
28.2. Host
hostnamectl
uname -a
ip addr
ip route
timedatectl
free -h
swapon --show
lsmod | grep -E 'overlay|br_netfilter'
sysctl net.ipv4.ip_forward
28.3. Runtime
sudo systemctl status containerd --no-pager || true
sudo systemctl status crio --no-pager || true
sudo crictl info
sudo crictl ps -a
sudo crictl images
28.4. Kubernetes-Komponenten
kubeadm version
kubelet --version
kubectl version --client
sudo systemctl status kubelet --no-pager
sudo journalctl -u kubelet -n 100 --no-pager
28.5. API und Nodes
kubectl cluster-info
kubectl get nodes -o wide
kubectl describe node <NODE>
kubectl get componentstatuses 2>/dev/null || true
kubectl get --raw='/readyz?verbose'
kubectl get --raw='/livez?verbose'
28.6. Control Plane Pods
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get pods -l component=kube-apiserver -o wide
kubectl -n kube-system get pods -l component=kube-controller-manager -o wide
kubectl -n kube-system get pods -l component=kube-scheduler -o wide
kubectl -n kube-system get pods -l component=etcd -o wide
28.7. DNS
kubectl run dns-test --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/dns-test --timeout=120s
kubectl exec dns-test -- nslookup kubernetes.default.svc.cluster.local
kubectl exec dns-test -- nslookup kube-dns.kube-system.svc.cluster.local
kubectl delete pod dns-test
28.8. Service Routing
kubectl create namespace svc-test
kubectl create deployment echo -n svc-test --image=registry.k8s.io/e2e-test-images/agnhost:2.53 -- /agnhost netexec --http-port=8080
kubectl expose deployment echo -n svc-test --port=80 --target-port=8080
kubectl run curl -n svc-test --rm -it --image=curlimages/curl --restart=Never -- curl -sS http://echo
kubectl delete namespace svc-test
28.9. LoadBalancer
kubectl create deployment lb-smoke --image=nginx:stable
kubectl expose deployment lb-smoke --port=80 --type=LoadBalancer
kubectl get svc lb-smoke -w
curl http://<EXTERNAL_IP>
kubectl delete svc lb-smoke
kubectl delete deployment lb-smoke
28.10. Storage
kubectl get storageclass
kubectl get pv,pvc -A
kubectl get volumeattachments -A 2>/dev/null || true
28.11. Security
kubectl auth can-i get pods -A
kubectl get serviceaccounts -A
kubectl get roles,rolebindings -A
kubectl get clusterroles,clusterrolebindings
kubectl get ns --show-labels | grep pod-security || true
29. Troubleshooting
29.1. kubelet startet nicht
Symptome:
- Node erscheint nicht im Cluster.
systemctl status kubeletzeigt Fehler.journalctl -u kubeletmeldet Runtime-, cgroup- oder Swap-Probleme.
Prüfung:
sudo journalctl -u kubelet -n 200 --no-pager
sudo crictl info
swapon --show
cat /var/lib/kubelet/config.yaml 2>/dev/null || true
Typische Ursachen:
- Swap aktiv.
- Runtime läuft nicht.
- cgroup-Treiber von Runtime und kubelet passen nicht zusammen.
- falsche Node-IP.
- Firewall blockiert kubelet oder API.
- Zertifikat oder Bootstrap-Token ungültig.
29.2. Node bleibt NotReady
kubectl describe node <NODE>
kubectl get pods -A -o wide
kubectl -n kube-system get daemonsets
Typische Ursachen:
- CNI nicht installiert.
- CNI-Pods crashen.
- Pod-CIDR fehlt oder passt nicht.
- Runtime kann Images nicht ziehen.
- DNS oder Registry nicht erreichbar.
- Kernelmodule oder sysctl fehlen.
29.3. CoreDNS startet nicht
kubectl -n kube-system describe deployment coredns
kubectl -n kube-system logs deployment/coredns
kubectl get nodes -o wide
Typische Ursachen:
- CNI fehlt.
- Nodes sind
NotReady. - Upstream-DNS im Host ist falsch.
- NetworkPolicy blockiert DNS.
29.4. Pods hängen in Pending
kubectl describe pod <POD> -n <NAMESPACE>
kubectl get events -n <NAMESPACE> --sort-by=.lastTimestamp
kubectl describe nodes | grep -A5 -E 'Allocated resources|Taints'
Typische Ursachen:
- keine freien Ressourcen,
- Taints ohne passende Tolerations,
- PVC nicht gebunden,
- NodeSelector/Affinity passt nicht,
- Image-Architektur passt nicht zur Node-Architektur.
29.5. ImagePullBackOff
kubectl describe pod <POD> -n <NAMESPACE>
sudo crictl pull <IMAGE>
Typische Ursachen:
- Registry nicht erreichbar,
- falscher Image-Name oder Tag,
- private Registry ohne Pull Secret,
- TLS-CA fehlt,
- Air-Gap-Mirror unvollständig.
29.6. Service hat keine Endpoints
kubectl get svc,endpoints,endpointslices -n <NAMESPACE>
kubectl get pods -n <NAMESPACE> --show-labels
kubectl describe svc <SERVICE> -n <NAMESPACE>
Typische Ursachen:
- Service-Selector passt nicht zu Pod-Labels,
- Pods sind nicht Ready,
- falscher Namespace,
- Readiness Probe schlägt fehl.
29.7. MetalLB vergibt keine IP
kubectl -n metallb-system get pods
kubectl -n metallb-system get ipaddresspools,l2advertisements,bgppeers,bgpadvertisements
kubectl describe svc <SERVICE> -n <NAMESPACE>
Typische Ursachen:
- IP-Pool fehlt,
- IP-Pool überschneidet sich oder ist nicht im richtigen Netz,
- L2/BGP-Modus passt nicht zur Netzumgebung,
- Speaker-Pods laufen nicht,
- Firewall blockiert Memberlist oder BGP.
29.8. HA API nicht erreichbar
getent hosts k8s-api.lab.local
nc -vz k8s-api.lab.local 6443
curl -k https://k8s-api.lab.local:6443/readyz
sudo journalctl -u haproxy -n 100 --no-pager || true
sudo journalctl -u keepalived -n 100 --no-pager || true
Typische Ursachen:
- VIP liegt nicht auf aktivem Load Balancer,
- HAProxy Backend zeigt auf falsche IPs,
- API Server läuft noch nicht,
- Zertifikats-SANs enthalten den API-Namen nicht,
- Firewall blockiert TCP 6443,
- DNS zeigt auf falsche IP.
29.9. etcd-Probleme
kubectl -n kube-system get pods -l component=etcd -o wide
kubectl -n kube-system logs <ETCD_POD>
sudo crictl ps | grep etcd
Mit lokalem etcdctl auf einem Control-Plane-Node:
sudo ETCDCTL_API=3 etcdctl endpoint health \
--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
Typische Ursachen:
- Zeitdrift,
- Disk-Latenz,
- Zertifikatsfehler,
- Peer-Port blockiert,
- Quorum verloren,
- falsche advertise URLs.
29.10. MTU-Probleme
Symptome:
- kleine Requests funktionieren,
- größere Transfers hängen,
- DNS wirkt instabil,
- Overlay-CNI hat Paketverlust.
Prüfung:
ip link
tracepath <ZIEL_IP>
ping -M do -s 1472 <ZIEL_IP>
Reduziere CNI-MTU nur bewusst und konsistent. Prüfe Underlay, VLANs, Bonding, VXLAN/Geneve und WireGuard/IPsec-Overhead.
30. Upgrade und Wartung
30.1. Wartungsgrundsätze
- Upgrade nie ohne etcd-Backup.
- Minor-Versionen nicht unkontrolliert überspringen.
- Erst Control Plane, dann Worker.
- Pro Node drainen, upgraden, validieren, uncordon.
- Add-ons auf Kompatibilität prüfen.
- CNI, CSI und Ingress separat planen.
- Paketquellen pro Kubernetes-Minor-Version kontrollieren.
30.2. etcd-Backup
Auf einem Control-Plane-Node mit gestapeltem etcd:
sudo ETCDCTL_API=3 etcdctl snapshot save /var/backups/etcd-snapshot-$(date +%F-%H%M).db \
--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
sudo ETCDCTL_API=3 etcdctl snapshot status /var/backups/etcd-snapshot-*.db -w table
30.3. Node drainen
kubectl cordon <NODE>
kubectl drain <NODE> --ignore-daemonsets --delete-emptydir-data
# Wartung durchführen
kubectl uncordon <NODE>
30.4. kubeadm-Upgrade grob
Auf erstem Control-Plane-Node:
sudo apt-mark unhold kubeadm || true
sudo apt-get update
sudo apt-get install -y kubeadm=<VERSION>
sudo apt-mark hold kubeadm
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply <VERSION>
Danach kubelet/kubectl:
sudo apt-mark unhold kubelet kubectl || true
sudo apt-get install -y kubelet=<VERSION> kubectl=<VERSION>
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet
Auf weiteren Control-Plane-Nodes nutzt du typischerweise kubeadm upgrade node, danach kubelet/kubectl aktualisieren.
30.5. Zertifikate prüfen
sudo kubeadm certs check-expiration
Erneuern:
sudo kubeadm certs renew all
sudo systemctl restart kubelet
Plane Zertifikatserneuerung mit Wartungsfenster und Backup.
31. Rückbau
Rückbau ist destruktiv. Sichere vorher alles, was du behalten willst.
31.1. Workloads entfernen
kubectl get ns
kubectl delete namespace <NAMESPACE>
31.2. Node aus Cluster entfernen
Auf Admin-Seite:
kubectl drain <NODE> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <NODE>
Auf dem Node:
sudo kubeadm reset
sudo systemctl stop kubelet
sudo systemctl stop containerd || true
sudo systemctl stop crio || true
31.3. CNI- und kubeadm-Reste löschen
sudo rm -rf /etc/cni/net.d
sudo rm -rf /var/lib/cni
sudo rm -rf /var/lib/kubelet
sudo rm -rf /etc/kubernetes
sudo rm -rf /var/lib/etcd
sudo iptables -F || true
sudo iptables -t nat -F || true
sudo iptables -t mangle -F || true
sudo ipvsadm --clear || true
Achtung: Pauschales Leeren von iptables/ipvs kann andere Dienste auf dem Host beeinflussen. Auf gemeinsam genutzten Hosts ist das nicht zulässig.
31.4. Pakete entfernen
Ubuntu/Debian:
sudo apt-mark unhold kubelet kubeadm kubectl || true
sudo apt-get purge -y kubelet kubeadm kubectl kubernetes-cni || true
sudo apt-get autoremove -y
RHEL/Rocky/Alma:
sudo yum remove -y kubelet kubeadm kubectl kubernetes-cni || true
31.5. k3s/RKE2/k0s/Talos Rückbau
k3s:
sudo /usr/local/bin/k3s-uninstall.sh || true
sudo /usr/local/bin/k3s-agent-uninstall.sh || true
RKE2:
sudo /usr/local/bin/rke2-uninstall.sh || true
sudo /usr/local/bin/rke2-agent-uninstall.sh || true
k0s:
sudo k0s stop || true
sudo k0s reset || true
Talos:
talosctl reset --nodes <NODE_IP> --endpoints <ENDPOINT_IP> --graceful=false --reboot
32. Workshop-Labs
32.1. Lab 1: Bare-Metal-Preflight
Ziel: Du prüfst, ob ein physischer Host Kubernetes-tauglich ist.
Aufgaben:
- Hostname setzen.
- IP, Route und DNS prüfen.
- Swap deaktivieren.
- Kernelmodule laden.
- sysctl setzen.
- Runtime installieren.
crictl infoausführen.
Abnahme:
swapon --show
lsmod | grep br_netfilter
sysctl net.ipv4.ip_forward
sudo crictl info
32.2. Lab 2: Single-Node kubeadm
Ziel: Du baust einen einzelnen Kubernetes-Node mit kubeadm.
Aufgaben:
kubeadminstallieren.- kubeadm-Konfiguration schreiben.
kubeadm initausführen.- Kubeconfig setzen.
- Control-Plane-Taint entfernen.
- CNI installieren.
- Smoke-Test ausführen.
Abnahme:
kubectl get nodes
kubectl get pods -A
kubectl run test --image=busybox:1.36 --restart=Never -- echo ok
32.3. Lab 3: Multi-Node Join
Ziel: Du fügst Worker-Nodes hinzu.
Aufgaben:
- Worker vorbereiten.
- Join-Command erzeugen.
- Worker joinen.
- Labels setzen.
- Deployment mit drei Replikas verteilen.
Abnahme:
kubectl get nodes -o wide
kubectl create deployment spread --image=nginx:stable --replicas=3
kubectl get pods -o wide
32.4. Lab 4: MetalLB und Ingress
Ziel: Du stellst Services mit externer IP bereit.
Aufgaben:
- MetalLB installieren.
- IPAddressPool anlegen.
- LoadBalancer-Service testen.
- Ingress Controller installieren.
- Beispiel-Ingress erstellen.
Abnahme:
kubectl get svc -A | grep LoadBalancer
curl http://<EXTERNAL_IP>
32.5. Lab 5: Storage
Ziel: Du validierst dynamische oder lokale Storage-Provisionierung.
Aufgaben:
- StorageClass prüfen.
- PVC erstellen.
- Pod mit Volume starten.
- Datei schreiben.
- Pod löschen und Verhalten prüfen.
Abnahme:
kubectl get pvc,pv
kubectl exec storage-smoke -- cat /data/test.txt
32.6. Lab 6: HA-Ausbau
Ziel: Du erweiterst einen Cluster auf mehrere Control-Plane-Nodes.
Aufgaben:
- API-Endpunkt bereitstellen.
kubeadm init --upload-certsausführen.- weitere Control-Plane-Nodes joinen.
- CoreDNS neu verteilen.
- API-Ausfall eines Control-Plane-Nodes simulieren.
Abnahme:
kubectl get nodes
kubectl -n kube-system get pods -l component=kube-apiserver -o wide
kubectl get --raw='/readyz?verbose'
33. Betriebsreife-Checkliste
Ein Bare-Metal-Kubernetes-Cluster ist erst betriebsreif, wenn diese Punkte erfüllt sind.
33.1. Architektur
- Zielarchitektur dokumentiert.
- Node-Rollen dokumentiert.
- Netzwerk- und CIDR-Plan dokumentiert.
- API-Endpunkt stabil.
- CNI- und CSI-Entscheidung dokumentiert.
- LoadBalancer-Modell dokumentiert.
- Ingress/Gateway-Modell dokumentiert.
33.2. Reproduzierbarkeit
- Betriebssysteminstallation ist automatisiert oder dokumentiert.
- Paketquellen sind versioniert.
- Kubernetes-Minor-Version ist festgelegt.
- kubeadm-Konfiguration liegt in Git.
- Add-ons werden deklarativ installiert.
- Manuelle Schritte sind begründet.
33.3. Security
- SSH gehärtet.
- BMC-Netz getrennt.
- RBAC Least Privilege.
admin.confnicht geteilt.- Pod Security Labels gesetzt.
- Secrets-Verschlüsselung bewertet.
- NetworkPolicies definiert.
- Registry- und Image-Policy bewertet.
33.4. Betrieb
- etcd-Backups vorhanden.
- Restore getestet.
- Upgrade-Verfahren dokumentiert.
- Monitoring installiert.
- Logs zugänglich.
- Alerting definiert.
- Kapazität beobachtbar.
- Rückbau dokumentiert.
33.5. Validierung
- Node-Health geprüft.
- CNI geprüft.
- DNS geprüft.
- Service-Routing geprüft.
- LoadBalancer geprüft.
- Ingress/Gateway geprüft.
- Storage geprüft.
- Security-Baseline geprüft.
34. Typische Anti-Patterns
| Anti-Pattern | Warum problematisch | Besser |
|---|---|---|
kubeadm init ohne Plan für CIDRs |
spätere Änderung ist schwierig | CIDRs vorher festlegen |
| Single-Control-Plane als „produktiv“ deklarieren | Control Plane ist SPOF | HA oder klares Lab-Label |
| CNI nach Bauchgefühl installieren | Pod-Netz kann nicht funktionieren | CNI-Anforderungen vorher prüfen |
| Docker Engine als Runtime erwarten | Kubernetes nutzt CRI | containerd oder CRI-O verwenden |
| LoadBalancer-Services ohne MetalLB/kube-vip erwarten | Bare Metal liefert das nicht automatisch | LB-Komponente installieren |
admin.conf verteilen |
Cluster-Admin-Zugriff unkontrolliert | Nutzer/RBAC/OIDC |
Storage über hostPath produktiv nutzen |
unsicher und nodegebunden | CSI/Local PV bewusst wählen |
| Firewall komplett deaktivieren und vergessen | Sicherheitslücke | Regeln dokumentiert setzen |
| Air-Gap ohne vollständige Image-Liste | Installation bricht mitten drin ab | Images vorher spiegeln |
| Upgrade ohne etcd-Backup | hohes Wiederherstellungsrisiko | Backup und Restore-Test |
35. Kommandoreferenz für Provisionierung
35.1. Preflight
hostnamectl
ip addr
ip route
getent hosts <NAME>
timedatectl
free -h
swapon --show
lsblk
findmnt
sudo cat /sys/class/dmi/id/product_uuid || true
35.2. Runtime
sudo systemctl status containerd --no-pager
sudo systemctl restart containerd
sudo crictl info
sudo crictl ps -a
sudo crictl logs <CONTAINER_ID>
sudo ctr namespaces list
35.3. kubeadm
kubeadm version
sudo kubeadm config images list
sudo kubeadm config images pull
sudo kubeadm init --config kubeadm.yaml --upload-certs
kubeadm token create --print-join-command
sudo kubeadm join <ENDPOINT>:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>
sudo kubeadm reset
sudo kubeadm certs check-expiration
35.4. kubectl Basis
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl describe node <NODE>
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl get --raw='/readyz?verbose'
35.5. Netzwerk
kubectl get svc,endpoints,endpointslices -A
kubectl get ingress -A
kubectl get gatewayclass,gateway,httproute -A 2>/dev/null || true
kubectl get networkpolicy -A
ss -tulpn
nc -vz <HOST> <PORT>
tracepath <IP>
35.6. Storage
kubectl get storageclass
kubectl get pv,pvc -A
kubectl describe pvc <PVC> -n <NAMESPACE>
kubectl get volumeattachments -A 2>/dev/null || true
lsblk
findmnt
35.7. Logs
sudo journalctl -u kubelet -f
sudo journalctl -u containerd -f
sudo journalctl -u crio -f
kubectl logs <POD> -n <NAMESPACE>
kubectl logs <POD> -n <NAMESPACE> --previous
kubectl logs deployment/<DEPLOYMENT> -n <NAMESPACE> --all-containers
36. Empfohlene Repository-Struktur
kubernetes-workshop/
docs/
environments/
environment-native-baremetal-kubernetes-DE.md
architecture-kubernetes-DE.md
concepts-kubernetes-DE.md
commands-kubernetes-DE.md
best-practices-kubernetes-DE.md
ansible/
inventory.ini
group_vars/
playbooks/
roles/
clusters/
bm-single/
kubeadm.yaml
cni/
metallb/
ingress/
storage/
bm-ha/
kubeadm-ha.yaml
loadbalancer/
addons/
scripts/
preflight-baremetal.sh
validate-cluster.sh
reset-node.sh
examples/
smoke/
network/
storage/
security/
37. Beispielskripte
37.1. preflight-baremetal.sh
#!/usr/bin/env bash
set -euo pipefail
echo "== Host =="
hostnamectl || true
uname -a
echo "== Network =="
ip addr
ip route
echo "== Time =="
timedatectl || true
echo "== Swap =="
swapon --show || true
free -h
echo "== Kernel modules =="
lsmod | grep -E 'overlay|br_netfilter' || true
echo "== sysctl =="
sysctl net.bridge.bridge-nf-call-iptables || true
sysctl net.ipv4.ip_forward || true
echo "== Runtime =="
systemctl is-active containerd || true
systemctl is-active crio || true
crictl info || true
37.2. validate-cluster.sh
#!/usr/bin/env bash
set -euo pipefail
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get --raw='/readyz?verbose'
kubectl run validation-dns --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/validation-dns --timeout=120s
kubectl exec validation-dns -- nslookup kubernetes.default.svc.cluster.local
kubectl delete pod validation-dns
37.3. reset-node.sh
#!/usr/bin/env bash
set -euo pipefail
read -r -p "Dieser Vorgang löscht Kubernetes-Zustand auf diesem Node. Fortfahren? [yes/NO] " answer
if [[ "${answer}" != "yes" ]]; then
echo "Abbruch."
exit 1
fi
sudo kubeadm reset -f || true
sudo systemctl stop kubelet || true
sudo systemctl stop containerd || true
sudo systemctl stop crio || true
sudo rm -rf /etc/cni/net.d /var/lib/cni /var/lib/kubelet /etc/kubernetes
sudo rm -rf /var/lib/etcd
38. Quellen und weiterführende Referenzen
Primäre Referenzen:
- Kubernetes Dokumentation: Getting started — https://kubernetes.io/docs/setup/
- Kubernetes Dokumentation: Installing kubeadm — https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
- Kubernetes Dokumentation: Creating a cluster with kubeadm — https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
- Kubernetes Dokumentation: Creating Highly Available Clusters with kubeadm — https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/
- Kubernetes Dokumentation: Container Runtimes — https://kubernetes.io/docs/setup/production-environment/container-runtimes/
- MetalLB Dokumentation — https://metallb.io/
- Cilium Dokumentation — https://docs.cilium.io/
- k3s Dokumentation — https://docs.k3s.io/
- RKE2 Dokumentation — https://docs.rke2.io/
- Talos Dokumentation — https://docs.siderolabs.com/talos/
- k0s Dokumentation — https://docs.k0sproject.io/
39. Abschluss
Bare-Metal-Kubernetes ist beherrschbar, wenn du es als Kette von kontrollierten Entscheidungen behandelst:
- Hardware und Firmware vorbereiten.
- Netzwerk, CIDRs, DNS, VIPs und Ports planen.
- Betriebssystem reproduzierbar installieren.
- Runtime korrekt konfigurieren.
- Kubernetes-Bootstrap deterministisch ausführen.
- CNI, LoadBalancer, Ingress, Storage und Add-ons bewusst auswählen.
- Validierung automatisieren.
- Backup, Upgrade, Security und Rückbau dokumentieren.
Wenn du diese Kette sauber abarbeitest, ist Bare Metal nicht „magisch“, sondern eine transparente, gut kontrollierbare Kubernetes-Plattform.