Im Docker-Hub gibt es für viele Dienste bereits fertige Images. Die Nutzung der Images ist in der Regel frei. Meine Erfahrung ist gemischt mit der Nutzung. Bei einigen klappt der Einsatz auf Anhieb, bei anderen ist nach mehrstündigen Anpassungen nicht der gewünschte Erfolg in Sicht. Bei Images die nicht zu den CORE-Images zählen ist die Erfolgsquote nicht gerade bei 100%. Auch die Aktualisierung der Images lässt vielfach zu wünschen übrig. Manchmal liegt ein letztes Update Jahre zurück.
Die Lösung für dieses Problem ist aus meiner Sicht einfach und lautet – Containerselbstbau. Bei vielen Diensten handelt es sich um die Dienste, welche auf einem Linux Betriebssystem basieren. Eines der am meisten genutzen und gut unterstützen CORE-Images ist Ubuntu. Grund genug dieses als Basis zu nutzen.
Um aufzuzeigen wie es geht und welche Schritte notwendig sind werde ich hier einmal aufzeichen, wie die VPN Lösung PiVPN in einem Container auf einem Virtual Private Server von Strato mit 1 Core und 1GB Ram erstellt und betrieben werden kann. Ebenfalls installiert habe ich es auf einem Raspberry Pi400-DE, ein PI4 mit 4GB RAM.
Ich möchte ausdrücklich darauf hinweisen, es gibt unterschiedliche herangehensweisen und Lösungen. Der hier gezeigte Weg ist einfach nur einer davon
Schritt 0: Vorbereitung
Zum Einsatz kommt ein VPS (oder ein Raspberry Pi mit installiertem Linux-OS, Ubuntu). Auf dem Server bin ich per SSH mit dem Benutzer root angemeldet.
Die Docker Installation, falls noch nicht erfolgt, ist im Schritt 0.1 beschrieben.
Portainer-CE ist ein hilfreiches Tool zum Management der Containerlösung – ist aber nicht zwingend notwendig.
Nachfolgende Bilder zeigen einige Details zur Umgebung.
Schritt 0.1: Docker Installation
Auf einem Ubuntu VPS ist die Installation einfach mit folgender Zeile abgeschlossen
sudo apt install docker
Auf einem Raspberry Pi erfolgt die Installation von Docker inklusive der Portainer-CE Installation mit folgenden Befehlen:
#
# Lade die aktuelle Docker Version von der DOCKER-Webseite
#
curl -fsSL https://get.Docker.com -o get-Docker.sh
sudo sh get-Docker.sh
sudo usermod -aG docker $USER
newgrp docker
#
# Hiermit wird der erste Container aus dem Docker-Hub geladen und ausgeführt
#
docker run hello-world
#
# Hiermit wird Portainer-CE aus dem Docker-Hub geladen und ausgeführt. Dieses ist eine Webmgnt Oberfläche
#
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
Auf Portainer wird dann über https:<IP-Adresse-des-RaspberryPI>:9443 zugegriffen. Die finale Installation erfolgt durch die weitere Menüführung.
Schritt 1: Auswahl und laden des Basisimages
Als Basisimage wähle ich aus dem Docker-HUB UBUNTU in der Version 22.04. Genau diese ist auch auf dem Server als Basis-OS installiert.
Auf der Konsole lade ich das Image in das lokale Repository.
docker pull ubuntu:22.04
root@ubuntu:~# docker pull ubuntu:22.04
22.04: Pulling from library/ubuntu
Digest: sha256:8eab65df33a6de2844c9aefd19efe8ddb87b7df5e9185a4ab73af936225685bb
Status: Downloaded newer image for ubuntu:22.04
docker.io/library/ubuntu:22.04
Schritt 2: Anpassung des Basisimages
Ziel dieses Arbeitsschrittes ist die generelle Anpassung des Images auf meine Wünsche. Die Anpassungen sind wie folgt:
- Installation weiterer Pakete für Netzwerkadministration, Textverarbeitung, …
- Shell Zugang mit SSH
Nun wird es Zeit auf dem Server ein Projektverzeichnis zu erstellen. Ich habe dazu im Homeverzeichnis von root, /root/ ein Verzeichnis docker erstellt und darin gibt es eine weitere Hierachie für die Images+Versionen. In diese Verzeichnisse werden die benötigten Files für die Images kopiert und daraus die Images gebaut.
mkdir /root/docker
mkdir /root/docker/VHS
mkdir /root/docker/VHS/VPN1.0
mkdir /root/docker/VHS/VPN1.1
mkdir /root/docker/VHS/VPN1.2
Die Anpassungen an dem Image werden in einer „Steuerdatei“ dem Dockerfile gemacht. Und genau so heisst die Datei auch. Mein Inhalt ist folgender:
#DOCKERFILE
# Use the official image as a parent image
FROM ubuntu
# Update the system
RUN apt-get update && apt-get upgrade -y
# Install OpenSSH Server
RUN apt-get install -y openssh-server
# Netzwerksupport- Tools
RUN apt-get install -y iproute2
RUN apt-get install -y net-tools
RUN apt-get install -y iputils-ping
RUN apt-get install -y less
RUN apt-get install -y curl
RUN apt-get install -y nano
RUN apt-get install -y mc
# Set up configuration for SSH
RUN mkdir /var/run/sshd
RUN echo 'root:startpasswd' | chpasswd
COPY sshd_config /etc/ssh/sshd_config
# SSH login fix. Otherwise, user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
# Benutzer pi anlegen
RUN useradd -m pi
RUN echo 'pi:vhskurs22' | chpasswd
# Expose the SSH port
EXPOSE 22
EXPOSE 1194
# Run SSH
CMD ["/usr/sbin/sshd", "-D"]
Jetzt brauchen wir noch eine sshd_config Datei. Ich nehme gern einfach die vom Hostsystem und passe die entsprechenden Parameter einfach an. Es kann auch eine neue mit den gewünschten Einstellungen erzeugt werden.
Hier ein Beispiel für eine minimale Datei
#
# Beispielfile sshd_config
#
Include /etc/ssh/sshd_config.d/*.conf
PermitRootLogin yes
KbdInteractiveAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
So wird sie vom Hostsystem kopiert.
cp /etc/ssh/sshd_config /root/docker/VHS/VPN1.0/.
Um das neue image zu erstellen wird folgender Befehl verwendet. Zu beachten ist der . am Ende.
docker build -t vhs-vpn:1.0 .
Wenn bis hier keine Fehlermeldungen gekommen sind kann mit folgendem Befehl geprüft werden, ob es jetzt ein neues Image gibt.
root@ubuntu:~/docker/VHS/VPN1.0# docker images | grep vhs
vhs-vpn 1.0 0ea5dfb5644e 3 minutes ago 327MB
root@ubuntu:~/docker/VHS/VPN1.0#
Schritt 3: Starten des Image und Anmelden am System
Das angepasste Ubuntu-Image soll jetzt gestartet werden. Die Parameter bedeuten folgendes:
- run = starten des Containers
- -d = detached
- -p = Netzwerkport Mapping, mehfache Parameter möglich
- vhs-vpn:1.0 = das zu startende Image
docker run -d -p 222:22 -p 1195:1194 vhs-vpn:1.0
root@ubuntu:~/docker/VHS/VPN1.0# docker run -d -p 222:22 -p 1195:1194 vhs-vpn:1.0
a8d09a138e8dd037021ac7cedf250da74c5c7f9eaa1f618da83bd02da3f82a97
root@ubuntu:~/docker/VHS/VPN1.0#
Mit folgendem Befehl lässt sich prüfen, ob der Container läuft.
docker ps
Taucht der Container in der Tabelle auf – läuft er. Nun läuft der Container und der SSH-Server ist auf dem Hostsystem unter Port 222 zu erreichen. Der noch nicht installierte openVPN-Server ist später unter Port 1195 auf dem Hostsystem erreichbar. Der Port ist bereits jetzt allokiert, noch nicht genutzt.
Jetzt ist es an der Zeit mit Putty eine SSH Verbindung zum Container aufzubauen. Anmeldung mit den Benutzer root und dem Kennwort startpasswd und auf dem Port 222 des Hostsystems. Nach der Anmeldung das Kennwort ändern nicht vergessen
Der Container verhält sich nun wie ein eigener Server mit dem Betriebssytem Ubuntu. Er stellt jetzt eine gute Grundlage für die Installation von weiteren Services, in unserem Fall PiVPN, dar.
An dieser Stelle habe ich meinen für mich angepassten Basis-Container erstellt. Dieses Image kann nun als „Microservice“ mehfach auf dem System gestartet werden oder kann als Basis für die Installation von weiteren Diensten dienen. In den folgenden Schritten wird dieses Basis-Image dazu verwendet die VPN-Lösung PiVPN zu installieren.
Noch einmal als Hinweis, das Image verhält sich jetzt generell wie ein ganz normaler Server und auch die Installation von PiVPN ist identisch wie auf einem Server.
Schritt 4: Basisinstallation von PiVPN
Die Netzwerkintegration von Docker erfordert das die Services auf Basis TCP kommunizieren. Das ist wichtig, denn openVPN verwendet per default UDP. Weiterhin beachtet werden muss, der Port ist 1194, jedoch kann dieser Port am Host nur ein einziges Mal vergeben werden. Laufen mehrere Container mit dieser Anforderung muss ein anderer Hostport verwendet werden. In unserem Beispiel 1195.
Die Daten (VPN-Konfiguration und Konfigdateien der Benutzer) liegen innerhalb des Containers und befinden sich im Conatinerimage. Wird der Container gelöscht sind die Daten verloren. Eine Lösung dazu kommt in den weiteren Schritten.
PiVPN wird mit einem Link auf eine Webseite installiert. Das Installationsprogramm wird heruntergeladen und lokal auf dem System ausgeführt. Da die Installation weitesgehend Menügeführt ist beschreibe ich diese nicht weiter. Die wichtigen Parameter sind:
- Protokoll = TCP
- Port = 1195 (weil ich bereits einen Dienst auf 1194 laufen habe)
- IP-Adresse (DNS Name) = IP-Adresse oder DNS Name des Hostsystems
- DNS Server = Google
- unattended upgrades = NO
- Reboot = No
Link um die Installation zu starten. Diese Zeile einfach im Putty-Fenster als Benutzer root ausführen.
curl -L https://install.pivpn.io | bash
Nach der Installation muss der Container neu gestartet werden. Jetzt zeigt sich der Unterschied zu einem echten Server. Der Befehl reboot hilf hier nicht, denn das bei einem Server vorhandene INIT-System gibt es hier nicht.
benötigt wird die Container-ID. Diese wird mit folgendem Befehl angezeigt.
docker ps
Mit folgendem Befehl wird der Container neu gestartet.
docker restart <Container-ID>
Nach dem erneuten anmelden am System kann durch eingabe von „pivpn“ überprüft werden, ob die Installation erfolgreich war.
Zwei wesentliche Dinge müssen nun gefixed werden:
a. openVPN wird kein TUN-Interface erstellen, weil die Rechte dazu fehlen. Hierzu muss der Container im Modus „privileged“ gestartet werden
b. openVPN wird nicht starten, weil das INIT-System eines Server nicht vorhanden ist. Im Container muss dieses anders gelöst werden
Schritt 5: Starten des Containers mit privileged Flag
Um den Container zu starten muss das laufende Image gestoppt werden und neu aufgerufen werden.
docker ps
docker restart <container-ID>
Eine Änderung im laufenden Betrieb geht nicht ohne weiteres. Da wir hier auch noch das starten weiterer Dienste innerhalb des Containers behandeln müssen erstellen wir vom laufenden Container ein neues Image mit folgendem Befehl.
docker ps
docker commit a8d09a138e8d vhs-vpn:1.1
Jetzt gibt es ein neues Imge in dem unsere bisherige Installation enthalten ist. Dieses Image kann jetzt gestartet werden. Vorher das Image mit Version 1.0 stoppen.
# Die ID wird der Tabelle von docker ps entnommen
docker stop da8d09a138e8
# Danach das neu erstellte Image mit angepassten Parametern starten
docker run --privileged -d -p 222:22 -p 1195:1194 vhs-vpn:1.1
Ohne das „privileged-Flag“ würde es openVPN nicht möglich sein das TUN-Interface zu erstellen. Das prüfen wir nun durch starten des openVPN-Servers. Der Prozess wird in den Hintergrund gelegt damit wir weiter auf der Shell arbeiten können.
openvpn --config /etc/openvpn/server.conf --log /etc/openvpn/openvpn.log &
Mit ifconfig läst sich prüfen, ob das TUN0-Interface vorhanden ist. Prinzipiell ist der VPN-Server damit am laufen, jedoch startet der VPN-Dienst noch nicht automatisch. Das wird im folgenden Schritt angepasst.
Schritt 6: Anpassen des Startsystems, Starten meherer Dienste
Nun wechseln wir auf dem Hostsystem in das Projektverzeichnis /root/docker/VHS/VPN1.2 und erstellen dort die Datei Dockerfile mit folgendem Inhalt
# Dockerfile
# Use the official image as a parent image
FROM vhs-vpn:1.1
COPY startup.sh .
RUN chmod +x ./startup.sh
# Expose the SSH port
EXPOSE 22
EXPOSE 1194
# Run SSH+openVPN
CMD ["/bin/bash","-c","./startup.sh"]
Danach wird eine Datei erstellt mit dem Namen startup.sh und folgendem Inhalt.
#/bin/bash
openvpn --config /etc/openvpn/server.conf --log /etc/openvpn/openvpn.log &
/usr/sbin/sshd -D
Auf Basis des Dockerfiles wird das Image jetzt angepasst.Mit folgendem Befehl wird daraus ein neues Image erstellt.
docker build -t vhs-vpn:1.2 .
docker images
In der Tabelle wird jetzt ein neues Image mit dem namen vhs-vpn und dem Tag 1.2 angezeigt. Das kann jetzt gestartet werden. Vorher den laufenden Container beenden.
docker run --privileged -d -p 222:22 -p 1195:1194 vhs-vpn:1.2
Nach dem erneuten anmelden am System wird mit „ifconfig“ und „netstat -an“ geprüft ob das Interface und die Dienste aktiv sind. Jetzt kann ganz normal mit PiVPN Benutzerprofile erzeugt werden und die Verbindung zum openVPN Server aufgebaut werden.
Es gibt einige Optionen welche in das Image eingebaut werden könnten. Eine Anforderung könnte sein, die Konfigurationsdaten des openVPN Server zu speichern. Auch hierzu gibt es mehere Wege. Der Container verhält sich in vielen Fällen wie ein Server, bdeutet auch das Thema Backup & Restore müsste analog betrachtet werden. In diesem Zusammenhang könnte dann auch die Datensicherung der Konfiguration und Profile erfolgen.
Ein anderer Weg ist, Dateien und/oder Verzeichnisse aus dem Container in das Hostsystem zu mappen. Damit sind die Daten im Hostsystem verfügbar und sind auch nach dem löschen eines Containers nicht verloren. Diese Einstellungen würden dann über den Befehl docker run und entsprechende Parameter (-v) umgesetzt werden.
Die Beschreibung ist auf Basis eines x86 Servers erstellt worden. Die Plattform sollte keine Rolle spielen und kann auch genau so auf einem Raspberry Pi umgesetzt werden.