Kubernetes mit kubeadm auf Netcup Root Servern

Um mich ein wenig mit Kubernetes auseinander zu setzen, brauchte ich einen eigenen Cluster, mit dem ich erste Erfahrungen sammeln und immer wieder neue Dinge ausprobieren konnte. Da mir die Ressourcen für anständige Tests bei den großen Cloud Providern (AWS, Azure, Google) zu teuer waren, habe ich auf einen Webhosting Anbieter gesetzt, der mir kostengünstige „Root Server“ vermietet. Ich beschreibe in diesem Artikel die Installation eines Kubernetes Clusters mit Hilfe des Tools „kubeadm“ auf 3 Servern (1 x Master und 2 x Worker Nodes) der Fa. Netcup GmbH. Zusätzlich benutze ich auf allen Servern ein zweites Netzwerk Interface, das an ein privates vLan Netz angebunden wird, über das der spätere Cluster kommuniziert. Das kann bei Netcup aktuell kostenlos dazu gebucht werden (kostenlos jedoch nur mit langsamen 100Mbit, ansonsten Kostenpflichtig) und im Server Control Panel als zusätzliches Netzwerk Interface dem eigenen Root Server hinzugefügt werden.

Betriebssystem Setup

Als Basis Betriebssystem benutzte ich Ubuntu 18.04, das in einer minimalen Server Version installiert wird. Hier reicht SSH Zugang, der Rest wird nachinstalliert. Ich gehe hier von einem fertig installierten Ubuntu 18.04 aus.
Als allererstes wird das Betriebssystem gegen Angriffe aus dem Internet „gehärtet“. Hierzu legen wir einen neuen „normalen“ Benutzer an.

adduser meinname

Damit dieser Benutzer auch alles machen darf, ist es notwendig diesem das Recht zu geben, als Root User zu agieren. Hierzu tragen wir ihn in die „sudoers“ ein und erlaube ihm Befehle als Root auszuführen, ohne erneute Eingabe des Passworts. Da ich mich schon Passwortlos auf dem Server anmelde, möchte ich nicht mein Passwort eingeben müssen um meine Rechte zu erweitern. Hier erstelle ich eine neue Datei /etc/sudoers.d/meinname mit folgendem Inhalt:

# User privilege specification
meinname ALL=(ALL) NOPASSWD: ALL

Anschließend sollte der Login mit dem neuen Benutzer meinname getestet werden, inkl. der erweiterten Rechte. Hier wurde die Anmeldung Beispielhaft mit dem ssh client auf einem Mac gemacht. Unter Windows kann hierfür Putty verwendet werden. Wichtig ist, dass die Rechte Erweiterung mittels „sudo“ klappt, bevor der Login des „root“ Users abgeschaltet wird.

ssh -l meinname meinserver
sudo su

War die Benutzereinrichtung erfolgreich, kann der SSH Login für den „root“ User deaktiviert werden. Hierzu muss die Datei /etc/ssh/sshd_config bearbeitet werden und die folgende Zeile von „yes“ auf „no“ geändert werden:

PermitRootLogin yes # <-- Diesen Wert auf "no" ändern

Diese Änderung wird nach dem nächsten Neustart aktiv, oder mit einem Neustart des SSH Daemons.

systemctl restart sshd

Zusätzlich installiere ich noch das Programm „fail2ban“. Dieses protokolliert in der Standardinstallation bereits die SSH Login Versuche und sperrt eine IP Adresse für 10 Minuten, wenn mehr als 3 fehlgeschlagene Loginversuche von einer IP Adresse registriert wurden.

apt install fail2ban

(Optional) Passwortloser SSH Zugang einrichten

Für den Zugriff mittels Public/Private Key anstatt des Benutzernames mit Password, richte ich noch einen passwortlosen SSH Zugang ein. Dies geschieht wie folgt:

Wechseln zum Benutzer meinname

su - meinname

Erzeugen eines SSH Schlüsselpaars

ssh-keygen

In dem eigenen .ssh Unterverzeichnis die Datei authorized_keys (~/.ssh/authorized_keys) anlegen. Dieser Datei wird der eigenen SSH Public Key hinzugefügt (z.B. ssh-rsa AAAAB3Nza……), mit dem ich mich zukünftig authentifizieren möchte.
Nach dem speichern der Datei muss diese Datei noch die korrekten Rechte bekommen (nur der eigene User hat Zugriff), da diese sonst nicht vom SSH Daemon akzeptiert wird.

chmod 600 ~/.ssh/authorized_key

Netzwerk Setup

Mein Zielsetup sieht wie folgt aus:

Netzwerk Ziel Setup inkl. Kubernetes virtuelles Weave Netzwerk

Da die Root Server bei Netcup grundsätzlich mit einer öffentlichen IP Adresse ausgestattet sind und der spätere Cluster Datenverkehr unverschlüsselt verläuft, plane ich ein privates vLAN zusätzlich einzurichten. Hierzu wird jedem meiner Root Server ein weiteres Netzwerk Interface hinzugefügt, das an zu einem separat gebuchten Netcup vLAN gehört.

vLAN Setup

Dieses zusätzliche Netzwerk Interface kann nicht über das „Server Control Panel“ konfiguriert werden und somit wird auch nicht automatisch eine IP Adresse vergeben. Das muss im Betriebssystem selbst gemacht werden. Ich entscheide mich hier für IP V4 Adressen aus dem privaten Subnetz 192.168.1.0/24. Es kann auch jedes andere private Subnetz verwendet werden, jedoch sollte man hierbei auf das im späteren Verlauf genutzte Kubernetes Cluster Netzwerk Plugin achten. Das von mir verwendete Netzwerk Plugin „weave“ vergibt standardmäßig private Adressen aus dem Subnetz 10.0.0.0/8 und kollidiert somit nicht mit meinem gewählten Subnetz für die Kubernetes Worker Nodes. Soll z.B. als Netzwerk Plugin später „calico“ benutzt werden, dann muss man auf Kollisionen achten, denn dieses Netzwerk Plugin hat in meinen Tests IP Adressen aus dem 192.168.0.0/16 Subnetz vergeben und würde damit mit einem Worker Nodes Subnetz kollidieren.

Sei es drum, ich konfiguriere jetzt meine 3 Root Server so, dass jeder eine private IP Adresse auf dem Netcup vLAN Interface bekommt. Hierzu lege ich die Datei /etc/netplan/02-vlan.yaml mit folgendem Inhalt an:

network:
version: 2
renderer: networkd
ethernets:
eth1:
dhcp4: no
addresses:
- 192.168.1.1/24
routes:
- to: 10.64.0.0/10
via: 192.168.1.1

Das zusätzliche Netzwerk Interface heißt bei meinen aktuellen Installationen standardmäßig eth1 (manchmal auch ens6, entsprechend in der Datei 02-vlan-yaml anpassen) und bekommt von uns die statische IP Adresse 192.168.1.1. Genauso werden auch die beiden anderen Root Server konfiguriert, jedoch bekommen diese die IP Adressen 192.168.1.2, bzw. 192.168.1.3. Damit wir direkt von unseren Worker Nodes auch auf die Cluster internen IP Adressen zugreifen können, benötigen wir noch eine Netzwerk Route. Wir sagen also, dass Netzwerk Verkehr für alle IP Adressen aus dem Subnetz 10.64.0.0/10, über das lokale private Subnetz 192.168.1.1 geroutet werden sollen. Diese Einstellung ist wichtig, damit die Worker Nodes sich später zum Kubernetes API Server verbinden können. Ohne diese Einstellung steht im Weave Plugin Log, dass die Adresse 10.96.0.1 nicht erreichbar ist und der Worker Node kann nicht korrekt gestartet werden. Mit dieser Route ist es hinterher auch möglich, auf alle Clusterinternen IP Adressen direkt von der Worker Node aus zuzugreifen. Nachdem die Datei erstellt wurde, kann die Netzwerkkonfiguration neu eingelesen werden mit den Befehlen.

netplan generate 
netplan apply

Sollte das Netzwerk interface jetzt noch nicht als aktives Interface vorhanden sein (z.B. in der Auflistung mit „ifconfig“), dann hilft hier ein Neustart des Servers. Bei einem Neustart heißt das Netzwerk Interface nämlich ens6 und wird korrekt konfiguriert und wenn man es nachträglich konfiguriert heißt es eth1 und müsste über diesen Namen konfiguriert werden. Dann funktioniert es aber beim nächsten reboot nicht mehr. Also lieber den Server durchstarten und nochmal testen, bevor es weiter geht.

Um jetzt noch allen IP Adressen auch einen Hostnamen zuzuweisen, bearbeite ich die Datei /etc/hosts und füge die folgende Einträge hinzu. Man sollte hier die tatsächlichen Hostnamen der Server angeben.

192.168.1.1     master
192.168.1.2 node1
192.168.1.3 node2

Ich gehe hier bereits davon aus, dass der Server mit der IP Adresse 192.168.1.1 unser Master Server wird. Der gewählte Hostname sollte auch dem tatsächlichen hostnamen des Servers entsprechen, da Kubernetes die Nodes später über den Hostnamen identifiziert. Evtl. muss also noch der Hostname des Servers angepasst werden.

DHCP Domain Anpassungen

Die Netcup Server beziehen über die DHCP Konfiguration auch einen domain Namen und einen domain Such-Namen. Der default Domain Name bei Netcup Server ist sowas wie „luckysrv“ (oder so ähnlich). Das Problem dabei war in älteren Kubernetes Versionen (weiß nicht ob es immernoch so ist), dass Kubernetes den Namen „localhost“ nicht korrekt auflösen konnte, da die DNS Konfiguration dann immer nach „localhost.luckysrv“ gesucht hat und somit nicht die lokale loopback IP Adresse 127.0.0.1 zurück bekommen hat, sondern eine IP Adresse im Netcup Netzwerk. Damit funktionierte das Cluster Setup nicht. Das umgehen wir, indem wir den DNS Domainnamen aus der DHCP Konfiguration entfernen. Hierzu bearbeiten wir die Datei /etc/dhcp/dhclient.conf und entfernen aus dem „request“ die folgenden Parameter: „domain-name“, „domain-search“ und „dhcp6.domain-search“. Die „request“ Konfiguration in der Datei sieht dann wie folgt aus:

request subnet-mask, broadcast-address, time-offset, routers,
domain-name-servers, host-name,
dhcp6.name-servers, dhcp6.fqdn, dhcp6.sntp-servers,
netbios-name-servers, netbios-scope, interface-mtu,
rfc3442-classless-static-routes, ntp-servers;

Evtl. ist dieser Schritt inzwischen auch schon wieder überholt, aber sollte während des Setups in den Log Dateien zu finden sein, dass der Endpunkt „localhost/<irgendwas>“ nicht erreichbar ist, dann solltet ihr diese Konfiguration prüfen.

Firewall Setup

Als simple Firewall konfiguration nutze ich das bereits vorinstallierte Tool „ufw“. Hiermit blockiere ich den meisten eingehenden Traffic, erlaube aber die interne Kommunikation auf dem privaten Netzwerk. Hierzu benutze ich folgende Befehle auf jedem Root Server.

ufw allow ssh
ufw allow from 192.168.1.0/24
ufw allow from 10.0.0.0/8
ufw enable

Der Server, der hinterher noch den HTTP Endpunkt bereit stellt (in meinem Beispiel wird das Node1 werden), sollte noch die entsprechenden HTTP Ports freigeben. Hierzu werden auf dem betreffenden Server noch die folgenden Befehle ausgeführt:

ufw allow http
ufw allow https

Unser Master Server startet den Kubernetes API Server. Damit dieser auch von außen erreichbar ist und wir z.B. das Tool „kubectl“ von unserem Client aus benutzen können, ist es notwendig auch den „apiserver“ port, nur auf dem master server, für alle freizugeben:

ufw allow 6443

Damit ist unser grundsätzliches Netzwerk Setup fertig und alle Server sollten sich gegenseitig mit dem Hostnamen über das private Netzwerk erreichen können.

Master Server Setup mit kubeadm

Jetzt kommen wir endlich zum eigentlichen Kubernetes Setup. Als erstes setzen wir den Kuberentes Master Server auf. Hierzu loggen wir uns aus dem Server „master“ (oder entsprechend der gewählte Hostname) ein und fügen den „apt key“ und das „apt repository“ für die Kubernetes Pakete hinzu:

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
    
apt-get update

Es gibt leider noch keine Paketquellen (stand 16.04.2019) für das aktuelle Ubuntu Release „bionic“, aber die „xenial“ Paket funktionieren auch hier noch.

Jetzt installieren wir die benötigten Pakete in der aktuellsten Version. Zuletzte getestet wurde das Setup mit Kubernetes 1.14.0. Außerdem setzen wir „selinux“ noch in den „Permissive Mode“, um störende Einflüsse zu vermeiden. Ist das aktuell noch notwendig? Sagt mir bescheid, wenn nicht.

apt-get install -y docker.io kubelet kubeadm kubectl kubernetes-cni selinux-utils nfs-common

setenforce 0

systemctl enable docker.service

Zusätzlich zum ganzen Kubernetes Kram installiere ich noch das nfs-common Paket, da ich somit hinterher auf NFS Shares zugreifen kann. Das ist der einfachste Weg persistenten Storage im Kubernetes Cluster verfügbar zu machen. Dazu komme ich aber in einem anderen Post.

Kubernetes unterstützt keinen „Swap“ Speicher mehr im Betriebssystem. Der Dienst „kubelet“ startet seit einigen Versionen nicht mal mehr, wenn „Swap“ Speicher konfiguriert ist. Bei meinen neueren Servern (mit viel Arbeitsspeicher) wird kein „Swap“ Speicher mehr konfiguriert. Bei einen „kleineren“ und alteren Servern starten diese sehr wohl mit einer Swap Konfiguration. Der Swap Speicher kann komplett und sofort deaktiviert werden mit dem Befehl

swapoff -a

Was aber evtl. nach dem nächsten Neustart wieder zurück gesetzt ist. Entweder gebt ihr diesen Befehl dann nach jedem Neustart ein, oder ihr googled nach einer permanenten Deaktivierung. Ich habe mich damit abgefunden den Befehl nach jedem Reboot einzugeben, das das nicht sehr häufig vorkommt.

Nun starten wir kubeadm um unseren Kubernetes Master Server zu initialisieren. Diesem müssen wir ein paar Parameter mitgeben, damit er unser privates Netzwerk benutzt anstelle des default Netzwerks.

kubeadm init --apiserver-cert-extra-sans 11.22.33.44,master.example.com --apiserver-advertise-address 192.168.1.1

cp /etc/kubernetes/admin.conf $HOME/.kube/config

Der „kubeadm init“ command startet das Setup des Master Servers. Während dieses Prozesses werden Zertifikate erstellt, die den Server als Vertrauenswürdig einstufen. Damit wir den API Server jedoch auch aus dem Public Netzwerk ansprechen können, ist es notwendig, dass das Zertifikat auch die public IP Adresse und den public Domain Namen des Servers enthält. Ansonsten ist ein Zugriff auf den API Server nur von den Kubernetes Nodes und Master möglich, was das deployment hinterher erschwehrt.
Außerdem ist hier wichtig, dass dem „init“ Befehl die IP Adresse mitgegeben wird, über die der API Server kommunizieren soll. Dies ist die lokale IP unseres privaten Netzwerks.
Der anschließende „copy“ Befehl kopiert die admin Zugangsdaten noch in das Standardverzeichnis/Datei, in dem das Programm „kubectl“ seine credentials erwartet. Somit können wir „kubectl“ anschließend ohne weitere Parameter verwenden.

Wenn der „kubeadm init“ Befehl erfolgreich durchgelaufen ist, wird in der Bildschirmausgabe der Befehl angezeigt, mit dem die Kubernetes Worker Nodes diesem Cluster beitreten können. Dieser Befehl sieht wie folgt aus:

kubeadm join 192.168.1.1:6443 --token 3azvyy.q1111y9101t77746 --discovery-token-ca-cert-hash sha256:f7d34dc111f65db829a38368f0e6d88b111e1e5a2acce3662229111c25d2ed5f

Dieser „join token“ verfällt nach einer Weile und es können keine weiteren Worker Nodes mehr damit hinzugefügt werden. Soll später ein weiterer Worker Node hinzugefügt werden, dann ist es evtl. notwendig ein neues Token zu generieren. Das kann auf dem master Server wie folgt erzeugt werden:

kubeadm token create --print-join-command

Zum Schluss ist es notwendig noch ein Netzwerk Plugin zu installieren, da Kubernetes dieses nicht selbst bereit stellt. Hier ist die Auswahl groß und ich habe mich für „weave net“ entschieden, da es damals als einziges Netzwerk Plugin eine Verschlüsselung angeboten hat. Diese benutze ich zwar nicht mehr, bin aber bei dem Plugin geblieben. Das Weave Plugin kann jetzt mit folgendem Befehl vom Master Server aus im Kubernetes Cluster deployed werden:

kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"

Jetzt muss man nur noch warten bis alle Kubernetes Pods im Status „ready“ sind und das Cluster Setup ist abgeschlossen. Der folgende Befehl sollte überall in der Spalte „Ready“ den Wert „1/1“ bzw. „2/2“ anzeigen.

kubectl get pods --all-namespaces

Node Server Setup mit kubeadm

Das Setup der Node Server ist hier etwas einfacher und muss für jeden Worker Node ausgeführt werden. Zuerstmüssen die selben Vorbereitungen, wie bei dem Master Server Setup, durchgeführt werden.

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
    
apt-get update

apt-get install -y docker.io kubelet kubeadm kubectl kubernetes-cni selinux-utils nfs-common

setenforce 0

systemctl enable docker.service

swapoff -a

Anschließend ist es nur noch notwendig das „join“ Kommando, dass bei der Master Installation auf der Konsole ausgegeben wurde, auf dem Worker Node auszuführen:

kubeadm join 192.168.1.1:6443 --token 3azvyy.q1111y9101t77746 --discovery-token-ca-cert-hash sha256:f7d34dc111f65db829a38368f0e6d88b111e1e5a2acce3662229111c25d2ed5f

Ob die Verbindung erfolgreich geklappt hat, kann auf dem Master Server mit folgendem Befehl geprüft werden. Dort sollte die hinzugefügte Worker Node mit dem Status „Ready“ angezeigt werden, inkl. der benutzten Kubernetes Version.

kubectl get nodes

Sieht die Rückgabe so aus, scheint alles gut zu sein.

NAME     STATUS   ROLES    AGE    VERSION
master Ready master 1d v1.14.1
node1 Ready 1d v1.14.1
node2 Ready 1d v1.14.1

Nachdem ihr alle Nodes dem Cluster hinzugefügt habt, stehen euch die Leistungen eures eigenen Kubernetes Clusters zur Verfügung. Zur bequemen Nutzung des Clusters würde ich jetzt noch die Admin User Credentials vom Master Server auf den lokalen Client kopieren (/etc/kubernetes/admin.conf), damit auch mit dem lokalen „kubectl“ auf dem remote Cluster gearbeitet werden kann.

Der Cluster mit den Netcup Root Servern steht jetzt bereit und kann benutzt werden. Und das im übrigen zu einem Bruchteil der Kosten, den die Managed Cloud Anbieter für ihre Kubernetes Cluster verlangen. Wir haben hier zwar keinen HA (High Availability) Cluster, aber ein Cluster mit diesem Setup leistet mir schon seit ca. 2 Jahren gute Dienste.
Ich hoffe der ein oder andere findet diese Anleitung hilfreich. Zukünftige Artikel von mir werden wohl von diesem Setup ausgehen.

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.