k3s und Netcup

K3S auf zwei fast frischen Netcup Server initialisieren und die Kommunikation via vLan realisieren.

k3s und Netcup
Photo by Venti Views on Unsplash

TLDR: https://github.com/JimTim/kubernetes-samples/tree/main/setup-k3s-on-netcup

Meine Server bei Netcup lagen etwas brach und so habe ich mich nochmal an mein Herz gefasst und probiert Kubernetes darauf zu installieren. Da K3S ein gutes Gesamtpaket bietet, wollte ich es damit probieren. Damit das Projekt nicht an irgendwelchen dummen und alten Einstellungen scheitert, habe ich die Server kurzerhand platt gemacht und ein neues Ubuntu 20.04 minimal ausgerollt.

Das heißt, bei diesen Beitrag werde ich quasi beim Urschleim ansetzen. Ich setze lediglich voraus, das der Server minimal abgesichert ist und die normalen ufw Firewall Regeln gesetzt hat:

sudo ufw allow ssh
sudo ufw enable

Vorrausetzungen schaffen

Ich besitze bei Netcup 2 Server, die unterschiedliche Ressourcen zur Verfügung stellen.

Ressourcen Server 1 aka manager Server 2 aka worker
Speicher 156,44GB 1,44TB
Arbeitsspeicher 8GB 16GB
CPU 2CPUs 8CPUs

Der etwas schwächere Server wird demnach der Manager des K3S Clusters werden und wird nachfolgend auch nur noch manager genannt.

Der andere Server mit mehr Ressourcen wird die ganzen Deployments ausführen und ist demnach der Worker und wird demzufolge auch nachfolgend worker genannt.

Damit die Knoten untereinander ungestört kommunizieren können, liegt an beiden Servern ein Cloud vLAN Free an. Damit wird also ein privates Lan aufgebaut und ich muss so manche Ports nicht für die Außenwelt öffnen.

Hostname setzen und vLan aufbauen

manager

Als erstes wird der Hostname neu gesetzt. Dadurch kann man später viel besser erkennen, wer etwas macht:

sudo hostnamectl set-hostname k8s-manager

Um das vLan aufzubauen, muss dieses im ServerControlPanel am Server gebunden sein. Ich habe diesen Schritt bei Netcup nie manuell gemacht und deswegen bin ich mir nicht sicher, warum meine Server das vLan direkt gebunden hatten. Vielleicht ist das also ein Service von Netcup, wenn der Kauf eines Cloud vLans ausgeführt wird.

Cloud vLan Free von Netcup

Nun muss dieses Interface im Ubuntu 20.04 noch initialisiert werden. Das passiert bei dieser Version mit Netplan:

sudo vi /etc/netplan/02-vlan.yaml
network:
   version: 2
   renderer: networkd
   ethernets:
      eth1:
        dhcp4: no
        addresses:
        - 192.168.100.1/24

Anschließend muss aus der Yaml der Netplan generiert und zusätzlich angewendet werden.

sudo netplan generate
sudo netplan apply

Danach sollte ein neues Interface mit dem Namen "eth1" auftauchen. Kontrolliert werden kann das mit ip a.

Damit später die IPs einfach gefunden werden können, werden sie zusätzlich in die /etc/hosts eingetragen:

echo "192.168.100.1 k8s-manager" >> /etc/hosts
echo "192.168.100.2 k8s-worker" >> /etc/hosts

Mit Voraussicht wurde hier schon die IP des Workers eingetragen.

worker

Nun müssen diese Schritte abgewandelt auf dem worker ausgeführt werden. Ich halte mich hier aber etwas kürzer.

Als Erstes natürlich wieder den Hostname setzen:

sudo hostnamectl set-hostname k8s-manager

Danach wird das vLan an einen Interface initialisiert:

sudo vi /etc/netplan/02-vlan.yaml
network:
   version: 2
   renderer: networkd
   ethernets:
      eth1:
        dhcp4: no
        addresses:
        - 192.168.100.2/24

Dann wird der Netplan generiert und angewendet:

sudo netplan generate
sudo netplan apply

Und als letzten Schritt wird die /etc/hosts aktualisiert;

echo "192.168.100.1 k8s-manager" >> /etc/hosts
echo "192.168.100.2 k8s-worker" >> /etc/hosts

K3S installieren und mit dem richtigen Netzwerk versehen

manager

Damit die ganze Kommunikation zwischen den Knoten privat passiert, muss das Flannel Interface richtig gesetzt werden.

💡
Flannel is a simple and easy way to configure a layer 3 network fabric designed for Kubernetes.
Quelle

Mit folgenden Befehl wird K3S heruntergeladen und das Skript wird ausgeführt:

export EXTERNAL_IP=""
export INTERNAL_IP=""
export INTERNAL_INTERFACE="eth1"
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--flannel-iface=$INTERNAL_INTERFACE --node-ip=$INTERNAL_IP --node-external-ip=$EXTERNAL_IP --tls-san $EXTERNAL_IP" sh -

Damit der Befehl etwas dynamischer ist, habe ich vorher noch die Umgebungsvariablen EXTERNAL_IP, INTERNAL_IP und INTERNAL_INTERFACE auf die richtigen Werte gesetzt.

Anschließend kann die generierte kubeconfig an die richtige Stelle kopiert werden.

sudo cp /etc/rancher/k3s/k3s.yaml .kube/config

Die erfolgreiche Installation kann dann mit folgenden Befehl verifiziert werden:

kubectl get node -o wide

An dieser Stelle wird nun die erste Node, der k8s-manager, ausgegeben und die richtigen IPs sind hinterlegt.

Der aufmerksame Leser sieht natürlich hier schon den Worker. Der wurde wie folgt initialisiert:

worker

Damit der Worker aber überhaupt angegangen werden kann, braucht es noch einen sogenannten Node Token vom Manager:

cat /var/lib/rancher/k3s/server/node-token

Dieser Token sollte irgendwo extern notiert werden, da er nun auf dem Worker gebraucht wird.

export EXTERNAL_IP_MANAGER=""
export EXTERNAL_IP=""
export INTERNAL_IP=""
export INTERNAL_INTERFACE="eth1"
export TOKEN="<wurde ein Block oberhalb extern notiert und sollte nun hier eingefügt werden>"
curl -sfL https://get.k3s.io | K3S_URL=https://$EXTERNAL_IP_MANAGER:6443 K3S_TOKEN=$TOKEN INSTALL_K3S_EXEC="--flannel-iface=$INTERNAL_INTERFACE --node-ip=$INTERNAL_IP --node-external-ip=$EXTERNAL_IP" sh -

Auch hier werden vorher die Umgebungsvariablen EXTERNAL_IP_MANAGER, EXTERNAL_IP, INTERNAL_IP, INTERNAL_INTERFACE und TOKEN mit sinnvollen Werten belegt.
Das Installationsskript von K3S erkennt nun automatisch, das es als Agent installiert werden soll.

Auf dem Manager kann nun wieder der kubectl get nodes -o wide Befehl ausgeführt werden. Zu diesem Zeitpunkt sollte sich aber der Worker beim Manager nicht registrieren können, da die notwendigen Firewall Regeln nicht angewendet wurden.

Firewall Regeln

Der Server wurde ja nur mit einer Firewall Regel initialisiert. Das heißt, aktuell ist nur die Kommunikation via SSH erlaubt. Damit die Kommunikation aber privat und öffentlich im Kubernetes Umfeld funktioniert, müssen noch einige Ports wieder geöffnet werden:

manager

sudo ufw allow ssh comment "SSH"
sudo ufw allow http comment "HTTP"
sudo ufw allow https comment "HTTPS"
sudo ufw allow 6443/tcp comment "Kubernetes API"
sudo ufw allow from 192.168.100.0/24 comment "Kubernetes Flannel Network Communication (internal)"
sudo ufw allow in on eth1 from 192.168.100.0/24 comment "Kubernetes Flannel Network Communication (internal) on eth1 incoming"
sudo ufw allow out on eth1 from 192.168.100.0/24 "Kubernetes Flannel Network Communication (internal) on eth1 outgoing"
sudo ufw enable

Ich habe einige Kommentare an die Regeln gepackt und damit sind die Befehle Erklärung genug.

worker

sudo ufw allow ssh comment "SSH"
sudo ufw allow http comment "HTTP"
sudo ufw allow https comment "HTTPS"
sudo ufw allow from 192.168.100.0/24 comment "Kubernetes Flannel Network Communication (internal)"
sudo ufw allow in on eth1 from 192.168.100.0/24 comment "Kubernetes Flannel Network Communication (internal) on eth1 incoming"
sudo ufw allow out on eth1 from 192.168.100.0/24 "Kubernetes Flannel Network Communication (internal) on eth1 outgoing"
sudo ufw enable

Auch hier sind die Kommentare Erklärung genug.

Damit sollte sich nun der Worker am Manager registrieren können. Ansonsten kann auch ein kurzer Uninstall & Install auf dem Worker helfen:

/usr/local/bin/k3s-agent-uninstall.sh
curl -sfL https://get.k3s.io | K3S_URL=https://$EXTERNAL_IP_MANAGER:6443 K3S_TOKEN=$TOKEN INSTALL_K3S_EXEC="--flannel-iface=$INTERNAL_INTERFACE --node-ip=$INTERNAL_IP --node-external-ip=$EXTERNAL_IP" sh -

Cert Manager und wie bekommen meine Anwendungen eigentlich ein Zertifikat?

An dieser Stelle könnte man auch schon aufhören und fröhlich Anwendungen via kubectl deployen. Aber jede Anwendung hat auch irgendwann eine Route in die Öffentlichkeit. Dieser sogenannte Ingress sollte natürlich nur via HTTPs erreichbar sein. Da ich keine Zertifikate einfach so rumliegen habe, werden diese immer mit Hilfe von LetsEncrypt generiert werden. Diese Aufgabe übernimmt das Tool cert-manager im Kubernetes Umfeld.

K3S bringt bereits Traefik als Ingress Manager mit und so muss der Cert-Manager mit Traefik initialisiert werden. Aber erstmal die Deployments vom Cert-Manager im Kubernetes bereitstellen:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.yaml

Da ich dieses Tutorial am 10.04.2022 schreibe, ist die Version 1.8.0 aktuell. Unter Installation von Cert-Manager kann aber die aktuelle Version herausgefunden werden. Noch besser, der aktuellste Installationsschritt ist dort dokumentiert.

Damit Zertifikate bei LetsEncrypt beantragt werden können, muss eine valide Mailadresse hinterlegt werden. Die Ressource heißt im Cert-Manager Umfeld Cluster-Issuer. Diesen Issuer kann man einmal für die Staging Plattform anlegen und dann später für die Produktionslinie von LetsEncrypt. Ich dokumentiere einfach mal beides:

Folgenden Inhalt als letsencrypt-staging.yaml speichern:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: mail@example.com
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource that will be used to store the account's private key.
      name: staging-issuer-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: traefik

Danach kann diese Datei mithilfe der kubectl angewendet werden:

kubectl apply -f letsencrypt-staging.yaml

Folgenden Inhalt als letsencrypt-prod.yaml speichern:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: mail@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource that will be used to store the account's private key.
      name: prod-issuer-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: traefik

Auch diese Datei wird mithilfe der kubectl angewendet:

kubectl apply -f letsencrypt-prod.yaml

Ab diesem Zeitpunkt ist das Tutorial fertig. Beide Server wurden erfolgreich initialisiert, vLan ist fertig aufgebaut und funktioniert mit der ufw Firewall und das K3S tauscht sich munter und sicher aus.

Als Sahnehäubchen werde ich im nächsten Tutorial dokumentieren, wie ich diesen Blog im Kubernetes betreibe und damit auch den Cert-Manager für das Zertifikat benutze.