Workshop wiki · German

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…

Workshop examples as ZIP files
Learning progress in the web app

Mark chapters as read, view reading progress and check off practice modules after enabling the optional web app. Workshop progress is calculated from chapters across all document pages. Your choices stay on this device; there is no account or server sync. Installation is not required. Withdrawing your choice in Privacy settings deletes the progress.

Reading progress0%
0%
0 / 39 chapters read
Other language versionEnglish ↗

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.

01 / 39

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]
02 / 39

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,
  • containerd oder CRI-O als Runtime,
  • kubeadm fü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.
03 / 39

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.
04 / 39

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
05 / 39

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.
06 / 39

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:

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
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
07 / 39

8. Betriebssystem-Baseline

8.1. Gemeinsame Linux-Vorbereitung

Diese Schritte gelten für klassische Linux-Hosts mit kubeadm, k3s, RKE2 oder k0s.

bash
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:

bash
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.

bash
sudo swapoff -a
sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
free -h

8.3. Kernel-Module und sysctl

bash
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:

bash
lsmod | grep -E 'overlay|br_netfilter'
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward

8.4. Paketbasis

Ubuntu/Debian:

bash
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:

bash
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:

bash
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.

bash
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:

bash
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:

bash
sudo ufw status verbose
# Nur im Lab, wenn du die Wirkung verstehst:
# sudo ufw disable

RHEL/Rocky mit firewalld:

bash
sudo firewall-cmd --state || true
sudo firewall-cmd --list-all || true

Beispielhafte Freigaben für Control Plane:

bash
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
08 / 39

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:

bash
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:

bash
sudo ctr version
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock info || true

9.3. containerd auf RHEL/Rocky/Alma/Fedora

bash
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.

bash
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:

bash
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:

bash
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:

bash
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:

bash
sudo crictl info
sudo crictl images

9.6. Registry Mirrors und private Registries

Beispiel für containerd mit internem Mirror:

toml
# /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:

bash
sudo systemctl restart containerd
sudo crictl pull registry.k8s.io/pause:3.10 || true
09 / 39

10. kubeadm installieren

10.1. Version festlegen

Lege die Kubernetes-Minor-Version zentral fest:

bash
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

bash
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

bash
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.

bash
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

bash
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.

10 / 39

11. kubeadm: Single-Node-Cluster

11.1. Konfigurationsdatei erstellen

Für ein reproduzierbares Setup nutzt du eine kubeadm-Konfigurationsdatei.

bash
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

bash
sudo kubeadm init --config ~/kubeadm/kubeadm-single-node.yaml

Kubeconfig einrichten:

bash
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:

bash
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:

bash
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

Beispiel mit Cilium CLI:

bash
cilium install --version <CILIUM_VERSION>
cilium status --wait

Beispiel mit Calico:

bash
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

bash
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
11 / 39

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.

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-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:

bash
sudo kubeadm init --config kubeadm-multi.yaml

12.2. Join-Command erzeugen

Wenn du den Join-Befehl nicht gespeichert hast:

bash
kubeadm token create --print-join-command

Beispiel:

bash
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:

bash
sudo kubeadm join k8s-api.lab.local:6443 \
  --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<HASH>

Danach auf deinem Admin-Client:

bash
kubectl get nodes -o wide
kubectl get pods -A -o wide

12.4. Labels setzen

bash
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

bash
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
12 / 39

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:

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:

bash
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:

cfg
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.

bash
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:

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
controllerManager: {}
scheduler: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd

Initialisierung:

bash
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:

bash
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:

bash
kubectl get nodes -o wide
kubectl -n kube-system get pods -o wide
kubectl get --raw='/readyz?verbose'

13.8. Worker joinen

bash
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.

bash
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
13 / 39

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

yaml
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
14 / 39

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.

bash
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.

bash
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:

bash
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

bash
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
15 / 39

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.

yaml
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:

bash
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:

yaml
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:

bash
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:

bash
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:

bash
kubectl get gatewayclass
kubectl get gateways -A
kubectl get httproutes -A
16 / 39

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

bash
kubectl get storageclass
kubectl get csidrivers
kubectl get pv,pvc -A

PVC-Test:

yaml
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:

bash
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, ReadWriteMany oder ReadOnlyMany?
  • 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?
17 / 39

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

bash
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

bash
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:

bash
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:

  1. Cluster-Basis mit kubeadm oder Distribution bereitstellen.
  2. CNI installieren.
  3. LoadBalancer-Funktion bereitstellen.
  4. GitOps-Controller installieren.
  5. Add-on-Repositories anbinden.
  6. Policies und Applikationen aus Git synchronisieren.

Argo CD Beispiel:

bash
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:

bash
flux check --pre
flux bootstrap git \
  --url=ssh://git@example.local/platform/kubernetes.git \
  --branch=main \
  --path=clusters/bm-lab-01
18 / 39

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:

bash
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.conf nicht teilen.
  • Nutzerzugriff über eigene Zertifikate, OIDC oder externe Identität lösen.
  • RBAC mit Least Privilege erstellen.
  • system:masters nicht für Alltagszugriff verwenden.
  • Audit Logging einplanen.
  • Admission Policies verwenden.

Prüfung:

bash
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:

bash
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:

yaml
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.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

DNS-Ausnahme:

yaml
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
19 / 39

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

bash
kubeadm config images list --kubernetes-version <VERSION>

Images vorab pullen:

bash
sudo kubeadm config images pull --kubernetes-version <VERSION> --cri-socket unix:///run/containerd/containerd.sock

Mit alternativer Registry:

bash
sudo kubeadm config images pull \
  --kubernetes-version <VERSION> \
  --image-repository registry.lab.local/kubernetes

20.4. Image-Rewrite in kubeadm

yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.0
imageRepository: registry.lab.local/kubernetes

20.5. Lokale Registry testen

bash
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.
20 / 39

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

bash
curl -sfL https://get.k3s.io | sh -

sudo k3s kubectl get nodes
sudo cat /etc/rancher/k3s/k3s.yaml

Kubeconfig für deinen Benutzer:

bash
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

bash
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:

bash
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:

bash
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:

bash
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

bash
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
21 / 39

22. RKE2 auf Bare Metal

RKE2 ist eine Kubernetes-Distribution von Rancher/SUSE mit Fokus auf Security und Enterprise-Umgebungen.

22.1. Erster Server

bash
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:

bash
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

bash
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

bash
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

bash
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
22 / 39

23. k0s auf Bare Metal

k0s bietet ein einfaches Controller-/Worker-Modell und bringt viele Komponenten gebündelt mit.

23.1. Single-Node k0s

bash
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:

bash
mkdir -p ~/.kube
sudo k0s kubeconfig admin > ~/.kube/config
chmod 600 ~/.kube/config
kubectl get nodes

23.2. Multi-Node k0s

Controller installieren:

bash
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:

bash
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

bash
sudo k0s status
sudo k0s kubectl get nodes -o wide
sudo k0s kubectl get pods -A -o wide
23 / 39

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

bash
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:

bash
talosctl apply-config \
  --insecure \
  --nodes 10.10.20.11 \
  --file ./talos-out/controlplane.yaml

Worker:

bash
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:

bash
talosctl bootstrap \
  --nodes 10.10.20.11 \
  --endpoints 10.10.20.11 \
  --talosconfig ./talos-out/talosconfig

Kubeconfig holen:

bash
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

bash
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
24 / 39

25. Automatisierung mit Ansible

Für wiederholbare Bare-Metal-Provisionierung solltest du manuelle Kommandos in Rollen überführen.

25.1. Inventar

ini
[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

text
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

yaml
- 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-certs nur kurz verwenden.
  • Secrets in Ansible Vault, SOPS oder einem Secret Manager verwalten.
25 / 39

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:

yaml
# 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

cfg
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.

26 / 39

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

bash
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

bash
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

bash
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.

bash
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

bash
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:

bash
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:

bash
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl -n kube-flannel get pods -w

Cilium-Beispiel:

bash
cilium install --version <CILIUM_VERSION>
cilium status --wait

27.8. Weitere Control-Plane-Nodes joinen

bash
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

bash
sudo kubeadm join k8s-api.lab.local:6443 \
  --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<HASH>

27.10. Cluster validieren

bash
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

bash
kubectl get ns metallb-system || true
# Installation nach Projektstandard: Helm, Kustomize oder Manifeste.

Pool:

yaml
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:

bash
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
27 / 39

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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
kubectl get storageclass
kubectl get pv,pvc -A
kubectl get volumeattachments -A 2>/dev/null || true

28.11. Security

bash
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
28 / 39

29. Troubleshooting

29.1. kubelet startet nicht

Symptome:

  • Node erscheint nicht im Cluster.
  • systemctl status kubelet zeigt Fehler.
  • journalctl -u kubelet meldet Runtime-, cgroup- oder Swap-Probleme.

Prüfung:

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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:

bash
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:

bash
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.

29 / 39

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:

bash
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

bash
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:

bash
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:

bash
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

bash
sudo kubeadm certs check-expiration

Erneuern:

bash
sudo kubeadm certs renew all
sudo systemctl restart kubelet

Plane Zertifikatserneuerung mit Wartungsfenster und Backup.

30 / 39

31. Rückbau

Rückbau ist destruktiv. Sichere vorher alles, was du behalten willst.

31.1. Workloads entfernen

bash
kubectl get ns
kubectl delete namespace <NAMESPACE>

31.2. Node aus Cluster entfernen

Auf Admin-Seite:

bash
kubectl drain <NODE> --ignore-daemonsets --delete-emptydir-data
kubectl delete node <NODE>

Auf dem Node:

bash
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

bash
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:

bash
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:

bash
sudo yum remove -y kubelet kubeadm kubectl kubernetes-cni || true

31.5. k3s/RKE2/k0s/Talos Rückbau

k3s:

bash
sudo /usr/local/bin/k3s-uninstall.sh || true
sudo /usr/local/bin/k3s-agent-uninstall.sh || true

RKE2:

bash
sudo /usr/local/bin/rke2-uninstall.sh || true
sudo /usr/local/bin/rke2-agent-uninstall.sh || true

k0s:

bash
sudo k0s stop || true
sudo k0s reset || true

Talos:

bash
talosctl reset --nodes <NODE_IP> --endpoints <ENDPOINT_IP> --graceful=false --reboot
31 / 39

32. Workshop-Labs

32.1. Lab 1: Bare-Metal-Preflight

Ziel: Du prüfst, ob ein physischer Host Kubernetes-tauglich ist.

Aufgaben:

  1. Hostname setzen.
  2. IP, Route und DNS prüfen.
  3. Swap deaktivieren.
  4. Kernelmodule laden.
  5. sysctl setzen.
  6. Runtime installieren.
  7. crictl info ausführen.

Abnahme:

bash
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:

  1. kubeadm installieren.
  2. kubeadm-Konfiguration schreiben.
  3. kubeadm init ausführen.
  4. Kubeconfig setzen.
  5. Control-Plane-Taint entfernen.
  6. CNI installieren.
  7. Smoke-Test ausführen.

Abnahme:

bash
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:

  1. Worker vorbereiten.
  2. Join-Command erzeugen.
  3. Worker joinen.
  4. Labels setzen.
  5. Deployment mit drei Replikas verteilen.

Abnahme:

bash
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:

  1. MetalLB installieren.
  2. IPAddressPool anlegen.
  3. LoadBalancer-Service testen.
  4. Ingress Controller installieren.
  5. Beispiel-Ingress erstellen.

Abnahme:

bash
kubectl get svc -A | grep LoadBalancer
curl http://<EXTERNAL_IP>

32.5. Lab 5: Storage

Ziel: Du validierst dynamische oder lokale Storage-Provisionierung.

Aufgaben:

  1. StorageClass prüfen.
  2. PVC erstellen.
  3. Pod mit Volume starten.
  4. Datei schreiben.
  5. Pod löschen und Verhalten prüfen.

Abnahme:

bash
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:

  1. API-Endpunkt bereitstellen.
  2. kubeadm init --upload-certs ausführen.
  3. weitere Control-Plane-Nodes joinen.
  4. CoreDNS neu verteilen.
  5. API-Ausfall eines Control-Plane-Nodes simulieren.

Abnahme:

bash
kubectl get nodes
kubectl -n kube-system get pods -l component=kube-apiserver -o wide
kubectl get --raw='/readyz?verbose'
32 / 39

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.conf nicht 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.
33 / 39

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
34 / 39

35. Kommandoreferenz für Provisionierung

35.1. Preflight

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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
35 / 39

36. Empfohlene Repository-Struktur

text
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/
36 / 39

37. Beispielskripte

37.1. preflight-baremetal.sh

bash
#!/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

bash
#!/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

bash
#!/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
37 / 39

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/
38 / 39

39. Abschluss

Bare-Metal-Kubernetes ist beherrschbar, wenn du es als Kette von kontrollierten Entscheidungen behandelst:

  1. Hardware und Firmware vorbereiten.
  2. Netzwerk, CIDRs, DNS, VIPs und Ports planen.
  3. Betriebssystem reproduzierbar installieren.
  4. Runtime korrekt konfigurieren.
  5. Kubernetes-Bootstrap deterministisch ausführen.
  6. CNI, LoadBalancer, Ingress, Storage und Add-ons bewusst auswählen.
  7. Validierung automatisieren.
  8. 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.

39 / 39

K8S · Orthevs Academy

31 modules as one continuous learning path.

The sequence remains unchanged: from the lab skeleton through technical foundations and operating models to final troubleshooting. The technical directory names are deliberately preserved.

Orthevs – Engineering the foundation for what comes next.

Orthevs as an app

Install Orthevs

The Orthevs web app can be installed on supported mobile devices and desktop browsers.

iPhone and iPad

Open Orthevs in Safari. Tap Share, then Add to Home Screen. Enable Open as Web App if offered, then tap Add.

Android

For the web app, open the browser menu and choose Install app or Add to Home screen. Confirm the installation.

Desktop browser

Open the browser menu or the install icon in the address bar, then choose Install app or Add to Dock.

Web app installation depends on your browser. If no install prompt appears, follow the browser steps shown above.