Einleitung

Docker Container werden ja immer beliebter aber leider gibt es keine direkte Unterstützung für FreeBSD. Auch sind leider richtig gute Open Source Projekte "Docker only" und beschreiben noch nicht mal mehr eine manuelle Installation, was die Portierung und den Betrieb in FreeBSD/Jails erschwert. Mit der richtigen, vor allem schlanken, Linux Distribution ist aber auch das kein Problem und ermöglicht mit dem folgenden Aufbau auf Basis der bereits vorbereiten BHYVE Virtualisierung einen Jail artigen Betrieb unter FreeBSD.

Es gibt seit FreeBSD 14 eine experimentelle Unterstützung für Podman, welches dann nativ Docker Container ausführen kann. Das könnte in Zukunft noch eine echte Alternative werden, aber ich beleuchte hier erst mal den "stabilen" Ansatz. Vielleicht wird der ja dann überflüssig. Schön wäre das.

Ziele

Das Ziel ist ein System, welches die Daten, bzw. Konfiguration vom System bzw. der VM trennt und damit komplett unabhängig ist, ohne dabei allzu sehr in die von den Projekten bereitgestellten Docker Compose Dateien eingreifen zu müssen. Ganz so, was ich eigentlich bei den nativen FreeBSD Jails sehr schätze.

Hier haben wir nur den "Schmerz" einer, dafür aber sehr schlanken, Virtualisierung. Das fällt heutzutage aber kaum noch ins Gewicht und ich habe damit durchaus meinen Frieden gemacht. Schlank bedeutet, das wir auch auf "Extras" wie grafische Oberflächen oder VNC Konsolen verzichten. Pure Konsole.

NEU: Für ganz ungeduldige habe ich einen "Konsole only" Abschnitt. Da gibt es nur Befehle, keine Erklärungen.

Letzte Aktualisierung:

  • 29.12.2024: NFS anstelle von virtio-9p
  • 22.12.2024: Letzter Feinschliff und Freigabe als Start einer eigenen Artikelreihe
  • 11.11.2024: Initiales Dokument

Grundbedingungen

Eckdaten

  1. Eine 16 GB kleine System Partition mit Alpine Linux als ZVol in /usr/local/bhyve/docker/root. docker wird der Name der VM. Also nur das sehr schlanke Basissystem, was Updates sehr einfach macht und lassen mit 16GB etwas Luft für die Docker Images. Aber keine Angst, die 16 GB werden nicht direkt zugeweisen. Durch sparse-zvol ist das ZVol auf dem Host System nur so groß, wie Daten drin liegen. Am Anfang sind das weniger als 100 MB.
  2. Die Konfigurationen und möglichst die eigenen Daten der Docker Applikationen (z.B. Datenbank, Bilder, Dateien) werden innerhalb der VM in /opt/docker abgelegt und mittels NFS nach /usr/local/bhyve/data/docker verbunden.
    Damit sind die wirklich wichtigen Daten völlig unabhängig vom System und können, zusätzlich zu den ZFS-Snapshots, separat als Archiv gesichert und wiederhergestellt werden.

Warum nicht das /var/lib/docker Verzeichnis komplett "rauslegen"? Docker arbeitet mit sogenannten "Overlay Images" um Platz zu sparen, was nicht per NFS funktioniert. Ohne Overlay hätten wiederkehrende Docker Images auch wiederkehrenden Platzbedarf.

Warum nicht alles in /usr/local/bhyve/docker ablegen? Wird die VM mit vm destroy docker entfernt, wird das komplette Dataset inkl. aller Unterordner entfernt, AUCH wenn manuell drunter Datasets hinzugefügt wurden. Böse Falle! Daher gibt es das Verzeichnis /usr/local/bhyve/data, genauso, wie das auch bei /usr/local/bastille/data in den anderen Jails Artikeln gemacht wird.

Diagramm

Damit sieht das Setup so aus:

                    ┌───────────────────────────────────────────────────────┐
                    │ FreeBSD:                                              │
                    │ ┌────────────────────┐                                │
                    │ │ VM: docker         │                                │
                    │ │   / ───────────────┼─► /usr/local/bhyve/docker/root │
                    │ │   /opt/docker ─────┼─► /usr/local/bhyve/data/docker │
                    │ └────────────────────┘                                │
                    └───────────────────────────────────────────────────────┘

Ich gehe hier nur auf einen "Grundbetrieb" ein, der für normale und heimische Installationen für eine handvoll Benutzer ausreichen wird. Bei Anwendungen, die darüber hinaus gehen, müssen die Themen CPU, RAM und SWAP nochmal genauer betrachtet und ggf. angepasst werden. Auch kann sicherlich noch das ein oder andere Verzeichnis zusätzlich nach außen gelegt werden.

ZFS

Optional: Wenn der zusätzliche data Pool genutzt werden soll, dann kann dieser mit zfs create -o mountpoint=/usr/local/bhyve/data data/byhve mit angelegt werden. Die Verzeichnisstruktur per zfs list | grep bhyve sieht dann so aus:

# zfs list | grep bhyve
data/byhve                             96K  1.75T    96K  /usr/local/bhyve/data
work/bhyve                             96K   893G    96K  /usr/local/bhyve/

Installation

Als erstes legen wir für Alpine Linux mit ee /usr/local/bhyve/.templates/alpine.conf ein Template an, was die Standards der VM festlegt. memory und disk0_size bei Erstellung angegeben

loader="uefi"                 # UEFI boot loader
cpu=2                         # 2 CPUs
network0_type="virtio-net"    # Virtio network driver
network0_switch="public"      # Name of the Switch
disk0_type="virtio-blk"       # Virtio storage driver (sata)
disk0_dev="sparse-zvol"       # Use growing ZVOLs
disk0_name="root"             # ZFS Volume name in /tank/bhyve/NAME
grub_install0="linux /boot/vmlinuz-virt initrd=/boot/initramfs-virt alpine_dev=cdrom:iso9660 modules=loop,squashfs,sd-mod,usb-storage,sr-mod"
grub_install1="initrd /boot/initramfs-virt"

Die eigentliche Erstellung der VM ist dann mit vm create -s 16G -t alpine docker schnell erledigt und gefolgt von einem vm install docker alpine-virt-3.20.3-x86_64.iso wird die VM von der heruntergeladenen ISO gestartet. Tut euch aber selber einen gefallen und ruft vm console docker in einer zweiten parallelen Shell auf:

  • In dieser wird nun Alpine Linux auf die virtuelle Festplatte installiert
    • Mit root einloggen ( Kein Passwort)
    • Dann mit setup-alpine die Installation starten. Wenn ich nichts angebe, einfach mit Enter weiter springen:
      • Hostname: docker.bsdbox.local
      • Changing password for root: 123
      • Which timezone are you in?: Europe/Berlin
      • Which NTP client to run?: none
      • Which ssh server?: openssh
      • low root ssh login?: yes
      • Which disk(s) would you like to use?: vda
      • How would you like to use it?: sys
    • Fertig und nun mit halt das System beenden

Tipp: Die vm console kann mit der Tastenfolge Enter ~ ~ . beendet werden

Damit ist die Installation fertig und das installierte System kann nun mit vm start docker final gestartet werden. Mit vm console docker wird eine Konsole eröffnet (besser in einer zweite Shell) und es erscheint der typische Login von Linux.

Tipp: Wer sich die IP Adresse bei der Installation gemerkt hat, kann alternativ ab hier mit zwei SSH Shells arbeiten.

zfs create data/bhyve/docker
zfs set sharenfs="maproot=root:wheel,network=192.168.1.12/32" data/bhyve/docker
zfs get sharenfs data/bhyve/docker # Order und Unterordner teilen (wird vererbt)
zfs unshare -a
zfs share -a

Nun per root einloggen und mit mkdir /opt/docker das Datenverzeichnis erstellen, in der wir das extern liegende /usr/local/bhyve/data/docker einbinden wollen. Dazu wird mit vi /etc/fstab die fstab um folgenden Eintrag erweitert:

coretwo:/usr/local/bhyve/data/docker /opt nfs4 rw 0 0

Mit mount -a wird alles, was in der /etc/fstab steht, auch ohne Neustart eingebunden.

Docker

Docker selber ist nach der ganzen Vorarbeit dann eigentlich schnell konfiguriert und gestartet:

Paketquellen

setup-apkrepos -cf # Paketquellen ermitteln
apk update # Paket Datenbank aktualisieren
apk upgrade --available # Pakete aktualisieren

Installieren

apk add docker docker-cli-compose # Docker installieren
rc-update add docker default # Docker automatisch starten 
service docker start # Docker starten

Mit einem reboot kann und sollte noch geprüft werden ob alles nach einem Neustart klappt:

  • Der Docker Dienst läuft. Das kann mit docker ps geprüft werden.
  • Das Verzeichnis /opt/docker/ ist eingebunden. Das kann mit mount | grep docker geprüft werden.

Backup

Neben dem Snapshot der ZFS Datasets kann auch ein komplettes Backup als TAR-Archiv erstellt werden. Das Archiv enthält die Konfigurations-Dateien und die Daten der Docker Container. Das ist ideal, um es (am besten verschlüsselt) extern zu sichern. Nun müssen wir noch entscheiden, wo das Archiv gespeichert werden soll. Hier verwenden wir das Verzeichnis /opt/docker/backup, welches von außerhalb des VM gemountet wurde. Damit sind die Backups sofort unabhängig gespeichert und können dann über Snapshots wieder separat gesichert und weiterverarbeitet werden.

Manuell

Ein manuelles Backup wird mit einem Befehl ausgeführt, insbesondere vor einem Update:

mkdir /opt/docker/backup/
tar -cpzf /opt/docker/backup/APPNAME_`date +%Y%m%d`.tar.gz /opt/docker/APPNAME/

Automatisch

Regelmäßige Backups sind das A und O einer Strategie, insbesondere wenn es sich um so wichtige Daten handelt. Das Aufräumen sollte auch in der VM selbst stattfinden, damit es nur ausgeführt wird, wenn die VM auch wirklich läuft. Ansonsten laufen die Backups leer.

vi /etc/crontabs/root

# House keeping
10 22 * * * root "find /opt/docker/backup/ -type f -mtime +30d -delete"
# APPNAME backup
0 22 * * * root "tar -cpzhf /opt/docker/backup/APPNAME_'$(date +\%Y\%m\%d)'.tar.gz /opt/docker/APPNAME"

Updates

cd /opt/docker/APPNAME
docker-compose stop # Dienst stoppen
docker-compose pull # Neue Images herunterladen
docker-compose up --force-recreate --build -d # Dienst neu erstellen
docker image prune -f # Alte Images entfernen

Konsole

zfs create -p zroot/bhyve_data/docker
vm iso https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-virt-3.20.3-x86_64.iso
ee /zroot/bhyve/.templates/alpine.conf # Siehe oben
vm create -t alpine docker
vm install docker alpine-virt-3.20.3-x86_64.iso

# In einer zweiten parallelen Shell
vm console docker
setup-alpine # Siehe oben

# In der ersten Shell
vm restart docker

# In einer zweiten parallelen Shell oder per SSH
vm console docker # oder ssh root@DOCKERHOST
mkdir /opt/docker
echo 'docker /opt/docker 9p trans=virtio,rw 0 0' >> /etc/fstab
mount -a
setup-apkrepos -cf
apk update
apk upgrade --available
apk add docker docker-cli-compose
rc-update add docker default
service docker start
reboot

Voilá