Inhaltsverzeichnis

Docker

Docker ist eine Containerplattform.

Installation

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

Man holt sich den Key des Repos, installiert diesen und fügt das Repo hinzu.

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
 
# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Danach wird Docker installiert.

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Um die Installation und den Docker-Daemon zu testen, holt man sich den ersten Container und startet diesen.

docker run hello-world

Grundsätzlich kann man die Installation nun so belassen. Alle Benutzer können, wenn sie dies dürfen, per sudo Container bedienen.
Dies ist so, da der Daemon über einen Unix-Socket und nicht über einen TCP-Port erreichbar ist. Der Socket gehört dem Benutzer „root“ und der Gruppe „docker“.
Soll jeder Benutzer dies machen dürfen, auch ohne sudo, so fügt man diesen der, bei der Installation hinzugefügten, Gruppe „docker“ hinzu.

sudo usermod -aG docker <BENUTZER>

Hat man docker mit sudo bedient und hat sich danach der Gruppe „docker“ hinzugefügt, so müssen noch die Rechte des eigenen Docker-Verzeichnisses angepasst werden.

mkdir -p /home/<BENUTZER>/.docker
sudo chown -R <BENUTZER>:<BENUTZERGRUPPE> /home/<BENUTZER>/.docker
sudo chmod -R g+rwx /home/<BENUTZER>/.docker

Konfiguration

Die Konfiguration des Daemons wird mit Startparametern geregelt.
Diese werden normalerweise über die Datei /etc/docker/daemon.json gesetzt.
Bei Betriebssystemen mit „Systemd“ als init-System werden die Parameter direkt in Systemd eigetragen.
Dazu erstellt man eine override.conf mit folgendem Befehl:

systemctl edit docker.service

Nun setzen wir z.B. den DNS-Server welcher an die Container übergeben werden soll.
Der Inhalt der override.conf sieht wie folgt aus:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --ip=<IP_DES_HOSTS> --dns=1.1.1.1 --dns=8.8.8.8 --dns-search="dns.domain.tld" -H fd://

Die leere „ExecStart=“ Zeile ist wichtig, da Systemd erst die Startsequenz löscht um sie dann neu zu definieren.

Nun noch Systemd die Änderungen mitteilen und dann den Docker-Daemon neustarten.

systemctl daemon-reload
systemctl restart docker

Note that the empty ExecStart= is required, as systemctl only will overrule the ExecStart if it is cleared first.

Autostart der Container nach Boot

Damit die Docker Container nach einem reboot des Docker Hosts automatisch starten, können folgende Methoden angewandt werden:

  1. starten über Prozessmanager wie systemd
  2. starten über Docker Restart Policies

Systemd

Quelle: https://mehmandarov.com/start-docker-containers-automatically/
Per Befehl vi /etc/systemd/system/docker-<CONTAINERNAME>.service einen neuen Service erstellen
docker-<CONTAINERNAME>.service

[Unit]
Description=<NAME_OF_CONTAINER>
Requires=docker.service
After=docker.service
 
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a <CONTAINERNAME>
ExecStop=/usr/bin/docker stop -t 2 <CONTAINERNAME>
 
[Install]
WantedBy=local.target

Erklärung:
ExecStart=/usr/bin/docker start -a → startet den Container im „attach“ Modus
ExecStop=/usr/bin/docker stop -t 2 → wartet 2 Sekunden auf den Stop des Container, bevor dieser gekillt wird

Nach dem Erstellen des Service diesen noch systemd bekannt machen und aktivieren:

systemctl daemon-reload
systemctl enable docker-<CONTAINERNAME>.service

Gestartet werden kann der neue Service über die üblichen start und stop Kommandos.

Restart Policies

Quelle: https://docs.docker.com/config/containers/start-containers-automatically/

Mit dem run befehl dem Container sagen, welche restart Policy er erhalten soll:

docker run --restart [no|on-failure|always|unless-stopped]
Restart Policy Beschreibung
no Do not automatically restart the container. (the default)
on-failure Restart the container if it exits due to an error, which manifests as a non-zero exit code.
always Always restart the container if it stops. If it is manually stopped, it is restarted only when Docker daemon restarts or the container itself is manually restarted. (See the second bullet listed in restart policy details)
unless-stopped Similar to always, except that when the container is stopped (manually or otherwise), it is not restarted even after Docker daemon restarts.

Mit foldendem Befehl kann man sehen welche Restart Policy aktuell im jew. Container gesetzt ist:

docker inspect graphite| jq '"Container: \(.[].Name)","Restart-Policy: \(.[].HostConfig.RestartPolicy.Name)"'

Debug

Mit der Startoption -D|–debug kann man das Debugging bei Docker einschalten.
Danach startet man die Log-Ansicht für einen Container wie folgt:

docker logs [<OPTIONEN>] <CONTAINER_NAME>

RUN, ENTRYPOINT, CMD

Die Befehle RUN, ENTRYPOINT und CMD werden im Dockerfile unterschiedlich benutzt.

Grundsätzlich können diese Befehle als Exec und Shell Format übergeben werden.

Exec:
Beispiel

[RUN|ENTRYPOINT|CMD] ["<BEFEHL>", "<PARAMETER1>", "<PARAMETER2>"]

Shell:
Beispiel

[RUN|ENTRYPOINT|CMD] <BEFEHL> <PARAMETER1> <PARAMETER2>
Befehl Beschreibung kann mehrmals
verwendet werden
RUN * wird verwendet um Befehle beim erstellen eines Images auszuführen ja
Entrypoint * wird verwendet um Befehle nach dem Start des Containers auszuführen
* wird auch ausgeführt wenn der docker run Befehl einen Standard Befehl am Ende ausführt
* wenn im Shell Format angegeben, wird jeder CMD Befehl ignoriert
ja
CMD * wird verwendet um eine Standard Befehl nach dem start des Containers abzusetzen
* Wenn beim docker run Befehl ein Standard Befehl am Ende angegeben wird, wird CMD ignoriert
* Kann auch verwendet werden um nur Parameter an ENTRYPOINT zu übergeben, wenn kein Standard Befehl beim docker run übergeben wird, wird der ENTRYPOINT ausgeführt
nein (bzw. nur der letzte CMD-Befehl wird ausgeführt)

Konfigurationsparameter

Mögliche Parameter für Konfigurationen können in den Reference-Docs unter docker.com eingesehen werden https://docs.docker.com/reference/.

Nützliche Befehle

dem Befehl docker können verschiedene Parameter übergeben werden.

Parameter Beschreibung
–version zeigt die Version von Docker in Kurzfassung
version zeigt die Version von Docker ausführlich
ps zeigt laufende Container
build -t <IMAGE_NAME> . erstellt ein Image mit angegebenen Namen über das im aktuellen Ordner befindlichen Dockerfile
run startet ein Image
run -p 8080:80 <IMAGE_NAME> startet ein Image wo der, von aussen, erreichbare Port 8080 auf den, im Container erreichbaren, Port 80 gemappt wird
run -d <IMAGE_NAME> startet ein Image im detached Modus (das Prompt ist wieder frei)
container ls listet alle laufenden Container auf
container ls -a listet alle Container auf (gestartet oder gestoppt)
container stop <CONTAINER_ID> stoppt den jew. Container (wie shutdown)
container kill <CONTAINER_ID> erzwingt das stoppen des jew. Containers
container rm <CONTAINER_ID> löscht den jew. Container vom Server
container rm $(docker container ls -a -q) löscht alle Container vom Server
image ls zeigt vorhandene Images
image ls -a zeigt alle Images
image rm <IMAGE_ID> löscht das jew. Image vom Server
image rm $(docker image ls -a -q) löscht alle Images vom Server
logs <CONTAINER_ID> zeigt logs über den jew. Container
inspect <CONTAINER_ID> zeigt ausführliche Infos über den jew. Container
attach <CONTAINER_ID> Verbindet sich zum Container zum Standard Input/Output Terminal (detach per STRG+P und danach STRG+Q)
exec -it <CONTAINER_ID> /bin/bash Verbindet sich zu einer Bash-Shell zum Container

Netzwerk

Docker verfügt über folgende Treiber für die Netzwerkanbindung eines Containers:

Treiber Beschreibung
bridge Das ist der Standard, wenn ein Container erstellt wird; befindet sich im NET-Netz hinter der Host Adresse
host Nutzt die Netzwerkschnittstelle des Hosts und ist direkt an dessen IP-Adresse gebunden
overlay Wird verwendet wenn mehrere Docker Hosts sich ein Container-Netzwerk teilen sollen
macvlan Layer 3 Netzwerk; kann verwendet werden um Container direkt an das Netzwerk mit einer eigenen IP-Adresse anzubinden (z.B. mehrere VLANs )
Es können für die Container Netzwerschnittstelle IP-Adressen oder MAC-Adressen definiert werden
iplan ähnlich wie macvlan der nur Layer 2

Möchte man die IP-Adressen eines Containers ausgeben lassen, dann macht man das mit docker container inspect <CONTAINER_NAME>.
Sollen alle IP-Adressen aller Container ausgegeben werden, so kann man diesen Befehl verwenden.
Hierbei muss beachtet werden, wie der Container angebunden ist, so ist der Befehl ggf. anzupassen.

docker inspect -f '{{.Name}}-{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' $(docker ps -aq)

MACVLAN

Bei einem macvlan Netzwerk, werden die Container direkt im Netzwerk behandelt wie jeder normale Host. Sie sind quasi mit einer eigenen Netzwerkschnittstelle im Netzwerk präsent.
Container können mit dem Parameter ip_address (docker-compose) eine eigene feste IP-Adresse besitzen.
Soll ein DHCP-Server die IP-Adressen verwalten, so muss man bedenken, dass sich die MAC-Adresse eines Containers ändert, wenn dieser oder der Docker-Daemon neugestartet wird.
Dies kann mit dem Parameter mac_address (docker-compose: IST IN VERSION 3 DEPRECATED) angepasst werden und eine eindeutige MAC-Adresse vergeben werden.

MAC-Adressen werden vom Docker-Daemon generiert. Hier ein Auszug aus der Docker Reference:

By default, the MAC address is generated using the IP address allocated to the container.
You can set the container’s MAC address explicitly by providing a MAC address via the --mac-address parameter (format:12:34:56:78:9a:bc).
Be aware that Docker does not check if manually specified MAC addresses are unique.

Eine einzigartige MAC-Adresse kann sich man aber relativ leicht zusammenstellen.
Wie immer geben die ersten 3 Stellen der MAC-Adresse den Hersteller (also die OUI ⇒ Organizationally Unique Identifier, „organisatorisch eindeutige Kennung“) an, bei Docker ist dies aber nicht der Fall.
Ein Docker-Daemon hat in diesem Fall die ersten 2 Stellen generiert, es geht also nur um die letzten 4 Stellen.

Dazu nimmt man einen Dezimal / Hexadezimal Rechner und wandelt die letzten 4 Oktette der IP-Adresse des Containers um.

Beispiel:
generierte MAC-Adresse: 02:42:c0:a8:8a:0b
Hier sind die Stellen 02:42 die festen Angaben, die letzten 4 würden in Dezimal folgendes ergeben 192:168:138:11.
Dies ist die IP-Adresse des Containers.

Hat man einen DCHP-Server, kann man bei diesem eine IP-Adresse für einen Container reservieren.
Danach rechnet man diese in Hexadezimal um und erhält somit die eindeutige MAC-Adresse.

Route von/zu Docker-Host

Generell sind Docker Container, welche sich in einem macvlan befinden, vom Docker Host isoliert (obwohl beide vielleicht im selben Subnetz sind.
Damit man die Verbindung wiederherstellen kann, muss eine Netzworkbridge mit einer entspr. Route zum Container/-Netzwerk erstellt werden.
Hier ein Beispiel (Quelle: https://www.networkshinobi.com/docker-host-cant-access-containers-running-on-macvlan/):

ip link add <NAME_CUSTOM_BRIDGE_DEVICE> link <PHYSICAL_LINKED_DEVICE_TO_NETWORK> type macvlan mode bridge
ip addr add <IP_ADDRESS_FROM_MACVLAN> dev <NAME_CUSTOM_BRIDGE_DEVICE>
ip link set <NAME_CUSTOM_BRIDGE_DEVICE> up
ip route add <IP_ADDRESS_FROM_CONTAINER_IN_MACVLAN>/32 dev <NAME_CUSTOM_BRIDGE_DEVICE>

Images und Container

Images werden in Layer aufgeteilt. Einzelne Anweisungen im Dockerfile führen dazu, dass verschieden viele Layer erzeugt werden. Diese Layer sind alle schreibgeschützt.
Erst wenn der Container mit seinem Befehl gestartet wird, erzeugt er einen neuen eigenen letzen Layer, welcher beschrieben werden kann.
Dieser enthält die Daten welche zur Laufzeit hinzugekommen/geändert sind/wurden.

Erstellen

Um ein Image zu erstellen, benötigt man eine Datei namens Dockerfile. Darin stehen die Anweisungen zum erstellen eines Containers.
Alle benötigten Dateien zum Erstellen eines Images, sollten sich in einem eigenen Ordner befinden.

Beispiel zum Erstellen eines Images mit Nginx und PHP:

mkdir nginx-php
cd nginx-php

Konfigurationsdateien

Folgende Dateien müssen erstellt und mit Inhalt gefüllt werden.

Erklärung

Datei Beschreibung
Dockerfile * steuert das Erstellen des Images und das Startverhalten des Containers
package.list * enthält die, in das Image, zu installierenden Pakete
startup.sh * Startscript welches nach dem Starten des Containers ausgeführt wird
* hier werden die Daemons für nginx und php gestartet
* der letzte Befehl ist dafür da, damit das Script und somit auch der Container dauerhaft läuft
nginx.conf * Konfiguration für nginx
my-website-conf * Konfiguration für Webseite
fastcgi-php.conf * Konfiguration für php und fastcgi
snakeoil.conf * Konfiguration für die SSL Verschlüsselung mit Snakeoil Zertifikaten
ssl-cert-snakeoil.key * SSL Zertifikat von Snakeoil
ssl-cert-snakeoil.pem * SSL privater Schlüssel von Snakeoil
ssl-conf * Konfiguration für SSL verschlüsselte Webseiten
dhparam.pem * Diffie Hellmann Schlüssel
index.php * Startseite
phpinfo.php * PHP-Info Webseite
hallo.html * SSL verschlüsselte Webseite

Build and Run

Nun kann man das Docker-Image bauen lassen. Dazu führt man folgenden Befehl aus:

docker build --build-arg TIMEZONE=Europe/Berlin -t <NAME_DES_IMAGES> .

Mit folgendem Befehl sieht man das nun erstellte Image

docker image ls

Das Images ist nun erstellt. Nun kann man seinen Docker-Container starten.

docker run -d -p 80:80 -p 443:443 --name nginx <IMAGE_NAME>:<TAG>

Zur Erklärung der Parameter:

Der Container sollte nun dauerhaft laufen und die Ports 80 und 443 nach außen bereitstellen.
Man kann nun im Webbrowser die URL http://<IP_DES_DOCKER_HOSTS> eingeben und die Webseite der index.php sehen.

Der folgende Befehl zeigt alle laufenden Container.

docker container ls

Dieser Befehl beendet den jew. Container

docker container stop <ID>

Volumes

Damit man wegen Konfigurationsänderungen nicht ständig zum Container verbinden muss, erstellt man sich Docker-Volumes und mountet diese mit den Volumes welche im Dockerfile angegeben wurden.
Die Volumes werden folgendermaßen angelegt:

docker volume create <VOLUME_NAME>

Der Docker Container wird dann so gestartet:

docker run -d -p 80:80 -p 443:443 -v nginx-nginx:/etc/nginx -v nginx-log:/var/log -v nginx-html:/var/www/html <IMAGE_NAME>:<TAG>

Zur Erklärung der Parameter:

Docker umziehen

Wenn Container/Images auf einen anderen Host umziehen sollen, so muss man beachten wie die Laufzeitdaten gesichert werden können.
Laufzeitdaten können in Images oder aber auch auf Volumes liegen.
Weiterhin ist es absolut wichtig, dass der Container wieder so gestartet wird, wie auf dem ursprünglichen Host.
Man muss also den Befehl für docker run… haben oder diesen nachbilden. Mit docker inspect.. werden detailierte Angaben zum Container ausgegeben.
Darüber kann man sich sein run Befehl zusammenbasteln.

Portainer

Portainer ist ein Docker Management Tool, welches auch als Container läuft.
Siehe https://www.portainer.io/.

Installation

docker volume create portainer_data
$ docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest

Upgrade

docker stop portainer
docker pull portainer/portainer
docker container rm portainer

Danach einfach Portainer wie bei der Installation beschrieben, starten.