IT-Knowledgebase
de DevOps Automatisierung Puppet

Kubernetes mit Puppet aufsetzen

Kubernetes ist im Trend und läuft bei einer steigenden Anzahl von Unternehmen. Immer mehr Anwendungen werden in ein Cluster verschoben oder sogar direkt dafür entwickelt. Um ein Cluster zu installieren, gibt es aber verschiedene Wege. Ein beliebtes Tool ist kubeadm, das immer in der gleichen Version wie Kubernetes erscheint.

Mit diesem Tool ist es einfacher, ein neues Cluster aufzusetzen, es hat aber trotzdem noch viele Fallstricke, was es gerade für Anfänger schwierig macht, die richtigen Parameter und Vorgehensweisen einzuhalten. Im professionellen Umfeld geht es zudem meist darum, ein größeres Cluster mit mehreren Mastern und Workern komplett identisch aufzusetzen. Das kann bei manueller Arbeit aufgrund von Tippfehlern oder Gedächtnislücken kompliziert werden.

Erschwerend kommt das nachträgliche Vergrößern des Clusters hinzu, da die Knoten auch nach mehreren Wochen oder Monaten noch identisch aufgesetzt werden sollten, um Fehlern vorzubeugen. Wichtig ist auch der Faktor Zeit. Da per Hand immer nur einer oder wenige Knoten gleichzeitig installiert werden können, dauern große Installationen oder Cluster gern etwas länger.

Hier kommt nun also Puppet ins Spiel. Puppet ist ein Konfigurationsmanagement-Tool, das ein fertiges Modul für das Installieren von Kubernetes-Clustern mitbringt.

Voraussetzungen und Testinfrastruktur

Um ein Kubernetes-Cluster mittels Puppet aufzusetzen, benötigen wir

  • einen oder mehrere Master-Server (dabei ist zu bedenken, dass ein Kubernetes-Master 2 CPUs haben sollte)
  • einen fertigen Puppet-Master
  • einen oder mehrere Client-Server (Optional)

Für unser Setup benutze ich einen Puppet-Master, einen Kubernetes-Master und einen Kubernetes-Worker-Server. Die drei Server basieren alle auf Ubuntu 20.04.

Die drei Ubuntu Server 20.04 haben folgende IP-Adressen:

  • Puppetmaster: 192.168.176.45
  • Kubernetes-Master: 192.168.176.46
  • Kubernetes-Worker: 192.168.176.47

Vorbereiten des Puppet Agents

Zuerst müssen wir den Puppet Agent auf den Servern installieren. Dafür gehen wir den normalen Weg, den wir bei den anderen Servern auch verwendet haben. Für einen Ubuntu Server 20.04 und die Puppet-Version 7 fügen wir zuerst die APT-Repositories hinzu.

Das können wir machen, indem wir uns das Paket dafür von Puppet direkt downloaden und anschließend das Paket installieren.

1wget https://apt.puppet.com/puppet7-release-focal.deb
2dpkg -i puppet7-release-focal.deb

Nun haben wir das Paket eingebunden und können unsere Pakete einmal aktualisieren mittels

1apt-get update

Wenn wir jetzt nach puppet suchen, finden wir jede Menge Pakete, wobei viele davon schon in Ubuntu enthalten sind. Die neuen Pakete sind nun puppetserver und puppet-agent.

Den Puppet Agent können wir jetzt also in der aktuellsten Version mittels apt installieren.

Um zu prüfen, ob es das richtige Paket ist, wird mit dem Befehl

1apt-cache search puppet

geschaut, welche Pakete in unserer Paketverwaltung "puppet" im Namen tragen. Um nun zu schauen, woher das Paket bezogen wird (da Puppet schon in den offiziellen Paketquellen enthalten ist, allerdings in einer älteren Version), verwenden wir den Befehl:

1apt-cache madison puppet-agent

Hier ein kleiner Vergleich zwischen zwei Paketen aus verschiedenen Quellen: Dabei ist das obere Paket aus den offiziellen Ubuntu-Quellen und das untere aus dem eben hinzugefügten Repository.

1root@kubernetes-1:~# apt-cache madison puppet
2    puppet | 5.5.10-4ubuntu3 | http://de.archive.ubuntu.com/ubuntu focal/universe amd64 Packages
1root@kubernetes-1:~# apt-cache madison puppet-agent
2puppet-agent | 7.15.0-1focal | http://apt.puppetlabs.com focal/puppet7 amd64 Packages
3puppet-agent | 7.14.0-1focal | http://apt.puppetlabs.com focal/puppet7 amd64 Packages

Installiert wird der Puppet Agent nun mittels

1apt-get install puppet-agent

Nun geht es weiter mit der Client-Anmeldung.

Client-Anmeldung, Request signieren, Daten generieren

Nach der Installation müssen wir den Client am Server "anmelden" beziehungsweise registrieren. Das geschieht in der Puppet-Architektur mittels Zertifikaten. Das Zertifikat wird generiert, wenn sich ein Client an dem Server anmeldet und muss anschließend am Master-Server signiert werden, damit der Server seine Daten vom Puppetmaster bekommen kann. Nach der Signatur sind Puppetmaster und Client richtig miteinander verbunden und letzterer dem Puppetmaster bekannt.

Die erste Anmeldung des Clients am Server kann man am besten mit einem Puppet-Run auslösen. Ein Puppet-Run wird am besten initialisiert, indem man die Puppet-Binary ausführt, und zwar mit dem Subcommand Agent und dem Parameter -t. Der Subcommand Agent regelt (wie könnte es anders sein) den Agent. Mit dem Parameter -t wird der Agent im Vordergrund gestartet und direkt ausgelöst, anstatt dass nur der Daemon gestartet und alle 30 Minuten (per Standard) der Puppet-Run ausgelöst wird.

Nach der Installation liegt die Puppet-Binary eigentlich in einem Pfad unter der PATH-Variable, sodass man sie ohne vollständigen Pfad ausführen kann. Sollte das einmal nicht der Fall sein (aufgrund irgendwelcher Richtlinien oder Änderungen bei der Installation), liegt das Programm meist unter

1/opt/puppetlabs/bin/puppet agent -t
2# alternativ wenn Puppet im Pfad liegt
3puppet agent -t

Nun muss noch ein Zertifikatsrequest erzeugt werden, um den Agent auch bei unserem Puppet-Server zu registrieren.

1puppet agent -t -noop

Dieser Befehl generiert einen Request an den Server, ändert bei dem Client aber nichts (was ohnehin nicht nötig ist, da wir noch keinen registrierten Server haben und daher keine Änderungen vom Puppetmaster bekommen würden).

Den Request signieren

Die Anfrage muss auf dem Puppet-Server signiert werden. Dafür gibt es mit dem puppetserver-Befehl den Subcommand ca (Certificate Authority; wird benutzt, um die Zertifikatsanfragen und die eigene Puppet-CA zu verwalten). Um sich die Zertifikate anzeigen zu lassen, wird dem Subcommand ca noch list mit dem Parameter -all angehangen, um wirklich alle Zertifikate angezeigt zu bekommen.

1root@puppet:~# puppetserver ca list --all
2Signed Certificates:
3    puppet.fritz.box             (SHA256)  1A:CC:4B:6E:CB:CA:3A:13:66:21:C4:89:8D:8D:4C:58:13:B7:4D:3B:C6:B2:A6:F1:77:38:4F:22:D6:6A:DF:81   alt names: ["DNS:puppet", "DNS:puppet.fritz.box"]       authorization extensions: [pp_cli_auth: true]

Unter Unsigned Certificates sollte nun unser Server stehen. Signiert werden kann mittels ca sign-Befehl, dazu der Parameter -certname und der eigentliche Zertifikatsname, der meistens der FQDN des Servers ist.

1/opt/puppetlabs/bin/puppetserver ca list --all
2/opt/puppetlabs/bin/puppetserver ca sign --certname kubernetes-1.localdomain

Wenn unser Server signiert ist, können wir mit dem Vorbereiten der Kubernetes-Manifeste beginnen.

Kubernetes Hiera-Daten generieren

Das offizielle Plugin von Kubernetes stellt uns einen Docker-Container bereit, mit dessen Hilfe wir die Daten für Hiera generieren können. Wir führen diesen Container einmal aus und mounten ein bestimmtes Verzeichnis in diesen Container hinein, um die generierten Dateien zu persistieren.

 1docker run
 2--rm
 3-v $(pwd):/mnt
 4-e OS=ubuntu
 5-e VERSION=1.20.2
 6-e CONTAINER_RUNTIME=docker
 7-e CNI_PROVIDER=calico
 8-e CNI_PROVIDER_VERSION=1.4.3
 9-e ETCD_INITIAL_CLUSTER=kube-control-plane:172.17.10.101,kube-replica-control-plane-01:172.17.10.210,kube-replica-control-plane-02:172.17.10.220
10-e ETCD_IP="%{networking.ip}"
11-e KUBE_API_ADVERTISE_ADDRESS="%{networking.ip}"
12-e INSTALL_DASHBOARD=true
13puppet/kubetool:6.2.0

Dieser Befehl generiert nun die Daten für Hiera. Hierin enthalten sind zum Beispiel die Daten für die unterschiedlichen Zertifikate, die Mitglieder der Master aus dem Cluster (gilt auch für den etcd) und die Versionen des CNI und der Control-Plane von Kubernetes an sich.

Die Hiera-Dateien kopieren wir nun unter die entsprechenden Pfade in unserer Hiera Hierarchy, das OS am besten in den OS-Ordner. Sollte eine OS-Ordnung nicht bestehen, müssen die Attribute daraus in jede einzelne Host-Datei hineinkopiert werden. Die einzelnen Host-Dateien kann man nun einfach in den Host-Ordner kopieren.

Die Parameter sind dabei meist selbsterklärend:

Parameter Beschreibung
OS Die OS-Version des Servers
Version Die Version des Kubernetes-Clusters
CONTAINER_RUNTIME Die Container Runtime (bis Version 1.24.0 ist es meistens docker)
CNI_PROVIDER Container Network Interface PROVIDER (es gibt eine Vielzahl von Anbietern, um das Netzwerk in dem Cluster zu managen)
CNI_PROVIDER_VERSION Die Version des CNI
ETCD_INITIAL_CLUSTER Die Adressen der initialen ETCD-Server. Meistens sind das auch die Master des Clusters.
ETCD_IP Die Adresse der ETCD-API (kann so gelassen werden, wird im Puppet durch die IP-Adresse des Servers ersetzt)
KUBE_API_ADVERTISE_ADDRESS Die Adresse der Kubernetes-API (kann so gelassen werden, wird im Puppet durch die IP-Adresse des Servers ersetzt)
INSTALL_DASHBOARD Ob das Kubernetes-Dashboard mitinstalliert werden soll oder nicht. Es ist dabei lediglich ein Deployment im Cluster.

Im nächsten Schritt binden wir das Kubernetes-Modul ein.

Das Kubernetes-Modul kann unter folgender Adresse gefunden werden:

https://forge.puppet.com/modules/puppetlabs/kubernetes

Um es nun in unserer Puppet-Umgebung zugänglich zu machen, gibt es mehrere Möglichkeiten.

Am einfachsten ist es, wenn r10k installiert ist. r10k ist ein Tool, um die Module von Puppet Forge einfacher installieren zu können. Mit dem Tool werden nun alle Puppet-Module in die Puppetfile-Datei geschrieben, was zusätzlich zur Übersicht auch gut dazu geeignet ist, mit einem Versionsverwaltungstool (wie zum Beispiel git,svn) zusammenzuarbeiten. Sollte r10k nicht installiert sein, sollte folgender Befehl ausreichen, um das Modul zu dem Puppet-Server hinzufügen zu können.

1puppet module install puppetlabs-kubernetes --version 6.3.0

Mit dem r10k-Tool https://github.com/puppetlabs/r10k fügt man folgenden Code der Datei Puppetfile in der gewünschten Puppet-Umgebung (die standardmäßig production ist) folgende Zeile hinzu:

1mod 'puppetlabs-kubernetes', '6.3.0'

Die ganze Datei sieht nun wie folgt aus:

1forge 'forge.puppetlabs.com'
2mod 'puppetlabs-kubernetes', '6.3.0'

Und es können alle darin enthaltenen Module installiert werden mittels

1/opt/puppetlabs/puppet/bin/r10k puppetfile purge
2&&  /opt/puppetlabs/puppet/bin/r10k puppetfile install

purge löscht alle vorhandenen Module, um sie mit dem nachfolgenden install-Kommando wieder neu zu installieren, um anschließend einen sauberen Stand zu haben.

Die Kubernetes-Manifeste für den Master schreiben

Die Manifeste werden nun auf die zuvor generierten yaml-Dateien abgestimmt. Erst einmal erstellen wir uns aber eine neue Profil-Klasse. Ich erstelle die Profile gern unter einem passenden Unterordner wie "k8s" oder "Kubernetes". Die Klasse nenne ich nun nur noch Master oder Worker, da sie durch den Ordner k8s oder Kubernetes das Präfix und dadurch die Aufgabe genauer erhält.

Unsere Datei füllen wir nun also mit einer leeren Klasse.

1# kubernetes::master class for all kubernetes master Server
2class profile::k8s::master{
3 
4}

Nun können wir den Inhalt definieren und müssen dort das Kubernetes-Modul einbinden. Das Einbinden des Moduls wird durch die Klasse kubernetes gemacht, die wir weiter oben installiert haben. Innerhalb der Klasse werden wir nun die verschiedenen Attribute an- und ausschalten oder die Werte vollständig ändern, um zum Beispiel Docker oder die Kubernetes-Pakete separiert zu managen (wichtig bei eigenem apt-Mirror).

Wir fangen also an, die Klasse "kubernetes" in unserem Manifest einzutragen und die nötigen Attribute zu vergeben. Da das hier der Kubernetes-Master werden soll, setzen wir auf jeden Fall das controller-Attribut auf true und das worker-Attribut auf false.

 1class {'kubernetes':
 2    controller             => true,
 3    worker                 => false,
 4    manage_kernel_modules  => true,
 5    manage_sysctl_settings => true,
 6    manage_etcd            => true,
 7    manage_docker          => false,
 8    #docker_apt_location     => 'https://download.docker.com/linux/ubuntu',
 9    #docker_apt_repos        => 'stable',
10    #docker_apt_release      => 'focal',
11    #docker_key_id           => '9DC858229FC7DD38854AE2D88D81803C0EBFCD88',
12    #docker_key_source       => 'https://download.docker.com/linux/debian/gpg',
13 
14    # Different available version can be found by apt-cache madison docker-ce
15    #docker_version          => '5:20.10.14~3-0~ubuntu-focal',
16    #docker_package_name     => 'docker-ce',
17    # kubelet_extra_arguments => ['cgroup-driver: systemd'],
18    cni_network_preinstall => 'https://docs.projectcalico.org/archive/v3.18/manifests/tigera-operator.yaml',
19    cni_network_provider   => 'https://docs.projectcalico.org/archive/v3.18/manifests/custom-resources.yaml',
20}

Innerhalb der Datei kann man nun noch gut erkennen, dass man auch das etcd und den docker-Daemon konfigurieren kann, wenn man möchte. Dafür reicht es, den manage_docker auf true und den manage_etcd auf true zu setzen.

Der Rest der Attribute, die beispielhaft hier noch stehen, sind dafür da, den Docker-Manager anzuweisen, welches Paket er ziehen soll – und woher.

Die Kubernetes-Manifeste für den Worker schreiben

Um den Worker zu installieren, kann die gleiche Datei kopiert und danach in "worker" umbenannt werden. Bei den Attributen setzen wir das controller-Attribut auf false und das worker-Attribut auf true.

Die Installation testen

Nun kann man zuerst auf dem Master ein Puppet-Run ausführen. Sobald dieser durchgelaufen ist, wird der Puppet-Run auf dem Worker ausgeführt. Wichtig hierbei ist nur, dass der Puppet-Run auf dem Master zuerst ausgeführt wird, da der Worker keinem Cluster beitreten kann, das noch nicht existiert. Nun sollte man auf den Master-Knoten gehen und sich einmal alle Kubernetes-Knoten anzeigen lassen.

Dafür gibt es den folgenden Befehl:

1kubectl get nodes

Damit werden die einzelnen Knoten und die dazugehörige Rolle ausgegeben. Hier sollten nun alle Server aufgelistet werden, die man mittels Puppet-Manifest installiert hat.

Fazit

Was können wir also aus dem Artikel mitnehmen? Wir haben eine Methode gefunden, die das Installieren neuer Kubernetes-Knoten ordentlich und auf immer gleiche Weise ausführt. Somit haben wir immer exakt gleiche Knoten, was in einer solchen Umgebung sehr wichtig ist, um Fehler aufgrund der Konfiguration ausschließen zu können.

Zusätzlich haben wir direkt eine ordentliche Dokumentation, welche Knoten welchen Job ausführen (Master oder Worker) – und auch, wie die einzelnen Server installiert werden, um so zum Beispiel bei neuen Servern das Cluster einfach und schnell erweitern zu können.

Kubernetes-Dokumentation:https://kubernetes.io/ Calico-Projekt: https://www.tigera.io/project-calico/ Puppet: https://puppet.com/ Puppet-Kubernetes-Modul: https://forge.puppet.com/modules/puppetlabs/kubernetes r10k: https://github.com/puppetlabs/r10k etcd: https://etcd.io/