Himbeerschwarm

Docker-Schwarm mit dem Raspberry Pi aufbauen
2 Kommentare

Wir beschäftigen uns in diesem Artikel damit, wie man Docker auf dem Raspberry Pi installiert und betreibt. Als Beispiel dient ein einfaches Docker Image, das eine JSP-Seite mit Tomcat ausliefert. Am Ende des Artikels bauen wir einen Schwarm mit drei Raspberry Pis auf.

Mit Docker kann man Anwendungen mit all ihren Abhängigkeiten in Images installieren. Die Images sind dann leicht zwischen Systemen austauschbar. Es gibt mittlerweile schon eine Vielzahl vorkonfigurierter Images, die man frei verwenden und für die eigenen Bedürfnisse anpassen kann. Durch ihre Verwendung spart man viel Zeit und Ressourcen. Wird ein Image gestartet, entsteht ein Container. Images und Container verhalten sich ähnlich zueinander wie Java-Klassen und deren Instanzen. Es können theoretisch beliebig viele Container aus einem Image erzeugt werden. Die einzige Begrenzung ist die Hardware, auf der die Container laufen. Diese Grenze lässt sich allerdings leicht nach oben verschieben, wenn man einen Docker-Schwarm einsetzt. Mit einem Schwarm kann man mehrere Docker-Installationen, die auf verschiedenen Rechnern laufen, miteinander verbinden. Sie lassen sich dann wie eine Installation verwalten. Ein Docker-Schwarm bietet eine gewisse Toleranz gegen Fehler. Bricht ein Rechner im Schwarm weg, können die übrigen Rechner seine Aufgaben automatisch übernehmen.

Die Images werden während der Laufzeit eines Containers nicht verändert. Die Container halten ihre eigenen Daten in einem Overlay Filesystem. Jeder neu gestartete Container hat zu Beginn die gleichen Daten wie das Image, aus dem er entstanden ist. Es gibt allerdings auch die Möglichkeit, einen Container mit seinen Daten wieder zu starten. Darauf gehen wir später noch einmal genauer ein. Zusätzlich stellt Docker sogenannte Volumes bereit. Mit ihnen ist es möglich, Teile des Host-Filesystems in einem Container zu verwenden und dadurch Daten persistent unabhängig von Status des Containers zu speichern. Es gibt zwei Varianten von Docker:

  • Docker EE (Enterprise Edition) ist für den professionellen Einsatz gedacht und verursacht Kosten.

  • Docker CE (Community Edition) kann frei heruntergeladen und verwendet werden.

In diesem Artikel verwenden wir Docker CE.

Set-up

Für unseren Schwarm verwenden wir drei Raspberry Pis vom Typ 3. Darauf installieren wir das aktuelle Raspbian Buster Lite. Wir verwenden die Lite-Variante, weil sie keine unnötigen Programme beinhaltet. Sie können Raspbian Buster Lite frei von der Downloadseite des Raspberry-Projekts herunterladen [1]. Wenn Sie das ZIP-File auf Ihrer Platte gespeichert haben, müssen Sie es nur noch entpacken und mit einem Tool Ihrer Wahl auf die SD-Karten schreiben. Wir benötigen für jeden Raspberry eine Karte. Es dauert schon ein Weilchen, bis man alle Karten beschrieben hat. ITler, die sich mit Murphys Gesetz auskennen („Alles, was schiefgehen kann, wird auch schiefgehen.“), werden sicherheitshalber gleich eine Karte mehr vorbereiten.

Um später nicht durcheinander zu kommen, sollten Sie jeden Raspberry eindeutig beschriften. Neben der eindeutigen Beschriftung hilft es auch, den Raspberries feste IP-Adressen und eindeutige Hostnamen zu geben. In Tabelle 1 können Sie beispielhaft sehen, wie man die Zuordnung vornehmen kann. Sie müssen hier natürlich IPs wählen, die in Ihrem eigenen Netzwerk funktionieren. Abbildung 1 zeigt den beschrifteten Raspberry-Schwarm. Da die Raspberries doch recht warm werden können, wenn sie so dicht beieinanderstehen, sollte man ruhig mit einem Lüfter für etwas Durchzug sorgen.

Abb. 1: Der Raspberry-Schwarm

Abb. 1: Der Raspberry-Schwarm

Hostname IP Beschriftung
docker01 192.168.3.70 01
docker02 192.168.3.71 02
docker03 192.168.3.72 03

Tabelle 1: Konfigurationsbeispiel des Schwarms

Wenn Sie alles soweit vorbereitet haben, wird es Zeit, die SD-Karten in die Raspberries zu stecken. Wir beschreiben hier am Beispiel des Rechners docker01, was für die Einrichtung zu tun ist. Für die beiden weiteren Rechner sind analog die gleichen Schritte durchzuführen.

Beim ersten Booten passt der Raspberry die Größe des Root-Filesystems auf die verwendete SD-Karte an, danach bootet er automatisch neu. Nach dem Reboot können wir uns wie gewohnt auf dem Raspberry (User: pi Passwort: raspberry) einloggen. Als Erstes sollten Sie den SSH-Server auf dem Raspberry aktivieren, damit Sie von Ihrem PC aus auf den Raspberry zugreifen können. Das erledigen Sie mit dem Kommandozeilentool raspi-config. Das Tool muss mit Superuserrechten ausgeführt werden (sudo raspi-config). Im Menüpunkt Interfacing Options | P2 SSH können Sie nun den SSH-Server aktivieren.

Ab jetzt können Sie von Ihrem PC aus weiterarbeiten. Linux-User können hierfür einfach das Kommando ssh pi@<RASPI_IP> verwenden. Wenn Sie ein anderes Betriebssystem verwenden, müssen Sie sich ein Tool wie Putty herunterladen, um die SSH-Verbindung herstellen zu können.

Wir geben nun jedem Raspberry eine feste IP-Adresse, das macht es uns leichter, die Raspberries zu unterscheiden. Um das zu erreichen, editieren wir die Datei, in der die Netzwerkeinstellungen abgelegt sind (sudo nano /etc/dhcpcd.conf). In Listing 1 können Sie die Stelle in der Datei sehen, die verändert werden muss, um eine feste IP zu verwenden.

# Example static IP configuration:
interface eth0
static ip_address=192.168.3.70/24
static routers=192.168.3.1
static domain_name_servers=192.168.3.1

Die Hostnamen der Raspberries sind momentan noch alle gleich. Daher sollten wir auch diese noch ändern. In der Datei /etc/hostname steht der Hostname eines Unix-Rechners. Bitte öffnen Sie die Datei mit Root-Rechten und ändern den Namen (sudo nano /etc/hostname). Nun gibt es noch eine zweite Datei, die angepasst werden sollte: /etc/hosts. Hier sollten sie auch den Namen raspberry gegen den entsprechenden Namen austauschen: docker0x. Auf die Datei /etc/hosts kommen wir im nächsten Abschnitt noch einmal zu sprechen. Damit unsere Änderungen wirksam werden, sollten Sie den Raspberry rebooten (sudo reboot).

Nun können wir per IP-Adresse auf die drei Rasberries zugreifen. Es wäre doch schön, wenn wir die Hostnamen anstelle der IP verwenden könnten. Ein einfacher Weg dorthin ist es, die Hostnamen mit den IP-Adressen in die hosts-Datei einzutragen. Mit der hosts-Datei ist es möglich, eine lokale Namensauflösung zu verwenden. Auf Linux-Systemen ist die Datei unter /etc/ zu finden. Bei Windows befindet sie sich unter c:\windows\system32\drivers\etc\. Um sie bearbeiten zu können, benötigt man Superuserberechtigung. Sie können nun einfach, wie in Listing 2 zu sehen ist, die Hostnamen und die IPs zu der Datei hinzufügen. Sobald die Datei gespeichert ist, können Sie die Namen verwenden, um auf die Raspberries zuzugreifen. Die Namen funktionieren aber nur auf dem Rechner, auf den die hosts-Datei angepasst ist. Sie können nun auch die hosts-Dateien auf den Raspberries so anpassen, dass sie sich untereinander per Name ansprechen können.

# Raspberry Docker Server
192.168.3.70 docker01
192.168.3.71 docker02
192.168.3.72 docker03

Wir haben nun die Vorbereitungen auf allen Raspberries abgeschlossen und können beginnen, die eigentliche Docker-Installation zu starten.

Docker installieren

Die Installation von Docker auf den Raspberries gestaltet sich recht einfach. Im ersten Schritt bringen wir die Raspberries auf den neuesten Stand. Anschließend kann man mit curl ein Installationsskript herunterladen und ausführen.

sudo apt update
sudo apt upgrade
sudo curl -fsSL https://get.docker.com | sh

Das Skript braucht eine Weile, aber erledigt dafür alle Installationsschritte automatisch. Um zu überprüfen, ob die Installation erfolgreich war, können Sie das Kommando docker version verwenden. Es sollte Ihnen die Version der Docker-Installation ausgeben. Damit wir Docker nicht als Root-User ausführen müssen, fügen wir nun mit dem Kommando sudo usermod -aG docker pi den User pi der Gruppe docker hinzu. Damit die Gruppenänderung wirksam wird, müssen Sie sich einmal aus- und wieder einloggen. Jetzt sind alle Vorbereitungen abgeschlossen und wir können uns mit Docker beschäftigen.

Grundlagen von Docker

Bevor wir beginnen, einen Schwarm mit Docker aufzubauen, sollten wir erst einmal ausprobieren, was man alles mit einer einzelnen Installation machen kann. Im ersten Schritt klären wir einige Begrifflichkeiten, mit denen wir gleich arbeiten werden:

  • Basis-Images: Das Basis-Image ist immer der Ausgangspunkt zur Erstellung eigener Images. Die Basis-Images können frei von der Docker-Website [2] heruntergeladen werden. Um einen Überblick zu bekommen, welche Images es gibt, können Sie die Suchfunktion der Seite verwenden [3]. Um Pakete für den Raspberry zu finden, müssen Sie bei Architectures den Haken bei arm64 setzen. Nun sollten Sie noch die Official Images auswählen, damit Sie nur getestete Images angezeigt bekommen. Es bleibt eine überschaubare Anzahl von etwas über 110 Images übrig. Basis-Images werden beim ersten Bauen eines eigenen Image automatisch heruntergeladen. Das kann allerdings etwas dauern. In einem Basis-Image befindet sich üblicherweise nur ein Service, den Sie erst für Ihre Zwecke anpassen müssen.

  • Dockerfile: Das Dokerfile steuert, aus welchem Basis-Image Ihr eigenes Image erstellt werden soll. Weiterhin beschreibt das Dockerfile, welche Modifikationen am Image benötigt werden. Das Dockerfile hat sinnvoller Weise den Dateinamen Dockerfile.

  • Docker Image: Ein Docker Image entsteht, wenn ein Basis-Image mit Hilfe des Dockerfiles angepasst wurde. Wenn wir bei dem Vergleich mit Java bleiben, ist das Docker Image so etwas wie eine abgeleitete Klasse der Basisklasse. Es beinhaltet alle Anpassungen, damit unsere Applikation so läuft, wie wir uns das vorstellen.

  • Container: Der Docker-Container ist eine Instanz eines Image. Es ist also eine Kopie des Image, die ausgeführt wird. Container können aus eigenen Docker Images oder direkt aus Basis-Images erstellt werden. Der Container hat ein Filesystem, in dem er Daten speichern kann.

Um ein eigenes Docker Image zu erstellen, benötigen wir zunächst ein Projektverzeichnis, in dem wir das Dockerfile ablegen: mkdir image_tomcat. Unser Image soll eine einfache JSP-Seite, die einige Daten anzeigt, ausliefern. Den Source Code der JSP-Seite können Sie in Listing 3 sehen. Die Seite gibt den Servernamen und den Timestamp des Servers aus. Das machen wir, damit wir später wissen, von welchem Knoten die Seite erstellt wurde. Der Timestamp ist eingebaut, damit sich die Seite bei jedem Aufruf verändert. In Listing 4 können Sie das Dockerfile sehen, das unser Image erzeugt. Die erste Zeile gibt an, aus welchem Basis-Image unser Image erstellt werden soll. Die zweite Zeile definiert, welche Zeitzone es haben soll. Mit der ENV-Klausel können beliebige Umgebungsvariablen des Image verändert oder gesetzt werden. Die folgende Zeile beschreibt, wie das Image modifiziert werden soll. In unserem Fall wird ein Verzeichnis im Tomcat webapps-Ordner angelegt. In dieses Verzeichnis kopieren wir die Datei index.jsp. In unserem Beispiel befindet sich die JSP-Datei im selben Verzeichnis wie das Dockerfile. Im Dockerfile kann man natürlich viel mehr als die hier gezeigten Kommandos verwenden. Eine Übersicht der Möglichkeiten finden Sie in Tabelle 2.

FROM tomcat:8.0
ENV TZ="Europe/Berlin"
COPY ./index.jsp /usr/local/tomcat/webapps/myapp/index.jsp
<!DOCTYPE HTML>
<HTML>
<HEAD>
<TITLE>Hallo Raspberry</TITLE>
</HEAD>
<BODY>
Servername: <% try{ out.println(InetAddress.getLocalHost());}catch(Exception e){}; %><BR>
Serverzeit: <%=System.currentTimeMillis() %><BR> 
</BODY>
</HTML>
Kommando Funktion
ADD Datei in das Image kopieren
CMD Kommando beim Start des Containers ausführen (Parameter verändern)
COPY Datei aus aktuellem Verzeichnis in das Image kopieren
ENTRYPOINT Wie CMD, kann aber nicht von außen überschrieben werden
ENV Umgebungsvariablen setzen
EXPOSE Ports des Containers
FROM Welches Basis-Image soll verwendet werden?
LABLE Metadaten für das Image setzen
RUN Kommando beim Start des Containers ausführen (Software installieren)
USER User zur Ausführung von CMD, RUN, ENTRYPOINT
VOLUME Volumenverzeichnis
WORKDIR Arbeitsverzeichnis festlegen

Tabelle 2: Übersicht der Kommandos im Dockerfile

Um nun unser Docker Image zu erstellen, wechseln wir in das Verzeichnis mit dem Dockerfile (cd image_tomcat) und führen das Kommando docker build -t image_tomcat. aus. Hierbei ist zu beachten, dass der Punkt am Ende bedeutet, dass das Dockerfile aus dem aktuellen Verzeichnis genommen werden soll. Der Parameter -t gibt an, welchen Namen das Image bekommen soll. Mit dem Kommando docker images können wir nun schauen, welche Images in unserer Docker-Umgebung existieren. Sollten Sie einmal in die Verlegenheit kommen, ein Image löschen zu müssen, können Sie das mit dem Kommando docker rmi <Liste von Image- IDs|Namen> erledigen. Ein Image lässt sich nur löschen, wenn es von keinem Container oder anderen Images mehr referenziert wird. Nun starten wir mit dem folgenden Kommando einen Container aus unserem Image: docker run -p 80:8080 –name container_tomcat -h host_tomcat image_tomcat.

Das Terminal wird nun für die Ausgaben des Tomcat-Servers verwendet. Port 8080 des Containers wird auf Port 80 des Hostsystems umgeleitet. Sie können nun einen Browser öffnen und sich die JSP-Seite ansehen (Abb. 2). Bitte öffnen Sie nun eine zweite SSH-Verbindung zum Docker-Host. Mit dem Kommando docker ps -a können Sie sich nun ansehen, welche Container laufen oder gelaufen sind.

Abb. 2: Die JSP-Seite im Browser

Abb. 2: Die JSP-Seite im Browser

Wenn Sie die Parameter für Namen des Containers und Hosts nicht mit angeben, werden diese zufällig gewählt. Das hat den Vorteil, dass man schnell viele Hosts zum Testen erzeugen kann. Einen Container können Sie mit dem Kommando docker rm<Liste von Container- IDs|Namen> wieder löschen.

Oft ist die Dokumentation über den internen Aufbau von Containern etwas spärlich. Die Grundidee ist ja auch, alles zu verbergen, was nicht unbedingt zum Betrieb gebraucht wird. Sollten Sie dennoch einmal schauen wollen, wie es in einem Container aussieht, kann Ihnen folgendes Kommando dabei helfen: docker exec -it <Container- ID|Name> /bin/bash. Es baut eine interaktive Verbindung zum Container auf und startet eine Shell, die an das aktuelle Terminal gebunden wird. Mit dieser Shell können wir innerhalb des Containers arbeiten. Die Shell hat Root-Rechte und wir können damit sogar Software innerhalb des Containers installieren.

Mit dem Kommando docker stop <Container ID/Name> können Sie einen laufenden Container anhalten. Um ihn wieder zu starten, wird das Kommando docker start <Container ID/Name> verwendet. Der Unterschied zwischen docker start und docker run ist leicht zu erklären. run erstellt einen komplett neuen Container, start startet einen bereits vorhandenen Container. Ein vorhandener Container beinhaltet bereits Daten von seinem letzten Lauf. Dieses kleine Detail sollten Sie im Hinterkopf behalten.

Nun haben Sie die Grundlagen von Docker und die wichtigsten Kommandos kennengelernt. Daher möchte ich an dieser Stelle den Abschnitt über die Grundlagen von Docker abschließen. Tabelle 3 gibt Ihnen eine Übersicht aller Docker-Kommandos und ihrer jeweiligen Funktion.

Kommando Funktion Kommando Funktion
attach Container mit Terminal verbinden (Stdin, Stdout) port Portmappings anzeigen
build Image aus einem Dockerfile erstellen/updaten ps Container anzeigen
commit Neues Image aus einem Container erstellen (alle Änderungen des Containers sind dann Bestandteil des Image) pull Image aus Registry holen
cp Dateien kopieren (lokales FS, Container) push Image in Registry verschieben
create Neuen Container erstellen rename Container umbenennen
diff Änderungen untersuchen restart Container neu starten
events Echtzeitereignisse auswerten rm Container löschen
exec Kommando im Container ausführen rmi Images löschen
export Container in ein Archiv exportieren run Container erstellen und starten
history Historie eines Image anzeigen save Images in ein Archiv speichern
images Liste der Images anzeigen search Auf Docker Hub nach Image suchen
import Container aus ein Archiv importieren start Container starten
info Systeminformationen anzeigen stats Status von Container anzeigen
inspect Containerstatus und Konfiguration anzeigen stop Container anhalten
kill Container beenden tag Tag erzeugen
load Image laden top Laufende Prozesse eines Containers anzeigen
login Anmelden an Docker Registry unpause Pause beenden
logout Abmelden von Docker Registry update Konfiguration von Container aktualisieren
logs Container-Logfiles anzeigen version Docker-Version anzeigen
pause Alle Prozesse eines Containers anhalten wait Warten, bis Container beendet sind

Tabelle 3: Übersicht der Docker-Kommandos

Schwarm aufbauen

Ein Docker-Schwarm besteht aus mindestens einem Manager-Knoten und beliebig vielen Worker-Knoten. Die Kommunikation der Knoten untereinander läuft immer verschlüsselt ab. Die Verschlüsselung wird von Docker intern verwaltet, sodass wir uns nicht darum kümmern müssen. Um einen Docker-Schwarm aufzubauen, werden nur wenige Kommandos benötigt. In unserem Fall kann man folgendes Kommando auf dem docker01-Rechner verwenden: docker swarm init –advertise-addr eth0. In der Ausgabe steht das Kommando, das Sie verwenden müssen, um dem Schwarm weitere Worker-Knoten hinzuzufügen. Bitte führen Sie nun das Kommando auf den beiden übrigen Raspberries aus.

Um zu überprüfen, ob der Schwarm vollständig ist, können Sie das Kommando docker node ls verwenden. In Abbildung 3 können Sie sehen, dass Ihr Schwarm jetzt aus drei Knoten besteht. Der Schwarm ist nun schon vollständig konfiguriert und wir können Container auf ihm ausführen.

Abb. 3: Erstellen eines Schwarms

Abb. 3: Erstellen eines Schwarms

Container im Schwarm starten

Wir können unseren tomcat_container aus dem ersten Beispiel nun ohne viele Anpassungen in einem Schwarm laufen lassen. Dazu sind nur eine zusätzliche Konfigurationsdatei (Listing 5) und ein etwas anderes Deployment nötig. In der Konfigurationsdatei wird einfach nur beschrieben, wie wir aus unseren Images einen Service machen. Diese Datei muss mit in das Projektverzeichnis abgelegt werden.

version: '3'
services:
web:
image: image_tomcat
ports: 
- "80:8080"

Um nun den Service im Schwarm zu starten, geben Sie bitte auf der Kommandozeile folgendes Kommando ein: docker stack deploy -c docker-compose.yml tomcat_swarm. Die Ausgabe des Programms sieht auf den ersten Blick wie eine Fehlermeldung aus, ist aber nur ein gut gemeinter Hinweis, den wir in unserem Fall ignorieren können. Die richtigen Images werden beim Starten der Container automatisch verteilt. Dieser Vorgang benötigt allerdings etwas Zeit.

Aktuell läuft unser Service nur auf einem Knoten. Das können wir mit dem Kommando docker service ls überprüfen. Um den Service nun auf alle Knoten auszuweiten, verwenden wir das Kommando: docker service scale tomcat_swarm_web=3. Jetzt werden die Images zu den restlichen Knoten übertragen und gestartet. Es dauert etwas, bis das Image auf allen Koten vorhanden ist, anschließend können wir mit docker service ls sehen, dass der Service nun auf drei Knoten läuft.

Um zu überprüfen, ob der Service auch tatsächlich verteilt wird, verwenden wir das folgende Kommando. Es sollte bei jedem Aufruf einen anderen Hostnamen ausgeben. Die Hostnamen sind automatisch generiert, daher sehen sie so aus wie Hashes: curl -s http://docker01/myapp/ | grep Servername.

Sie können natürlich auch einen Browser zum Testen nehmen. Achten Sie dabei bitte darauf, die Seite wirklich neu zu laden, sonst kann Ihnen der Browsercache einen Streich spielen. Wenn Sie den Zustand des kompletten Schwarms überprüfen wollen, können Sie das mit dem Kommando docker stack ps tomcat_swarm tun.

Das war auch schon alles, was nötig ist, um aus einem einzelnen Docker Image einen Service in einen Schwarm zu erstellen. Mit dem Kommando docker stack rm tomcat_swarm kann man den Service wieder vom Schwarm entfernen. Wenn Sie nun Änderungen am Image vornehmen möchten, müssen Sie dieses neu builden und danach die Schritte für das Deployment auf dem Schwarm wiederholen. Mit Hilfe der Bash History ist das eine Sache von wenigen Sekunden.

Fazit

Leider gibt es für den Raspberry Pi auf Docker Hub nicht so viele vorbereitete Docker Images wie für die X86-Welt. Die wichtigsten Images sind aber vorhanden und gepflegt, sodass man ohne Probleme den Raspberry verwenden kann, um die Grundlagen von Docker zu erlernen. Der Sprung von einem einzelnen Image zu einem Service im Schwarm ist mit Docker leicht möglich. Die Verwaltung des Schwarms über die Kommandozeile funktioniert wirklich gut.

In diesem Artikel konnten wir nur einen kleinen Überblick geben, was mit Docker alles möglich ist. Sich etwas intensiver mit Docker zu beschäftigen, lohnt sich auf jeden Fall.

 

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

2 Kommentare auf "Docker-Schwarm mit dem Raspberry Pi aufbauen"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Roman H.
Gast

Wie wäre es anstatt sich für jeden Raspy eine SD-Karte zu kaufen, PXE zu verwenden?

schleeke
Gast

Moin,

nur als kleine Ergänzung: Windows bringt den ssh-Server und -Client mittlerweile selbst mit; Putty & Co. sind nicht mehr erforderlich ^^
…und (ein wenig off-topic, da’s ja um den Pi ging) Docker EE verursacht nur unter Linux bzw. Win-Clients Lizenzkosten – unter Server 2016 und 2019 darf die EE kostenlos eingesetzt werden…

X
- Gib Deinen Standort ein -
- or -