Docker Swarm Cluster Ubuntu

Änderungsstand: 2022-11-18

Veraltet! Neue Version für Ubuntu 22.04 erstellt! Siehe Menuleiste.

Festgestellte Fehler: Bei Verwendung von Raspberry Pi 4 in Verbindung mit 64bit gibt es Probleme, NFS-Freigaben mit der fstab zu starten. Ich verwende deshalb für das Einhängen der Freigabe nicht die fstab sondern ein Script.

Installation Software:

Installation von Ubuntu-Server 20.04 (LTS-64bit) auf einem RaspberryPi 3 und/oder RaspberryPi 4 – HEADLESS.

Es war nie einfacher, 4 SD-Cards für einen Cluster vorzubereiten. Das Ganze, ohne Monitor und Tastatur an einem Raspi zu verwenden.

Ich bereite meine 4 RaspberryPi für einen Cluster vor. Dafür installiere ich als Software das Ubuntu-Server 64bit und richte eine statische IP-Adresse ein. Meine IP-Adressen für die 4 Raspis im Cluster sind | 192.168.1.131 | 192.168.1.132 | 192.168.1.133 | 192.168.1.134 |.

Download des Images: https://ubuntu.com/download/raspberry-pi

Das heruntergeladenen Image mittels „Raspberry Pi Imager“ auf einer SD-Card oder SSD bringen. Anschließend die Datei „network-config“ mit Notepad++ öffnen (unter Umständen muss die SD-Card entfernt und wieder eingesetzt werden, damit das Verzeichnis sichtbar wird) und folgendes ersetzen (an alle 4 Raspis anwenden):

SD-Card für Raspi 1

ethernets:
  eth0:
    dhcp4: true
    optional: true

Ersetzen mit:

ethernets:
  eth0:
    addresses:
      - 192.168.1.131/24
    gateway4: 192.168.1.1
    nameservers:
      addresses: [192.168.1.1]
    optional: true

Datei speichern.

SD-Card für Raspi 2

ethernets:
  eth0:
    dhcp4: true
    optional: true

Ersetzen mit:

ethernets:
  eth0:
    addresses:
      - 192.168.1.132/24
    gateway4: 192.168.1.1
    nameservers:
      addresses: [192.168.1.1]
    optional: true

Datei speichern.

SD-Card für Raspi 3

ethernets:
  eth0:
    dhcp4: true
    optional: true

Ersetzen mit:

ethernets:
  eth0:
    addresses:
      - 192.168.1.133/24
    gateway4: 192.168.1.1
    nameservers:
      addresses: [192.168.1.1]
    optional: true

Datei speichern.

SD-Card für Raspi 4

ethernets:
  eth0:
    dhcp4: true
    optional: true

Ersetzen mit:

ethernets:
  eth0:
    addresses:
      - 192.168.1.134/24
    gateway4: 192.168.1.1
    nameservers:
      addresses: [192.168.1.1]
    optional: true

Datei speichern.

Nun die SD-Cards in die jeweiligen Raspis gesteckt, Netzwerkkabel angeschlossen und gestartet. Nach ca. 2 Minuten kann man sich auch schon per SSH einloggen. Ich verwende dafür Putty als SSH Client.

Login: ubuntu Password: ubuntu

Jetzt wird man aufgefordert, das Passwort zu ändern. Dazu ubuntu eingeben, Enter und anschließend sein eigenes Passwort eintragen, Enter. Dieses nochmals bestätigen, Enter. Fertig.

Installation Cluster Swarm:

Erster Raspi (Manager 1)

Meine statische IP: 192.168.1.131

Hostname ändern, falls noch nicht erledigt:

sudo hostnamectl set-hostname node1

Zweiter Raspi (Manager 2)

Meine statische IP: 192.168.1.132

Hostname ändern, falls noch nicht erledigt:

sudo hostnamectl set-hostname node2

Dritter Raspi (Worker 1)

Meine statische IP: 192.168.1.133

Hostname ändern, falls noch nicht erledigt:

sudo hostnamectl set-hostname node3

Vierter Raspi (Worker 2)

Meine statische IP: 192.168.1.134

Hostname ändern, falls noch nicht erledigt:

sudo hostnamectl set-hostname node4

Folgendes gilt identisch für alle Nodes:

Optional Start:

Zuerst füge ich alle Nodes in allen Host-Dateien hinzu.

sudo nano /etc/hosts

Folgendes am Ende hinzufügen:

# Cluster-Nodes
192.168.1.131   node1 manager-01
192.168.1.132   node2 manager-02
192.168.1.133   node3 worker-01

Weitere Nodes erfordern weitere Einträge. Soweit selbsterklärend…

Optional Ende.

Jetzt zusätzliche Software installieren (vorerst wird hierfür nicht alles essentiell benötigt, dafür im Nachgang etwas Arbeit erspart):

sudo apt update && sudo apt upgrade -y && sudo apt install -y mc git ca-certificates curl gnupg lsb-release software-properties-common apt-transport-https linux-modules-extra-raspi

Nun Docker-Key hinzufügen:

sudo mkdir -p /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo apt update

Docker-Repository hinzufügen:

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo chmod a+r /etc/apt/keyrings/docker.gpg
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce -y

Überprüfen, ob alles soweit funktioniert hat:

systemctl status docker

Die Ausgabe sollte etwa so aussehen:

● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-10-22 11:14:49 UTC; 1min 45s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 34310 (dockerd)
      Tasks: 9
     Memory: 22.9M
        CPU: 980ms
     CGroup: /system.slice/docker.service
             └─34310 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Gegebenenfalls mit strg-c beenden.

sudo reboot

Benutzer zur Docker-Gruppe hinzufügen:

sudo usermod -aG docker ${USER}
newgrp docker

Jetzt wieder beachten, auf welchem Node man sich befindet:

Erster Raspi (Manager 1)

Erstelle Docker Swarm Cluster:

sudo docker swarm init --advertise-addr 192.168.1.131

Die Ausgabe, für die Worker, nach „To add a worker to this swarm, run the following command:“ für die Worker kopieren und dort einfügen! docker swarm join –token …..

sudo docker swarm join-token manager

Die Ausgabe, für einen oder mehreren Manager, wenn verwendet, kopieren und im jeweiligen node für Manager reinkopieren! docker swarm join –token …..

Zweiter Raspi (Manager 2)

Die kopierte Ausgabe von node1, für einen weiteren Manager, einfügen! sudo docker swarm join –token …..

(das sudo davor nicht vergessen!)

Dritter Raspi (Worker 1)

Die kopierte Ausgabe von node1, für die Worker, einfügen! sudo docker swarm join –token …..

(das sudo davor nicht vergessen!)

Vierter Raspi (Worker 2)

Die kopierte Ausgabe von node1, für die Worker, einfügen! sudo docker swarm join –token …..

(das sudo davor nicht vergessen!)

Wurden alle Raspis soweit eingerichtet, prüfe ich am ersten Raspi, meinem Hauptmanager (node1), mit folgendem Befehl, ob alles übernommen wurde:

sudo docker node ls

Die Ausgabe:

pi@node1:~ $ sudo docker node ls
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
ph8befzsxehjkkjpy6m3qa4mo *   node1      Ready     Active         Leader           20.10.8
tfk0w8cyv88ghjszpg9w26n8p     node2      Ready     Active         Reachable        20.10.8
tszt0aa16fzb9uew8k4lxx094     node3      Ready     Active                          20.10.8
5vdwr2awbn571bj5b5x2aavly     node4      Ready     Active                          20.10.8
pi@node1:~ $

Passt. Alles in Ordnung. Die Raspis wurden nun vorbereitet und sind für Docker Swarm Cluster bereit.

Ich möchte Tage oder Wochen später einen weiteren Worker im Swarm hinzufügen? Dafür setze ich am node1 folgenden Befehl ab:

sudo docker swarm join-token worker

Die Ausgabe wie schon bekannt mit einem sudo davor am hinzuzufügendem Worker eintragen.

Für einen weiteren Manager, am ersten node:

sudo docker swarm join-token manager

Ab nun arbeite ich am ersten Raspi (node1) weiter.

Zuerst installiere ich zum Test einen Webservice, um zu schauen, ob alles soweit funktioniert. Des Weiteren bekommt man hier schon einen Einblick, wie man einen Service über die Kommandozeile installiert und repliziert.

sudo docker service create --name web-server --publish 3200:80 ghcr.io/linuxserver/nginx

Nun repliziere ich diesen Web-Service auf weitere Nodes. Die letzte Ziffer gibt an, auf wie vielen Nodes insgesamt repliziert wird.

sudo docker service scale web-server=2
sudo docker service ls
cluster@node1:~$ sudo docker service ls
ID             NAME                  MODE         REPLICAS   IMAGE                              PORTS
g546zd76f42b   web-server            replicated   2/2        ghcr.io/linuxserver/nginx:latest   *:3200->80/tcp

Aufruf im Browser: 192.168.1.131:3200

Portainer:

Ich installiere nun Portainer im Swarm-Mode. Mit dem Portainer wird der benötigte „Agent“ mit installiert, der sich um die komplette Verwaltung kümmert.

sudo curl -L https://downloads.portainer.io/portainer-agent-stack.yml -o portainer-agent-stack.yml

Um diesen Docker zu starten, folgenden Befehl verwenden:

sudo docker stack deploy -c portainer-agent-stack.yml portainer

Überprüfen, ob die Services gestartet wurden:

sudo docker service ls
ubuntu@node1:~$ sudo docker service ls
ID             NAME                  MODE         REPLICAS   IMAGE                              PORTS
3gudskr4hw9j   portainer_agent       global       1/1        portainer/agent:2.11.1
rog3nz3j65hf   portainer_portainer   replicated   1/1        portainer/portainer-ce:2.11.1      *:8000->8000/tcp, *:9000->9000/tcp, *:9443->9443/tcp
g546zd76f42b   web-server            replicated   2/2        ghcr.io/linuxserver/nginx:latest   *:3200->80/tcp
ubuntu@node1:~$

Portainer wurde installiert, die Services wurden gestartet und der „Agent“ kümmert sich um die Verteilung bzw. die Abarbeitung im Knoten. Um Portainer zu öffnen, im Browser die IP-Adresse des ersten Raspi:9000 eingeben: 192.168.1.131:9000

Die Initialisierung dauert etwas. Bitte ca. 1 Minute warten, bevor man Portainer im Browser öffnet.

Eine NFS-Freigabe einbinden – wozu?

Wozu benötigt man eigentlich ein Freigabelaufwerk? Nun, die anfallenden Daten der erstellen Docker sind im Normalfall nicht persistent. Das heißt, wenn der Docker Crasht oder gelöscht wird, sind die Daten weg. Abhilfe schafft die Angabe von Volumes im Compose-Script oder im Docker-Script. Dort kann man dem Docker mitteilen, dass die anfallenden Daten, wie z.B. Konfigurationsdateien, auf einer Freigabe abgelegt werden sollen. Wird der Docker gelöscht und anschließend wieder installiert und man gibt das selbe Verzeichnis als Volume wieder an, werden die sich darin befindlichen Daten wieder verwendet. Da ich aber hier in einem Cluster arbeite, macht es wenig Sinn, die Daten auf der SD-Card zu schreiben, weil der Manager auch andere Raspis zuweisen kann. Und dann wären die Daten dort nicht vorhanden. Abhilfe schafft also dementsprechend eine externe Freigabe (in meinem Beispiel eine HDD), auf welcher alle Raspis im Cluster zugreifen können. Diese HDD muss nicht zwingend an einem Raspi im Cluster hängen. Man könnte auch eine Freigabe eines NAS verwenden oder eine HDD auf einem anderen Raspi, der nicht im Cluster hängt. Hier sind der Kreativität keine Grenzen gesetzt.

Meine Empfehlung hierzu ist, eine ext. Platte am Manager oder am Worker anzubinden oder eine NFS-Freigabe eines anderen Servers zu verwenden. Hier ist die Ausfallwahrscheinlichkeit geringer, dafür die Zugriffszeit paar Millisekunden höher, es sei denn, die Platten dort fahren nach gewisser Zeit in einem Standby. Dann dauert der Zugriff erheblich länger. Ideal wäre hierfür entweder ein ZFS-System oder, wie man oft im Unraid-Server findet, einen Cache für die Freigabe zu verwenden oder einen Server, ohne Spindown der Laufwerke, zu benutzen. Das sind allerdings nur Anhaltspunkte, die ich in meinem hier dargestellten Beispiel nicht berücksichtige.

Da ich einen Server habe, lege ich meine NFS-Freigabe dort hin. Wer keinen Server oder NAS hat, kann auch eine ext. Festplatte an einem Node oder an einem separaten RaspberryPi, außerhalb des Clusters anbinden.

NFS-Freigabe von Worker 1 verwenden:

Ich schließe zum Test eine weitere SSD am ersten Worker an:

lsblk

Meine erkannte SSD hat die Bezeichnung sdb. Diese lösche nun und schreibe das Label neu:

sudo parted /dev/sdb "mklabel gpt"
Yes
sudo mkfs.ext4 /dev/sdb
y

Die Platte wurde formatiert. Jetzt binde ich diese ein:

sudo mkdir -p /mnt/data
sudo mount /dev/sdb /mnt/data
sudo chown nobody:nogroup /mnt/data/

Das Laufwerk wurde unter /mnt/data gemountet und ist verfügbar.

Jetzt schreibe ich für das Einbinden dieser Freigabe, ein Script, da die fstab leider nur sporadisch funktioniert (RPI4).

sudo su
cd && mkdir -p scriptfiles
cd && cd scriptfiles && nano mountscript.sh

Folgendes eintragen und ggnf. Server-IP und Pfade anpassen:

#!/bin/bash
sudo mount /dev/sdb /mnt/data
#

Strg-x, y, Enter

sudo chmod 700 mountscript.sh

Zum Aufruf des Scriptes folgenden Befehl eingeben:

sudo ./mountscript.sh

Das Laufwerk wurde eingehängt. Um das Script nach einem Serverneustart zu verwenden, gehe ich wie folgt vor:

sudo crontab -e

Folgendes gebe ich am Ende ein:

# Mount ssd /mnt/ssd1
@reboot sleep 20 && ~/scriptfiles/mountscript.sh >/dev/null 2>&1
#

Strg-x, y, Enter

sudo reboot

20 Sekunden nach Restart wird die Platte eingehängt und ist verfügbar.

Nun die eigentliche NFS-Freigabe:

Folgendes auf dem Host (in meinem Fall Worker1 – wo die Platte dran hängt) installieren:

sudo apt install nfs-kernel-server
sudo systemctl enable --now nfs-server
sudo nano /etc/exports

Folgendes trage ich am Ende der Datei ein:

 /mnt/data          192.168.1.0/24(rw,no_root_squash)
#

Strg-x, y, Enter

sudo exportfs -rav

Folgendes auf allen restlichen Nodes installieren:

sudo apt install -y nfs-common
sudo mkdir -p /mnt/data
sudo chmod 777 /mnt/data/
sudo chown nobody:nogroup /mnt/data/

Jetzt schreibe ich für das Einbinden dieser Freigabe, ein weiteres Script:

sudo su
cd && mkdir -p scriptfiles
cd && cd scriptfiles && nano nfsmountscript.sh

Folgendes eintragen und ggnf. Server-IP und Pfade anpassen:

#!/bin/bash
sudo mount -t nfs 192.168.1.153:/mnt/user/nfspath /mnt/data
#

Strg-x, y, Enter

sudo chmod 700 nfsmountscript.sh

Zum Aufruf des Scriptes folgenden Befehl eingeben:

sudo ./nfsmountscript.sh

Die Freigabe wurde eingehängt. Um das Script nach einem Serverneustart zu verwenden, gehe ich wie folgt vor:

sudo crontab -e

Folgendes gebe ich am Ende ein:

# Mount NFS-Freigabe
@reboot sleep 20 && ~/scriptfiles/nfsmountscript.sh >/dev/null 2>&1
#

Strg-x, y, Enter

sudo reboot

20 Sekunden nach Restart wird die Freigabe eingehängt und ist verfügbar.

Jetzt noch ein Test, ob das auch funktioniert. Ich erstelle unter /mnt/data eine fake-Datei und schaue, ob diese auch an den anderen Nodes verfügbar ist:

sudo touch /mnt/data/testdatei.txt

Nun gebe ich folgenden Befehl auf allen anderen Nodes ein:

cd /mnt/data && ls

Die Datei testdatei.txt sollte nun auf allen Nodes sichtbar sein.

Sollte man die einzelnen Nodes neu starten müssen, starte ich immer den Node mit der ext. Datenplatte zuerst.

Weiter im Text…

NFS-Freigabe von einem Unraid-Server verwenden:

Bsp.: Ich erstelle eine Freigabe auf meinem Unraid-Server (192.168.1.153) Namens nfspath und gebe bei den „NFS Sicherheitseinstellungen“ – „Exportieren: Ja“ ein.

Folgendes identisch auf allen Nodes anwenden:

sudo apt install -y nfs-common
sudo mkdir -p /mnt/data
sudo chmod 777 /mnt/data/

Jetzt schreibe ich für das Einbinden dieser Freigabe, ein Script, da die fstab leider nur sporadisch funktioniert (RPI4).

sudo su
cd && mkdir -p scriptfiles
cd && cd scriptfiles && nano mountscript.sh

Folgendes eintragen und ggnf. Server-IP und Pfade anpassen:

#!/bin/bash
sudo mount -t nfs 192.168.1.153:/mnt/user/nfspath /mnt/data
#

Strg-x, y, Enter

sudo chmod 700 mountscript.sh

Zum Aufruf des Scriptes folgenden Befehl eingeben:

sudo ./mountscript.sh

Die Freigabe wurde eingehängt. Um das Script nach einem Serverneustart zu verwenden, gehe ich wie folgt vor:

sudo crontab -e

Folgendes gebe ich am Ende ein:

# Mount NFS-Freigabe
@reboot sleep 20 && ~/scriptfiles/mountscript.sh >/dev/null 2>&1
#

Strg-x, y, Enter

sudo reboot

20 Sekunden nach Restart wird die Freigabe eingehängt und ist verfügbar.

Der Cluster ist komplett eingerichtet, betriebsbereit und eine externe Freigabe, als NFS-Freigabe, verfügbar.

Jetzt unbedingt ein Test, ob auch alle Nodes auf die Freigabe zugreifen können. Ich erstelle auf einem Node in der Freigabe eine Datei und schaue, ob diese Datei auch auf allen anderen Nodes angezeigt wird:

Node 1:

sudo touch /mnt/data/testdatei.txt

Nun gebe ich folgenden Befehl auf allen anderen Nodes ein:

cd /mnt/data && ls

Die Datei testdatei.txt sollte nun auf allen Nodes sichtbar sein.

Optional Start: Backup der SD-Card (eigene Werte beachten, NFS-Freigabe am Zielserver setzen, diese am Raspi mounten und dd-Backup starten [alles ohne fstab-Eintrag]):

Mein Backupordner am Unraid-Server, inkl. NFS-Freigabe: /mnt/user/backups/node1

sudo mkdir -p /mnt/backup
sudo mount 192.168.1.153:/mnt/user/backups/node1 /mnt/backup
sudo dd if=/dev/mmcblk0 of=/mnt/backup/node1.img bs=4MB status=progress
sudo reboot

Optional Ende.

Optional Start: Backup der SD-Card (eigene Werte beachten, NFS-Freigabe am Zielserver setzen, diese am Raspi mounten und dd-Backup starten [alles ohne fstab-Eintrag]):

Mein Backupordner am Unraid-Server, inkl. NFS-Freigabe: /mnt/user/backups/node2

sudo mkdir -p /mnt/backup
sudo mount 192.168.1.153:/mnt/user/backups/node2 /mnt/backup
sudo dd if=/dev/mmcblk0 of=/mnt/backup/node2.img bs=4MB status=progress
sudo reboot

Optional Ende.

Optional Start: Backup der SD-Card (eigene Werte beachten, NFS-Freigabe am Zielserver setzen, diese am Raspi mounten und dd-Backup starten [alles ohne fstab-Eintrag]):

Mein Backupordner am Unraid-Server, inkl. NFS-Freigabe: /mnt/user/backups/node3

sudo mkdir -p /mnt/backup
sudo mount 192.168.1.153:/mnt/user/backups/node3 /mnt/backup
sudo dd if=/dev/mmcblk0 of=/mnt/backup/node3.img bs=4MB status=progress
sudo reboot

Optional Ende.

Optional Start: Backup der SD-Card (eigene Werte beachten, NFS-Freigabe am Zielserver setzen, diese am Raspi mounten und dd-Backup starten [alles ohne fstab-Eintrag]):

Mein Backupordner am Unraid-Server, inkl. NFS-Freigabe: /mnt/user/backups/node4

sudo mkdir -p /mnt/backup
sudo mount 192.168.1.153:/mnt/user/backups/node4 /mnt/backup
sudo dd if=/dev/mmcblk0 of=/mnt/backup/node4.img bs=4MB status=progress
sudo reboot

Optional Ende.

.

Quelle(n):

https://docs.docker.com/engine/install/debian/

https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/

https://documentation.portainer.io/v2.0/deploy/ceinstallswarm/

https://computingforgeeks.com/how-to-install-docker-swarm-on-ubuntu/

https://docs.docker.com/engine/install/ubuntu/

Erstelle eine Website wie diese mit WordPress.com
Jetzt starten