Docker ist omnipräsent, ebenso wie es extrem günstige linuxfähige System on a Chip (SoC)-Geräte sind. Nach Beagleboard, Pandaboard, diversen Raspberry Pi etc sind wir bei 15€ pro System inkl. WiFi angekommen. Unfassbar für ein vollwertiges System, dessen Fähigkeiten für so manchen Büroarbeitsplatz ausreichen würden.
Die logische Konsequenz: Wenn man auf jedem Stück Blech die neuesten Linux-Distributionen und Kernel-Versionen laufen lassen kann, warum dann auch nicht Docker?
Man möchte allerdings die bestmöglichste Hardware (bzw Cloud-Service) zum Bau von Images für alle Plattformen nutzen und verwendet daher Cross-Compilation (typischerweise unter amd64/x86_64).
Die technischen Details sind nichts neues, deshalb erspare ich sie euch bis auf eine kurze Umschreibung: QEMU als Emulator, binfmt_misc um architekturfremde Binaries durch QEMU ausführen zu lassen. Und um das ganze mit Docker zu verheiraten, schiebt man das statische QEMU binary in den Build-Context und zwar an die Location, die man dem Kernel vorher global konfiguriert hat. Mehr dazu steht beispielsweise hier.
Zwei Dingen fehlten mir jedoch bis dahin:
- Ich lasse Docker Hub automatisch Images aus GitHub Repositories bauen und möchte, dass Docker Hub mir für alle von mir gewünschten Architekturen automatisch Images baut.
- Ich möchte einen gemeinsamen tag für alle Plattformen haben, d.h. egal auf welchem system docker pull rmoriz/multiarch-test:latest soll das passende Image zurückliefern.
Die Integration Dockerhub lässt sich mit etwas Shell-Frickelei lösen, hat man dort doch relativ großzügige root-Rechte, kann den gesamten Build-Prozess mit Shellscripts anpassen oder überschreiben und natürlich Pakete installieren. Sogar das Setzen von binfmt war problemlos möglich. Erstaunlicherweise war das größte Problem die dort eingesetze steinalte Docker EE-Version auf Client-Seite. Auch hier kann man glücklicherweise stattdessen ein eigenes, neueres Binary ausführen.
Um mehrere Architekturen hinter einem tag zu verbergen, muss man Manifests anlegen. Alles noch experimental, erfordert das neueste docker-cli und schlecht dokumentiert.
Der grobe Ablauf:
- Installiere QEMU Pakete, eigener docker client
- registriere binfmt handler
- baue alle images mit jeweils eigenen Tags für jede Architektur
- exportiere alle images auf festplatte, patche im Image-Manifest die Architektur auf die Zielarchitektur (hier wird IMMER die aktuelle dockerd-Architektur übernommen)
- importiere den Kram wieder
- pushe die Images in die Registry
- Erstelle ein Manifest
- Annotate das Manifest, damit die Archtekturen stimmen (eventuell durch 4. überflüssig geworden)
- pushe das Manifest in die Registry
- done
Große Frickelei, für euch an zwei Abenden durch trial+error gebaut:
Source Code hier: https://github.com/rmoriz/multiarch-test
Beispiel Log des Builds: https://gist.github.com/rmoriz/4204f1f30eeda3892248e797024ed794