Dienstag, 12. Januar 2021

Container Schiff

Schon oft gelesen und dann immer wieder abgestorben - so lief das Thema Docker für mich bisher. Dabei bin ich ja schon seit der ersten vmware Version ein großer Fan von Virtualisierung!

Aber jetzt - ein kleiner Vortrag über FHEM im Container hat mich bewogen es zu versuchen, wie so oft schreib ich hier mal mit, damit ich später weiß was ich gemacht habe.

Ich habe als Ergebnis des Artikels ein Setup Script auf Github abgelegt. Dieses Script

  • installiert docker und docker-compose
  • bringt den aktuellen user in die Gruppe docker
  • richtet portainer als ersten Container ein
Die Entwicklung von docker und docker-compose schreitet voran, es können sich jeden Tag Änderungen ergeben. So wie heute (18.12.2021) gestern ging die hier beschriebene Installation mit pip noch, heute funktioniert es nicht mehr. Ich versuche den Artikel immer wieder zu aktualisieren.

Umgebung bauen

Meine Testumgebung war der Raspberry und ein bisschen auch Windows mit WSL. Also erstmal docker beschaffen, auf dem RaspberryPi nimmt man das Convenience Script und befolgt was dann am Ende dort steht:

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

Das Script installiert alle notwendigen Dinge und läuft eine ganze Weile auch mal still!

Hinweis: Das Script kann auch zum Update einer bereits laufenden Docker Umgebung verwendet werden, wenn diese mit dem Script installiert wurde! Allerdings wird docker auch bei apt full-upgrade aktualisiert.

Dann kann man den "Hello World" Container ausführen - ja und nun? Das bringt mich nicht sehr voran, ich will einfach Container verwenden und keine programmieren!

Docker Compose ist da das Zauberwort! Nach etwas planlosem herumirren habe ich heraus gefunden: Das ist nicht der zweite Schritt wie ich am Anfang dachte - das ist quasi für mich der Erste! Man braucht (in aller Kürze):

  • kein sudo (Abschluss vom Setup beachten),
  • docker-compose an sich (siehe auch Versionshinweise weiter unten)
  • einen Arbeitspfad,
  • eine docker-compose.yml Datei.
  • eine .env Datei wenn man spezifische Informationen an die docker-compose.yml Datei geben will. 
Es gibt mittlerweile docker compose v2 - Achtung: docker-compose war ein extra Befehl, compose v2 ist ein plugin für docker und wird als docker Sub-Befehl aufgerufen. Die Befehle sind im Ansatz identisch aber wohl nicht zu 100%. Es gibt auch noch compose switch - eine Kompatibiltätsschnittstelle die docker-compose in docker compose Befehle übersetzt. Ich belasse weiter unten alle Varianten. Die Installation für arm Plattform ist leider derzeit nicht wirklich gut automatisierbar. Hier also eine Variante zur Installation v2 für den aktuellen Benutzer

# Replace with the latest version from https://github.com/docker/compose/releases/latest
COMPOSE_VER="2.2.2"
# For 64-bit OS use:
COMPOSE_ARCH="aarch64"
# For 32-bit OS use:
COMPOSE_ARCH="armv7"
mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v${COMPOSE_VER}/docker-compose-linux-${COMPOSE_ARCH} -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
# Test
docker compose version

Man kann durch erneutes Ausführen der paar Zeilen auch eine aktuellere Version installieren (upgrade).

Arbeitspfad "docker stack" erzeugen.

mkdir dockertest
cd dockertest

Achtung: Ab hier bitte beachten v1 -> v2: docker-compose -> docker compose 

Der erste Container zur Ergänzung der Spielwiese ist Portainer, den notwendigen Inhalt der compose Datei findet man hier. Für den Start laden wir die compose Datei einfach herunter. Link zur Referenz.

Hinweis: Mit dieser Vorgehensweise bekommt man die alte Portainer Version. Zur neuen Version siehe weiter unten!

wget https://downloads.portainer.io/docker-compose.yml

Die Datei läuft so wie sie ist, anschauen oder ergänzen geht z.B. mit nano. Danach ein Kommando zum bauen und starten.

nano docker-compose.yml
docker-compose up

Portainer hilft als Benutzerinterface beim Verstehen der neuen Containerumgebung

  • Logs - Logfile des Containers anschauen (sieht man sonst bei compose up im Terminal)
  • Inspect - Konfiguration anschauen
  • Stats - Speicher-, CPU-, Netzwerkverwendung 
  • Exec Console - Shell im Container öffnen (man braucht keinen ssh Server im Container!)
  • Add Container - neue Container zusammenstellen, konfigurieren und starten
  • Stacks - unser dockertest ist quasi ein Stack (außerhalb erzeugt), man kann hier Neue erzeugen. Ich glaube aber die manuelle Erzeugung ist einfacher.

Die compose Datei im yaml Format kann leicht um ein Service/Container/Image erweitert werden, dazu kopiert man z.B. aus der Beispieldatei des fhem docker Images den passenden Abschnitt in den vorhandenen services Abschnitt. Für diesen minimalen Versuch kommentiert man dafür aus dem "Minimum Example" noch den network Abschnitt aus.

Nach einem erneuten docker-compose up hat man ein FHEM am Laufen. 

Start der Containerumgebung

Ist die Umgebung fertig konfiguriert, schickt man die Console beim nächsten (finalen) Start einfach in den Hintergrund

docker-compose up -d

Muss man später etwas umkonfigurieren, wird mit diesem Befehl die neue Konfiguration aktiviert. Dabei werden nur die veränderten Container neu gestartet!

Der richtige Eintrag im services Abschnitt startet die Container in Zukunft automatisch mit dem System. 

restart: always

Im Hintergund

Wird die Konfiguration eines Containers geändert, wird der alte zerstört und ein neuer erzeugt. Das Image bleibt dabei erhalten.

Container Transport

Das ist dann eben wirklich der Knaller: Man schnürt die Ladung zusammen (tar) und stellt den Container auf ein Schiff (scp).

docker-compose stop
cd ~
sudo tar -cvf docker.tar ./docker
scp docker.tar user@host:

Am Zielort lädt man das Schiff wieder aus:

sudo tar -xvf docker.tar

Dabei hat man hier nur die "eigenen" Daten transportiert. Führt man jetzt ein docker-compose up aus werden die eigentlichen Images wieder herunter geladen und danach alles neu gestartet. 

Feinkonfiguration

Die Beispieldatei im fhem docker Image gibt einen ersten Eindruck über die mögliche Konfiguration von

  • Netzwerk - per default sind alle Container eines Hosts gemeinsam in einem Netzwerk isoliert
  • Ports - mit Ausnahme network: host müssen Ports explizit bekannt gemacht werden!
  • Datenspeicherung - Daten müssen außerhalb des Containers gespeichert werden.

Docker verwendet für sein bridge Network die Adresse 172.17.0.1. Lässt man den Eintrag network: in der yml Datei frei - entsteht ein Netzwerk (Typ bridge) dockertest_default mit der Adresse 172.18.0.1

Hier ein Grundgerüst für eine FHEM Umgebung mit Portainer, FHEM (Alexa im fhem Image), Homebridge und Sonos.

Hinweis: Ich bin da eine Weile drüber gestolpert: man kann bestimmte Werte in der yml Datei entweder als Array (- variable=wert) oder Dictionary (variable: wert) angeben. Details findet man in der Doku. Es gibt online Tools zum Syntaxcheck (Beispiel) aber die sagen auch nur richtig oder falsch.

version: '3'

services:
  portainer:
    image: portainer/portainer
    command: -H unix:///var/run/docker.sock
    restart: always
    ports:
      - 9000:9000
      - 8000:8000
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
  fhem:
    image: fhem/fhem:latest
    restart: always
#    networks:
#      - net
    ports:
      - "8083:8083"
      - "1883:1883"
    volumes:
      - "./fhem/:/opt/fhem/"
  sonos:
    image: svrooij/sonos2mqtt
    restart: unless-stopped
    ports:
      - "6329:6329"
    environment:
      - SONOS2MQTT_DEVICE=192.168.56.207 # Service discovery doesn't work very well inside docker, so start with one device.
      - SONOS2MQTT_MQTT=mqtt://192.168.56.121:1883 # mqtt2_server inside FHEM
      # - SONOS2MQTT_DISTINCT=true # if your want distinct topics
      - SONOS_LISTENER_HOST=192.168.56.121 # Docker host IP
      #- SONOS_TTS_ENDPOINT=http://sonos-tts:5601/api/generate # If you deployed the TTS with the same docker-compose
  homebridge:
    image: oznu/homebridge:latest
    restart: always
    network_mode: host
    environment:
      - PGID=1000
      - PUID=1000
      - HOMEBRIDGE_CONFIG_UI=1
      - HOMEBRIDGE_CONFIG_UI_PORT=8080
    volumes:
      - ./volumes/homebridge:/homebridge
volumes:
  portainer_data:

Homebridge erfordert den Host Modus. Die notwendigen Informationen zum pairen der Homebridge findet man nach dem Start im Log des Containers.

Notizen zur Installation

In Zukunft bitte compose v2  einsetzen hier steht in etwa wie es geht. Da wird sich bestimmt immer wieder was ändern. Bevor v2 installiert wird, muss docker-compose v1 deinstalliert werden.

Aktuell (Dezember 2021) funktionieren alle offiziellen Beschreibung hier für den Raspberry Pi (armv7l) nicht: https://docs.docker.com/compose/install/ 

Die Anleitung von hier, die mit pip3 arbeitet (Zeilen einzeln abarbeiten) funktioniert seit 18.12.2021 nicht mehr:

sudo apt-get install libffi-dev libssl-dev
sudo apt-get install python3 python3-pip
sudo pip3 install docker-compose

Docker-compose gibt es auch als Container für eine v1 Installation vielleicht zu bevorzugen.

Die Vorgehensweise aus der offiziellen Doku funktioniert für armhf Plattformen nicht. Ich habe hier eine Alternative gefunden. Der container hat auch Nachteile: startet langsamer, belegt mehr Platz, Installation dauert länger ...

Es wird ein Script heruntergeladen und ausführbar gemacht. Der Container wird bei beim Aufruf von docker-compose gestartet, beim ersten Aufruf wird das Image geladen.

sudo curl -L --fail https://raw.githubusercontent.com/linuxserver/docker-docker-compose/master/run.sh -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Unter Windows installiert man Docker Desktop for Windows, da ist Docker Compose mit enthalten. Docker Desktop arbeitet mit WSL zusammen.

Vielleicht ist es besser Portainer in der aktuellen Version vorab direkt zu installieren?

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
Der Parameter -v ... /var/run/docker.sock stellt dabei die Verbindung zur laufenden docker Umgebung her!

Notizen zur Verwendung

Die Sub-Befehle stop und start - stoppen und starten existierende Container. 
Die Sub-Befehle run und up erzeugen die Container neu! Vor einem erneuten run muss der Container mit rm entfernt werden.
Dabei erzeugt ein docker-compose up nur eine Neukonfiguration (und Neustart) der Container im Verbund, bei denen etwas geändert wurde.

Um aufzuräumen verwendet man entweder portainer interaktiv oder es gibt auch was für die Kommandozeile, da habe ich hier ein gute Auftsellung gefunden.

Update eines Containers

Portainer hat mir gesagt es gibt eine aktuelle Version - wie macht denn ein Update? 

Alles auf einmal ist in der Kommandozeile  simpel:

docker-compose pull
docker-compose up -d

Oder man zieht einzelne Images per Kommandozeile, dabei passiert außer dem Download nichts an der laufenden Umgebung:

docker pull imagename
# oder 
docker image pull imagename

Mit Portainer kann man das auch in der GUI machen:

Mit portainer selbst das neue Image ziehen portainer/portainer-ce:latest. Das steht dann als unused in der Liste. Danach einfach docker-compose up -d gestartet. Portainer meldet recreating und schon ist es passiert. Jetzt steht am alten Image: unused. Man kann das alte Image löschen.

Informationen des Host an die Container übergeben

Zugriffe aus dem Container heraus auf den Host sind "schwierig". Ich habe z.B. die Notwendigkeit, den Hostname des Hosts im Container zu verwenden. Man kann Informationen über Variablen zur Docker Konfiguration übertragen:

    environment:
      - HOST_HOSTNAME=${HOST_HOSTNAME}

Über das Konstrukt ${} wird eine - im Host existierende - Variable aufgelöst und die docker-compose Variable geschrieben (Die Namensgleichheit ist nicht Bedingung). Man kann/muss die Variable beim Aufruf des docker-compose Befehls setzen:

HOST_HOSTNAME=$(hostname) docker-compose up -d

Hier im Beispiel wird der Hostname per Befehl ermittelt in die Variable geschrieben und der docker-compose Konfiguration beim Start übergeben. Die Container werden  mit dieser Information gestartet.

Wichtig: Die Namensauflösung muss funktionieren!

host $(hostname)

Ich finde dieses Verfahren noch nicht schön, ich habe aber nichts besseres gefunden.

Eventuell doch:

Man kann bei docker-compose die environment Variablen durch die .env Datei ersetzen lassen. Prüfen kann man diese Funktion jederzeit mit docker-compose config. Allerdings hat die .env Datei auch keine bash Funktioninalität, um die Aufgabe zu lösen, muss man diese Datei Computer spezifisch erstellen!

echo HOST_HOSTNAME=$(hostname) > .env

Jetzt kann man jederzeit den docker-compose Befehl ohne extra vorangestellter Umgebungsvariable aufrufen. Die im Arbeitspfad liegende .env wird automatisch gelesen.

3 Kommentare:

  1. Guter Beitrag Otto!
    Ich versuche mich gerade auch mit Docker. Ich hatte Nginx als Proxy, Fhem, ConfigDB, DBLog, Mysql, Homebridge, LEPresence, I2C, Collectord, Sonos, HMCCU, MQTT und viel mehr bisher auf dem Raspberry liegen. Jetzt hab ich günstig einen ThinkCentre bekommen und möchte nun gern umziehen. Docker ist ein großes Thema und es tauchen immer wieder Probleme auf die nicht 1:1 zum Raspberry gelöst werden können. Meine Nginx Konfiguration bekomme ich z.B. nicht zum Laufen. Bin gespannt was sich bei dir tut

    AntwortenLöschen
    Antworten
    1. Hallo Mark, danke fürs Lob. Ich arbeite ev. noch etwas an diesem Artikel, eventuell schiebe ich auch noch einen nach. Die Umgebung oben ist schnell erstellt - wenn man dann die vielen kleinen Dinge in Betrieb nehmen will, entstehen schnell wieder offene Punkte:
      - mount von Serverlaufwerken
      - lokal Bluetooth
      - WOL
      Was baut man in den Container? Braucht man noch einen extra? Macht man auch ein paar Dinge einfach auf dem Host - wie "clean" lässt man den?
      Gerade kläre ich für mich noch das Thema Volume vs. Bindmount im Docker.
      Also das Thema ist gerade erst angekratzt :)

      Löschen
  2. Hi Otto,

    ich sehe ich habe einen "Seelenverwandten" gefunden der auch über all diese Themen "stolpert". Sehr schön wie du alle Themen anreißt mit denen ich auch gekämpft habe bzw. mich gerade ran taste.

    Ich habe eine Docker Umgebung mit Fhem, Raspmatic, Zigbee2mqtt und Portainer laufen. Ich arbeite gerade an der docker-compose config damit ich nicht immer alles per Hand in der richtigen Reihenfolge starten muss. Da gibt mir dein Projekt ein paar gute Tipps - danke dafür. Ich sehe es auch so wie du ... man kratzt an verschiedenen Oberflächen und darunter tun sich immer wieder große Themen auf.

    AntwortenLöschen