Aufgaben in Docker-Container verbannen

Um so komplexer das gesamte Umfeld wird, umso schwieriger wird es, den Überblick zu bewahren. Inzwischen haben sich einige Funktionen angesammelt. Um diese voneinander zu separieren, wollen wir die einzelnen Funktionen in Docker-Container einsperren. Diese laufen dann auf einem Raspberry Pi 4.

Die Vorteile beim Einsatz von Docker sind dabei vor allem, die einfache Möglichkeit neue Versionen einzuspielen und die Unabhängigkeit der unterschiedlichen Komponenten bezüglich der verwendeten Bibliotheken sicherstellen. So ist es einfach möglich, verschiedene Anwendungen mit unterschiedlichen Versionen von Bibliotheken zu verwenden. Bei der Installation von neuen Anwendungen sind die bestehenden Anwendungen nicht davon betroffen. Zusätzlich motiviert es, selbstgeschriebene Anwendungen möglichst klein und einfach zu halten.

Wie das ganze mit Docker funktionieren kann, zeigt der folgende Text.

Installation von Docker

Als erstes müssen wir auf dem Raspberry Pi Docker installieren. Das geht sehr einfach mit einem einzigen Befehl:

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

Wer es lieber manuell ausführen will, findet hier eine ausführliche Anleitung.

Es kann hilfreich sein, den Docker Nutzer die passenden Rechte zu geben, so dass man docker nicht immer mit einem sudo aufrufen muss. Wem das lieber ist, der benutzt dazu folgenden Befehl:

sudo usermod -aG docker pi

Wurde der Befehl nicht verwendet, so muss vor alle folgenden Befehle immer eine sudo geschrieben werden. Mit einigen einfachen Befehlen lässt sich überprüfen, ob Docker läuft.

docker version

Die installierte Version wird nun angezeigt.

Verwendung von Standard-Containern

Docker bietet ein riesiges Sammelsurium von bereits fertigen Containern an. Diese werden aus dem Docker Repository gezogen und dann auf dem Gerät gebaut und gestartet. Wie das geht zeigen wir am Beispiel von MariaDB, der von uns verwendeten Datenbank.

Für MariaDB gibt es bei Docker für den Raspberry Pi ein eigenes Image mit dem Tag jsurf/rpi-mariadb:latest. Dieses Image verwenden wir, um den Container zu erstellen. Dazu verwenden wir den Befehl docker run. Dieser braucht als Parameter einen Namen für den Container, über den wir den Container in weiteren Befehlen ansprechen können. Hierfür nutzen wir einfach den Namen mydb. Damit sieht der einfache Befehl wie folgt aus:

 docker run --name mydb jsurf/rpi-mariadb:latest

Damit kommen wir aber nicht weit, da uns das Root PW fehlt, um auf die Datenbank zugreifen zu können. Auch können wir von außerhalb des Containers nicht auf den DB Port 3306 zugreifen.

Dieses Problem lösen wir, indem wir einfach die folgenden beiden Parameter -e und -p verwenden. Für das MySQL Passwort sollte natürlich ein sinnvoller Wert eingegeben werden. Auch der Port kann auf einen anderen Port gemapped werden, z.B. wenn mehrere Datenbanken auf einem System laufen. Im Beispiel bleiben wir aber beim Standard-Port denn den erwarten auch die meisten Anwendungen.

 docker run --name mydb -e MYSQL_ROOT_PASSWORD=xxxx -p3306:3306 jsurf/rpi-mariadb:latest

Nun handelt es sich bei dem aktuellen Container ja um eine Datenbank, d.h. zur Zeit würden die Daten alle innerhalb des Containers liegen. Damit würde ein versehentliches Löschen des Containers auch zu einem Datenverlust führen. Deswegen kann es sinnvoll sein, den Speicherort auf dem Basissystem festzulegen. Dazu mappen wir den Datenordner von MariaDB einfach auf ein lokales Laufwerk mit dem Befehl -v. Das Ganze sieht dann so aus:

 docker run --name mydb -v /mnt/data/sql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=xxxx -p3306:3306 jsurf/rpi-mariadb:latest

Als Ergebnis haben wir jetzt die Datenbank in den Container verbannt, von außen sieht es aber gar nicht so aus. Wir können von anderen Systemen auf die Datenbank über den Standard-Port zugreifen.

Arbeiten mit dem erzeugten Docker-Container

Die erzeugten Container müssen wir auch stoppen und löschen können, z.B. wenn wir einen Fehler beim anlegen gemacht haben. Für die kommenden Beispiele verwenden wir den oben angelegte Container mydb.

Den Container starten wir mit dem folgenden Befehl:

 docker container start mydb

Dementsprechend geht dann das Stoppen über den folgenden Befehl:

 docker container stop mydb

Wenn der Container gestoppt ist, dann können wir ihn auch Löschen. Dazu verwenden wir den folgenden Befehl:

 docker container rm mydb

Jetzt fehlt uns nur noch die Möglichkeit, uns den Status unserer Container anzusehen. Dabei helfen uns die folgenden Befehle:

docker ps

Dies zeigt einen allgemeinen Überblick über die laufenden Container und ihre Laufzeit an. Alternativ können wir für das gleiche Ergebnis auch den folgenden Befehl verwenden.

docker container ls

Für unseren Datenbank Container ist es auch sinnvoll, ein Backup der Datenbank ziehen können, bzw. einspielen zu können. Auch dies können wir direkt über docker antriggern, indem wir Befehle in den Container schicken.

Um ein Backup (dass als SQL Datei vorliegt) in einem Datenbank namens ourdata einzuspielen, hilft uns der folgende Befehl:

docker exec -i mydb mysql -uroot -pxxxx ourdata < backup.sql

Wir führen den Befehl also als Root User der Datenbank unter Verwendung des weiter oben gesetzten Root PW aus. Wichtig ist dabei, dass die Datenbank mit dem Namen ourdata schon existiert, wenn nicht können wir sie so anlegen:

docker exec -i mydb mysql -uroot -pxxxx CREATE TABLE ourdata;

Wollen wir jetzt einen Dump ziehen, so ist dies einfach mit dem folgenden Befehl möglich:

docker exec -i mydb mysqldump -uroot -pxxxx ourdata > /path/to/file/backup.sql

Es ist auch einfach möglich, hier noch den Schritt des Zippen der Datei um Speicherplatz zu sparen mit einzubauen:

docker exec -i mydb mysqldump -uroot -pxxxx ourdata | gzip --best > /path/to/file/backup.sql.gz

Das ganze können wir jetzt verwenden, um z.B. im Crontab einen regelmäßigen Job einzurichten, der ein Backup der Datenbank zieht.

Erstellung von eigenen Docker Containern

Neben den Standard Container wollen wir auch Container nutzen, die auf Basis von Python laufen, aber mit eigenen Python Skripten gestartet werden. Diese Skripte liegen typischerweise auf Github herum und wurden als lokale Kopie auf den Raspberry Pi in ein eigenes Verzeichnis gepackt.

Unter dieser Voraussetzung erklären wir jetzt, wie darauf ein Docker Container werden kann.

Erstellung des Dockerfile für Python Container

Wir nutzen ein Dockerfile, um die Container anzulegen. Dazu erzeugen wir in das Verzeichnis, in dem unsere Python Skripte liegen eine entsprechende Datei mit nano.

nano Dockerfile

In diese Datei kommt der folgende Inhalt:

FROM python:3.7-slim
WORKDIR /code

RUN apt-get update && apt install gcc mariadb-server mariadb-client libmariadbclient-dev python3-my$

COPY requirements.txt .
RUN pip install -r requirements.txt

RUN apt-get remove gcc -y

COPY / /code/
CMD ["python", "./manage.py", "runserver",  "0.0.0.0:8000", "--noreload"]

Hier eine kurze Erläuterung der einzelnen Zeilen:

  • Zeile 1: hiermit wählen wir die Python Version aus, die Verwendet werden soll. Die Tags für Python finden sich hier. Wir haben hier Python 3.7 gewählt, da unsere Django Installation auf dieser Version getestet ist. Entsprechend neuere Versionen können folgen, wenn alle Funktionen dementsprechend angepasst wurden.
  • Zeile 2: Das Arbeitsverzeichnis für den Container wird festgelegt. Wir haben uns für das Verzeichnis /code entschieden.
  • Zeile 4: Per apt werden notwendige Bibliotheken für die Anbindung von mariadb an Django installiert. Dazu muss als erstes apt selbst geupdated werden.
  • Zeile 6: Die Requirements.txt enthält die Auflistung der notwendigen Python Bibliotheken. Diese Datei wird in den Container kopiert.
  • Zeile 7: pip wird aufgerufen, um die Python Bibliotheken zu installieren.
  • Zeile 9: Die zum laufen nicht notwendigen durch APT installierten Bibliotheken werden wieder gelöscht. GCC haben wir nur benötigt, um die mariadb Pakete installieren zu können.
  • Zeile 11: Alle Dateien werden nach /code kopiert, jetzt liegen unsere Python Skripte also im Container.
  • Zeile 12: Der Django Server wird gestartet. Dabei werden die Parameter den Aufrufes einzeln im Array genannt, übersetzt entspricht das dem folgenden Befehl auf der Kommandozeile: python ./manage.py runserver 0.0.0.0:8000 --noreload

Bauen des Container Image und des Containers

Nun können wir mit Hilfe der Datei das Image des Containers bauen. Dazu gehen wir in das Verzeichnis wo das Dockerfile liegt und rufen den folgenden Befehl auf:

docker build -t myimage .

Wir haben jetzt ein Docker Image mit dem Namen myimage. Aus diesem Image erzeugen wir dann der Container:

 docker run --name mygui -p8000:8000 -d --restart=always myimage

Bei dem Befehl sieht man, dass wir wieder einen Port freigegeben haben. Das ist der Port 8000, auf dem wir den Django Server gestartet haben. Außerdem haben wir die Option —restart=always hinzugefügt. Diese sorgt dafür, dass der Container im Fehlerfall immer neu gestartet wird. Deswegen haben wir auch Django selbst mit der Option –noreload ausgestattet, dass also der Django Server keinen Neustart im fehlerfall durchführen soll. Für andere Anwendungen muss man ausprobieren, was die beste Kombination ist.

Es macht jetzt Sinn, das Dockerfile dem Repository hinzuzufügen, um so flexibel auch auf anderen Systemen den Container erzeugen zu können. Wurden jetzt Skripte geändert, so kann man das Image neu bauen und darauf wieder einen neuen Container erstellen. Da der Container keine Daten beinhaltet ist dies ohne Probleme möglich.

Weitere hilfreiche Docker-Befehle

Wenn man ein bisschen mit Docker herumspielt, so muss man feststellen, dass Docker mit der Zeit einiges an Speicherplatz auf der SD-Karte belegt. Dies liegt daran, dass bei der Erstellung von Images Zwischenschritte gespeichert werden, um bei kleinen Änderungen schneller zu sein.

Um einen Überblick zu bekommen hilft der folgende Befehl:

docker system df

Hier steht auch, wie viel Speicherplatz freigebbar (reclaimable) ist. Steht hier eine große zahl und will man nicht mehr viel mit dem System machen, so lohnt es sich, diesen Speicherplatz zurückzuholen. Dazu nutzen wir den folgenden Befehl:

docker system prune -a

Weiterhin kann es hilfreich sein, sich die Last des Systems durch die Container anzusehen. Hierbei hilft der folgende Befehl:

docker stats

Dieser Befehl zeigt in Echtzeit die CPU und Speichernutzung der einzelnen laufenden Container an. Wenn es also Probleme gibt, kann man sie so leichter lokalisieren.

Wie geht es weiter

Nach diesem Schritt ist es sinnvoll, auch für alle anderen Anwendungen spezielle Docker-Container zu erstellen. Die Herausforderung dabei wird vor allem die Installation von PyAds und die Freigabe der Ports für HEOS sein. Deswegen folgt dafür ein eigener Artikel.

Schreibe einen Kommentar

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

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.