Docker Swarm, die 2. - mit USB

Vor einigen Woche habe ich mit Docker Swarm experimentiert um einige lokale Services zuhause um zu stellen: Raspis mit Docker ist ganz praktisch und um Schwarm wäre das Setup leichter zu verwalten als überall mit einem Compose File zu arbeiten - so der Gedanke.

Doch dabei kamen einige Unschönheiten und zuletzt Probleme in den Weg, die mich dann doch zu Compose-Files gebracht haben.

Per Remote-Compose war das dann auch recht praktisch:

1DOCKER_HOST="ssh://pi@raspi1" docker compose up -d --remove-orphans

Bis dann nach einem Linux-Update inklusive Docker-Update nach dem Reboot die Container nicht mehr alle hochgefahren sind.

Da hat sich dann auch das Logging via Fluentd ( ☛ Stetig locken die Logs) gerächt: ist der nicht zu erreichen, beendet Docker einfach mal den Container!

Der Profi sagt jetzt mode: non-blocking. Hoffentlich hält das.

Aber Tatsache ist, dass die Images nicht mehr hoch kamen und nichts sich darum gekümmert hat. Im Schwarm - so meine Hoffnung - wird das so nicht sein, da wird irgend ein Swarm Master das immer wieder veranlassen.

Zurück zu dem Problemen: Ein Image muss nämlich auf das USB-Device zurück greifen. Das kann Swarm Mode aber nicht:

1   devices:
2     - /dev/ttyUSB0

Das ist im Swarm nicht möglich, weder per Stack noch per create service. Findet sich auch in diversen Foren.

Aber der Workaround - der finale, nicht irgendwelche gebastelten - ist genial (Github Diskussion🖹):

  • ein Image mit Docker (DOD, Docker-Out-Of-Docker), das das Compose-File kennt
  • starten von docker-compose mit eben jenem File startet dann das Image lokal und damit gehen Devices

Wegen Raspi und Armv7 gibts nicht auf Docker Hub und weil ich sowieso das Compose-File im Image will, gleich mal das passende Image selbst gebaut:

1FROM alpine:3.16
2RUN apk add --no-cache docker-compose tzdata
3ENV TZ=Europe/Berlin
4ENV COMPOSE=test
5RUN mkdir /compose/
6COPY --chmod=700 entry.sh /entry.sh
7COPY *-compose.yml /compose/
8CMD ["/entry.sh"]

Es reicht das Package "docker-compose".

Und ich packe gleich alle meine Composes, die in den Wrapper sollen, ins Image. Das erspart mit ein extra Netzwerkvolume, auf dem die Datein liegen müssten. Klar, so muss ich ein neues Image bauen, wenn sie die Compose-Files ändern. Aber ganz ehrlich: den Imagebau habe ich sowieso im Makefile und macht somit dann keine große Arbeit. Das NFS-Share wieder extra anzulegen, die Dateien dann dorthin zu kopieren und das ganze testen macht mehr Mühe. Und wenn ein neues Compose-File da ist muss ich den Stack sowieso neu starten. Nur dass der das ohne neues Image gar nicht für relevant hält 😬.

Das Entry-Script:

 1#!/bin/sh
 2
 3CFILE=/compose/${COMPOSE}-compose.yml
 4
 5echo "Running $CFILE"
 6
 7if [ -e "$CFILE" ]; then
 8        mkdir -p /compose/up/${COMPOSE}
 9        cd /compose/up/${COMPOSE}
10        docker-compose -f $CFILE up --remove-orphans
11        e=$?
12        echo "docker-compose finished: $e"
13        exit $e
14else
15        echo "$CFILE not found!"
16        exit 127
17fi

Und fertig ist die Lösung. Im Stack sieht das dann so aus:

1version: '3.9'
2
3services:
4  ser2net:
5   image: swarmwrapper:latest
6   volumes:
7      - /var/run/docker.sock:/var/run/docker.sock
8   environment:
9      - COMPOSE=ser2net

Und das Compose dazu dann so:

 1version: '3.9'
 2
 3services:
 4  ser2net:
 5   container_name: ser2net
 6   image: ser2net:latest
 7   restart: unless-stopped
 8   devices:
 9     - /dev/ttyUSB0
10   ports:
11     - target: 3501
12       published: 3501
13       protocol: tcp
14       mode: host

docker stack up

Jaja, geht natürlich nicht sofort. Denn sobald man in dem swarmwrapper Image mit einer privaten Registry mit Passworten arbeitet, kommt der nicht an die Images.

Also muss noch das passende Json-File ins Image:

1FROM alpine:3.16
2RUN apk add --no-cache docker-compose tzdata
3ENV TZ=Europe/Berlin
4ENV COMPOSE=test
5RUN mkdir /compose/
6COPY --chmod=700 entry.sh /entry.sh
7COPY config.json /root/.docker/config.json
8COPY *-compose.yml /compose/
9CMD ["/entry.sh"]

Ja, Reihenfolge der COPYs ist so, das was sich am ehesten ändert steht weiter am Ende.

siehe auch