Dieser Blogbeitrag zeigt, wie man einen Kubernetes-Cluster auf Hetzner-Cloud-Servern aufsetzt. Der resultierende Cluster ist (noch) nicht vollständig, unterstützt aber bereits Persistent Volumes (Hetzner Block Storage Volumes). Ein Folgebeitrag beschreibt dann, wie wir einen (nginx-)Ingress und LoadBalancer einrichten (den Hetzner noch nicht anbietet).
Ich habe das nicht komplett allein gemacht, sondern konnte mich auf ein paar grossartige Quellen stützen:
- Christian Beneke: https://community.hetzner.com/tutorials/install-kubernetes-cluster
- blinkeye: https://blinkeye.github.io/post/public/2019-07-25-hetzner-k8s-with-private-network/
- Kubernetes-Dokumentation (die sehr gut ist!): https://kubernetes.io/docs/home/
Ich beschreibe die Schritte mit Fedora 31 als Betriebssystem für die Server. Warum Fedora? Mit Ubuntu bin ich nicht mehr so zufrieden wie noch vor ein paar Jahren. Warum also nicht eine andere grosse Distro ausprobieren und ihre Höhen und Tiefen kennenlernen?
Was wir tun #
- Kubernetes auf einem Setup mit einem Master und 2 Worker-Nodes aufsetzen, das etwa 13,24 € im Monat kostet (ohne Storage). Storage schlägt mit zusätzlich 4,76 €/100 GB pro Monat zu Buche
- Die API-Kommunikation läuft über ein privates Netzwerk und nicht über das Internet
- Du kannst Volumes auf Basis von Hetzner Cloud Block Storage Volumes für Datenspeicherung provisionieren
Was wir (noch) nicht tun #
- Einen Ingress einrichten, um Websites über eine öffentliche IPv4 auszuliefern
- Einen LoadBalancer einrichten (für Ingress erforderlich)
- Eine Backup-Strategie für die im Cluster und auf den Volumes gespeicherten Daten einrichten
Über diese Themen schreibe ich in ein paar Folgebeiträgen – schau also in ein paar Tagen/Wochen wieder auf meinem Blog vorbei.
Voraussetzungen #
- Grundlegendes Linux-Know-how, besonders Shell-Befehle
- Ein Hetzner-Cloud-Konto
Schritt 1 – Das hcloud-CLI-Tool installieren
#
Beim Arbeiten mit Hetzner Cloud ist es oft praktisch, das meiste in der Shell zu erledigen. Dafür ist das hcloud-CLI-Tool nützlich.
Auf einem Mac wie meinem reicht:
brew install hcloudBist du nicht auf einem Mac, findest du die Installationsanleitung hier: https://github.com/hetznercloud/cli
Schritt 2 – Die Ressourcen auf Hetzner Cloud erstellen #
Für das Setup brauchen wir mindestens 2–3 Server, die wir mit dem hcloud-Tool erstellen. Wir erstellen und richten ein:
- 1 Hetzner Cloud Network
- 1 Hetzner Cloud CX11 Server
- 2 Hetzner Cloud CX21 Server
Der kleinere CX11-Server dient als Kubernetes-Master, die 2 CX21-Server sind Worker-Nodes.
2.1a – Ich habe noch keinen SSH-Key #
Falls noch nicht geschehen, füge deinen öffentlichen SSH-Key zu Hetzner Cloud hinzu, damit du deine Server per Client-Zertifikat einrichten kannst.
hcloud ssh-key create --name <whateverthenameyoulike> --public-key-from-file ~/.ssh/id_rsa.pubund notiere dir die Nummer des erstellten Keys, denn die brauchen wir später.
2.1b – Ich habe bereits einen Hetzner-Cloud-SSH-Key #
Falls du schon einen SSH-Key erstellt hast, ermittle jetzt die Nummer/ID dieses Keys, denn die brauchen wir gleich.
hcloud ssh-key list2.2 – Netzwerk und Nodes erstellen #
Erstelle nun die Ressourcen auf Hetzner Cloud.
# Create private network (you can alter the ip range of course!. 10.98.0.0 is just a suggestion)
hcloud network create --name kubernetes --ip-range 10.98.0.0/16
hcloud network add-subnet kubernetes --network-zone eu-central --type server --ip-range 10.98.0.0/16
# Create Servers (this is where you'll need that ssh key from step 2.1)
hcloud server create --type cx11 --name master --image fedora-31 --ssh-key <ssh key id from 2.1>
hcloud server create --type cx21 --name worker-1 --image fedora-31 --ssh-key <ssh key id from 2.1>
hcloud server create --type cx21 --name worker-2 --image fedora-31 --ssh-key <ssh key id from 2.1>
# Attach the servers to the private network (we will use a easy-to-remember ip for the master, but this is optional)
hcloud server attach-to-network --network kubernetes --ip 10.98.0.10 master
hcloud server attach-to-network --network kubernetes worker-1
hcloud server attach-to-network --network kubernetes worker-2Jetzt kannst du dich per SSH auf allen Servern einloggen:
hcloud server ssh <name-of-server>
# Example
hcloud server ssh masterSchritt 3 – Die Systeme für die Installation vorbereiten #
Bevor wir Kubernetes installieren, müssen wir die Systeme vorbereiten. Das gilt nur für Fedora 31. Willst du Ubuntu nutzen, empfehle ich den Beitrag von Christian Beneke.
Wichtig! Diese Schritte musst du auf jedem Server ausführen (Master und die 2 Nodes).
Fedora kommt ohne aktivierte bash-completion. Das lässt sich leicht beheben mit
dnf install -y nano bash-completion source /etc/profile.d/bash_completion.shfertig
3.1 Das System aktualisieren #
Vor dem Start empfiehlt es sich, das System zu aktualisieren:
dnf update -y3.2 Networking für Kubernetes aktivieren #
cat <<EOF > /etc/sysctl.d/k8s.conf
# Allow IP forwarding for kubernetes
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOFErstelle Konfigurationsdateien für Kubernetes und Docker, die wir gleich installieren.
cat <<EOF > /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS=--cloud-provider=external --runtime-cgroups=/systemd/system.slice --kubelet-cgroups=/systemd/system.slice
EOF
mkdir -p /etc/systemd/system/docker.service.d/
cat <<EOF > /etc/systemd/system/docker.service.d/00-cgroup-systemd.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd
EOF
systemctl daemon-reload3.3 cgroups v1 statt v2 nutzen #
Wenn du die Release Notes von Fedora 31 aufmerksam gelesen hast, ist dir vielleicht der Wechsel zu cgroups v2 aufgefallen. Da Docker und Kubernetes stark auf cgroups setzen, laufen wir definitiv in Probleme, weil sie cgroups v2 (noch) nicht unterstützen.
Also müssen wir zurück auf v1:
dnf install -y grubby
grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0"Da es ein Kernel-Parameter ist, müssen wir das System vollständig neu starten:
reboot3.4 Docker installieren #
Folge https://docs.docker.com/install/linux/docker-ce/fedora/, um Docker zu installieren, im Wesentlichen also:
# Add Repo
dnf config-manager \
--add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
# Install packages
dnf install -y docker-ce docker-ce-cli containerd.io
# Enable the service
systemctl enable --now docker3.5 Kubernetes installieren #
Du kannst fast vollständig https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ folgen, um Kubernetes zu installieren – mit ein paar Ausnahmen.
# Ensure iptables tooling does not use the nftables backend
update-alternatives --set iptables /usr/sbin/iptables-legacy
# Add repo (difference to the original is the "exclude=kube*" part, since we should do Kubernetes updates manually)
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
# Set SELinux in permissive mode (effectively disabling it)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# Install the packages for 1.17.3 (difference to the original is the addition of cni, since we we'll need that for flannel)
dnf install -y kubelet-1.17.3-0 kubeadm-1.17.3-0 kubectl-1.17.3-0 kubernetes-cni --disableexcludes=kubernetes
# Enable the Kubelet
systemctl enable --now kubelet3.6 Versionen pinnen #
Der Befehl dnf upgrade würde auch Docker- und Kubernetes-Pakete upgraden. Das wollen wir vielleicht nicht, denn für jedes Kubernetes-Upgrade gibt es spezifische Schritte: https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/
Es ist also sinnvoll, die installierte Version festzunageln und diese Pakete manuell zu upgraden. Um dnf am Upgrade aller Pakete zu hindern, kannst du Excludes in der Konfiguration definieren. Es gibt aber einen anderen Weg: dnf-plugin-versionlock. Dieses dnf-Plugin fügt Pakete zu einer Liste hinzu, die bei dnf upgrade nicht aktualisiert werden.
dnf install -y dnf-plugin-versionlockund dann fügen wir alle installierten Pakete für Kubernetes und Docker zu dieser Lock-Liste hinzu:
dnf versionlock add kube* docker-* containerd*Wenn wir upgraden müssen, entfernt man einfach den Version-Lock
dnf versionlock delete <package>und installiert die gewünschte Version
dnf install -y kubeadm-1.17.3-0und fügt das Paket danach wieder dem Version-Lock hinzu. Mach das mit jedem Paket entlang der offiziellen Upgrade-Anleitung: https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/
Schritt 4 – Den Cluster initialisieren – Control Plane installieren #
Wenn du bis hierher alle Schritte auf jedem Server befolgt hast, haben wir alles, um den Cluster zu initialisieren. Die folgenden Schritte machst du nur auf dem Master-Node.
Du brauchst die öffentliche IP-Adresse deines Master-Servers und die Adresse im privaten Netzwerk. Da der API-Server-Traffic zwischen Master und Nodes nicht vollständig gesichert ist (siehe https://kubernetes.io/docs/concepts/architecture/master-node-communication/), will ich das private Netzwerk zwischen allen Servern nutzen. Wir sagen kubeadm also, den API-Server über die private IP (10.98.0.10, erstellt in Schritt 2.2) zu advertisen, aber auch die Authentifizierung über die öffentliche IP des Servers zu erlauben. Denn ich will auch von meinem Mac aus deployen (dieser Traffic ist durch das selbstsignierte Zertifikat in .kube/config verschlüsselt). Das --ignore-preflight-errors=NumCPU brauchen wir nur auf einem CX11-Server, da er nur eine CPU hat. Ein Kubernetes-Master empfiehlt mindestens 2 CPUs.
kubeadm init \
--apiserver-advertise-address=10.98.0.10 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=10.244.0.0/16 \
--kubernetes-version=v1.17.3 \
--ignore-preflight-errors=NumCPU \
--apiserver-cert-extra-sans <public ip of your master server>Jetzt solltest du eine Erfolgsmeldung erhalten wie
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.98.0.10:6443 --token 9bsd1v.jl1351231kk53 \
--discovery-token-ca-cert-hash sha256:aca30f96f58ea1479fe421936e232d5dc46fe19f03fe9d7888821154bb457039Notiere dir den Join-Befehl in der letzten Zeile, denn den brauchen wir später.
Kopiere die Cluster-Config in dein Home, um mit kubectl auf den Cluster zuzugreifen:
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/configSchritt 5 – Ein Container-Netzwerk deployen #
Damit die Container/Pods kommunizieren können, brauchen wir ein Container-Netzwerk (CNI). Eine Option ist Cilium, du kannst aber auch ein anderes wählen: https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network
Update 20. Jan. 2020: Flannel hat auf meinem Cluster Probleme verursacht – warum? Lies hier
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/v1.7.1/install/kubernetes/quick-install.yamlOptional
Prüfe nun, ob das Netzwerk läuft: Du kannst einen gründlichen Connectivity-Test deployen, um zu sehen, ob jeder Container auf jedem Node die anderen über verschiedene Network Policies erreichen kann.
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.7.1/examples/kubernetes/connectivity-check/connectivity-check.yaml
# after a while
kubectl get pods
NAME READY STATUS RESTARTS AGE
echo-a-9b85dd869-292s2 1/1 Running 0 8m37s
echo-b-c7d9f4686-gdwcs 1/1 Running 0 8m37s
host-to-b-multi-node-clusterip-6d496f7cf9-956jb 1/1 Running 0 8m37s
host-to-b-multi-node-headless-bd589bbcf-jwbh2 1/1 Running 0 8m37s
pod-to-a-7cc4b6c5b8-9jfjb 1/1 Running 0 8m36s
pod-to-a-allowed-cnp-6cc776bb4d-2cszk 1/1 Running 0 8m36s
pod-to-a-external-1111-5c75bd66db-sxfck 1/1 Running 0 8m35s
pod-to-a-l3-denied-cnp-7fdd9975dd-2pp96 1/1 Running 0 8m36s
pod-to-b-intra-node-9d9d4d6f9-qccfs 1/1 Running 0 8m35s
pod-to-b-multi-node-clusterip-5956c84b7c-hwzfg 1/1 Running 0 8m35s
pod-to-b-multi-node-headless-6698899447-xlhfw 1/1 Running 0 8m35s
pod-to-external-fqdn-allow-google-cnp-667649bbf6-v6rf8 1/1 Running 0 8m35sWenn alles im Status ready ist, hast du Glück – alles funktioniert wie erwartet. Du kannst den Check löschen:
kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.7.1/examples/kubernetes/connectivity-check/connectivity-check.yamlSchritt 6 – Worker-Nodes beitreten lassen #
Führe den in Schritt 4 ausgegebenen Join-Befehl auf beiden Worker-Nodes aus.
Etwa so:
kubeadm join 10.98.0.10:6443 --token 9bsd1v.jl1351231kk53 \
--discovery-token-ca-cert-hash sha256:aca30f96f58ea1479fe421936e232d5dc46fe19f03fe9d7888821154bb457039Schritt 7 – Den Hetzner Cloud Controller Manager deployen #
Für die Integration mit Hetzner Cloud haben sie einen “Kubernetes Cloud Controller Manager” entwickelt. Der braucht Zugriff auf die Hetzner Cloud, also erstellen wir ein weiteres API-Token.
Um ein Hetzner-Cloud-API-Token zu erstellen, melde dich im Web-Interface an und navigiere zu deinem Projekt -> Access -> API tokens und erstelle ein neues Token.
Nun erstellen wir in Kubernetes ein Secret mit diesem Token. Dafür brauchst du die Network-ID, die wir in Schritt 2.2 erstellt haben:
hcloud network listcat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: hcloud
namespace: kube-system
stringData:
token: "<the api token you created just yet>"
network: "<network-id>"
EOFJetzt können wir den Controller deployen:
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/hcloud-cloud-controller-manager/master/deploy/v1.5.1.yamlSchritt 8 – Hetzner Cloud Storage (CSI) deployen #
Um die PVC zu nutzen, die die meisten Anwendungen brauchen, musst du ein CSI deployen. Auch dafür hat Hetzner einen Treiber geschrieben.
Erstelle das API-Token aus dem vorherigen Schritt neu oder nutze es wieder und erstelle ein Secret für den Treiber.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: hcloud-csi
namespace: kube-system
stringData:
token: "<the api token you created just yet>"
EOFJetzt deploye die API und den Treiber:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/csi-api/release-1.14/pkg/crd/manifests/csidriver.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/csi-api/release-1.14/pkg/crd/manifests/csinodeinfo.yaml
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/v1.2.3/deploy/kubernetes/hcloud-csi.ymlFazit #
Jetzt solltest du neue Anwendungen mit Storage-Fähigkeit (Volumes) deployen können. Um darauf zuzugreifen, bräuchtest du aber einen Einstiegspunkt aus dem Internet wie NodePorts – doch es gibt bereits eine bessere Lösung: LoadBalancer/Ingress.
Das nächste Thema ist, wie wir auf die in Kubernetes deployten Anwendungen zugreifen können.