Docker und Container
Docker verpackt Software mit allem, was sie benötigt (Bibliotheken, ROS 2, Python-Pakete), in ein Image, das auf einem Laptop, einem CI-Runner und dem Roboter gleich läuft. Strido verwendet ein Image, strido-ros:jazzy, sowohl für die Gazebo-Simulation als auch für den echten Roboter.
In Strido
docker/ros2/Dockerfileerstellt das Workspace-Image mit ROS 2 Jazzy + Gazebo Harmonic für amd64 und arm64 (Raspberry Pi 5, Apple Silicon).docker/compose.yamldefiniert drei Services:ros(erstellt das Image),sim-smoke(Gazebo-Lauftest ohne Oberfläche) undrobot(läuft auf dem Pi mit Zugriff auf/dev).docker/ros2/entrypoint.shlädt ROS 2 und den Workspace;docker/ros2/sim_smoke.shist der automatisierte Simulationstest hintermake sim-gazebo.ROS_DOMAIN_IDist standardmäßig42(Konfiguration).
Container im Vergleich zu virtuellen Maschinen
Eine virtuelle Maschine emuliert einen ganzen Computer: eigener Kernel, virtuelle Festplatte und virtuelle Hardware. Ein Container ist nur eine Gruppe normaler Prozesse auf dem Host-Kernel, isoliert mit Linux-Namespaces (eigene Sicht auf Dateisystem, Prozessliste, Netzwerk) und begrenzt mit cgroups.
| Virtuelle Maschine | Container | |
|---|---|---|
| Kernel | Eigener Gast-Kernel | Teilt den Host-Kernel |
| Start | Zehn Sekunden und mehr | Etwa eine Sekunde |
| Overhead | Reservierter RAM und CPU | Fast keiner |
| Hardwarezugriff | Über emulierte oder durchgereichte Geräte | Gerätedateien des Hosts lassen sich direkt einbinden |
Für einen Roboter ist das wichtig: Der Pi 5 hat in diesem Aufbau 4 GB RAM, und mcu_bridge fragt alle 2 ms eine serielle USB-Schnittstelle ab. Ein Container bietet nahezu native Leistung und direkten Zugriff auf /dev/strido_mcu; eine VM würde Speicher kosten und Latenz hinzufügen.
Unter macOS betreibt Docker Desktop eine kleine Linux-VM, in der die Container laufen. Deshalb funktioniert der Gazebo-Test auf einem Mac, USB-Geräte sind dort aber schwer erreichbar: Sie testen die Simulation auf dem Laptop und die Hardware auf dem Pi.
Images und Schichten
Ein Image ist ein Stapel schreibgeschützter Schichten (Layer), eine je dateiändernder Anweisung im Dockerfile. Docker speichert Schichten zwischen: Sind eine Anweisung und alles davor unverändert, wird die zwischengespeicherte Schicht wiederverwendet. Das Strido-Dockerfile ist so geordnet, dass langsame, selten geänderte Schritte zuerst kommen:
Das Bearbeiten eines ROS-Nodes macht nur die letzten Schichten ungültig, daher dauert ein Neubau nach einer Codeänderung Sekunden bis Minuten, statt Gazebo neu herunterzuladen.
Beachtenswerte Details:
- Fixierte Abhängigkeiten. Der Slamtec-LiDAR-Treiber ist für Jazzy nicht paketiert, daher wird er auf einem festen Commit geklont (
ARG SLLIDAR_COMMIT=...). Durch das Fixieren erzeugt ein Neubau im nächsten Jahr denselben Treiber. --no-deps-Installation vonstrido-core. Das Kernpaket (Gait, Kinematik, Protokoll, NumPy-Policy-Laufzeit) benötigt zur Laufzeit nur NumPy, das apt bereits installiert hat. Weder PyTorch noch CadQuery noch MuJoCo gelangen in das Roboter-Image (ADR-003 in Entscheidungen).rm -rf /var/lib/apt/lists/*im selbenRUNwieapt-get installhält den Paketindex aus der Schicht heraus. Ihn in einer späteren Schicht zu löschen, würde das Image nicht verkleinern, weil frühere Schichten unveränderlich sind.- Multi-Architektur.
ros:jazzy-ros-basewird für amd64 und arm64 veröffentlicht, daher baut dasselbe Dockerfile nativ auf dem Pi und auf einem Mac mit Apple Silicon.
Container zur Laufzeit
Ein Container ist eine laufende Instanz eines Images plus einer dünnen beschreibbaren Schicht. Alles, was darin geschrieben wird, verschwindet, wenn der Container entfernt wird (--rm). Das passt zum Roboter: Zustand, der erhalten bleiben muss (Servokalibrierung), liegt im Flash des ESP32, nicht im Container.
Der Entrypoint besteht aus drei Zeilen:
source /opt/ros/jazzy/setup.bash
source /ws/install/setup.bash
exec "$@"Damit sieht jeder übergebene Befehl (zum Beispiel ros2 launch ...) sowohl ROS 2 als auch die Strido-Pakete, und exec ersetzt die Shell, sodass Signale wie Strg-C ROS direkt erreichen.
Compose: docker/compose.yaml des Repos
Docker Compose beschreibt mehrere Container in einer YAML-Datei, sodass Sie sich keine langen docker run-Befehlszeilen merken müssen.
| Service | Funktion | Wichtige Einstellungen |
|---|---|---|
ros | Erstellt strido-ros:jazzy aus docker/ros2/Dockerfile (Kontext = Repo-Wurzel) | network_mode: host, ipc: host, ROS_DOMAIN_ID |
sim-smoke | Führt /sim_smoke.sh aus: Gazebo ohne Oberfläche, aufstehen, 8 s mit 0,15 m/s laufen, ≥ 0,4 m Fortschritt und Sensor-Topics prüfen | der Exit-Code ist das Testergebnis |
robot | Führt ros2 launch strido_bringup robot.launch.py auf dem Pi aus | privileged: true, /dev:/dev, Host-Netzwerk |
Befehle, die Sie verwenden werden:
docker compose -f docker/compose.yaml build ros # build or update the image
docker compose -f docker/compose.yaml run --rm sim-smoke # headless Gazebo test (what make sim-gazebo runs)
docker compose -f docker/compose.yaml run --rm robot # on the Pi: bring up the robot
docker compose -f docker/compose.yaml run --rm ros bash # interactive shell with ROS 2 sourced
docker compose -f docker/compose.yaml run --rm ros \
ros2 run teleop_twist_keyboard teleop_twist_keyboard # drive from a second terminalmake sim-gazebo POLICY=<policy.npz> bindet eine gelernte Policy schreibgeschützt in den Container ein (-v ...:/policy.npz:ro) und setzt POLICY=/policy.npz, wodurch sim_smoke.sh policy_node statt gait_node startet.
Warum Host-Netzwerk und Host-IPC
ROS 2 findet Nodes über DDS-Multicast und kann zwischen Prozessen auf demselben Rechner Shared Memory verwenden. Das Standard-Bridge-Netzwerk von Docker verbirgt Multicast, und ein privater IPC-Namespace verbirgt Shared Memory. network_mode: host und ipc: host setzen den Container in das Netzwerk und den IPC des Hosts, sodass ein Teleop-Container, der Roboter-Container und ein Laptop im selben WLAN (gleiche ROS_DOMAIN_ID) die Topics der anderen ohne zusätzliche Konfiguration sehen.
Verwenden Sie je Roboter eine andere ROS_DOMAIN_ID, wenn sich mehrere ein Netzwerk teilen:
ROS_DOMAIN_ID=43 docker compose -f docker/compose.yaml run --rm robotGerätezugriff
Ein Container sieht keine Host-Hardware, sofern Sie sie nicht freigeben. Der Service robot tut das ohne Umschweife:
privileged: true
volumes:
- /dev:/devDas gesamte /dev per Bind-Mount einzubinden ist wichtig, weil die udev-Symlinks (/dev/strido_mcu, /dev/rplidar) vom udev des Hosts angelegt werden und ein USB-Gerät, das abgesteckt und wieder angesteckt wird, einen neuen Geräteknoten erhält. Eine feste devices:-Liste würde nur Knoten einbinden, die beim Start des Containers existieren. privileged: true hebt die Geräte-cgroup-Beschränkungen auf, sodass die neuen Knoten nutzbar sind.
WARNING
Ein privilegierter Container hat nahezu volle Kontrolle über den Host. Auf einem dedizierten Roboterrechner ist das akzeptabel; verwenden Sie den Service robot nicht auf Ihrem Alltagslaptop. Für die Simulations-Services, die keine Hardware benötigen, sind keine Privilegien gesetzt.
Die udev-Regeln müssen auf dem Host installiert werden, nicht im Image (Linux auf dem Pi).
Was nicht im Image ist
- Kamera-Node. Der Kamera-Stack des Pi 5 benötigt den libcamera-Fork von Raspberry Pi; das Image hat keinen Kamera-Node, und
robot.launch.pyverwendet standardmäßigcamera:=false. Bauen Siecamera_rosauf dem Host oder verwenden Sierpicam-vidmit einer GStreamer-Brücke (Software). - Firmware-Werkzeuge. PlatformIO ist nicht installiert; flashen Sie den ESP32-S3 von einem Entwicklungsrechner aus oder installieren Sie PlatformIO auf dem Pi (Firmware).
- GUI. Gazebo mit Fenster (
gazebo.launch.pyohneheadless:=true) benötigt eine X11/Wayland-Weiterleitung in den Container (Simulation).
Selbsttest
- Warum startet ein Container schneller und braucht weniger Speicher als eine VM?
- Welche Dockerfile-Änderung löst den kürzesten Neubau aus, und warum?
- Was funktioniert nicht mehr, wenn Sie
network_mode: hostaus dem Servicerobotentfernen? - Warum bindet der Service
robotganz/devein, statt zwei Geräte aufzulisten? - Wo muss
99-strido.rulesinstalliert sein, damit der Container/dev/strido_mcusieht?
Antworten
- Er teilt den Host-Kernel: kein Gast-Betriebssystem zu booten und kein reservierter RAM; es sind nur isolierte Prozesse.
- Das Ändern von Dateien in
ros2/macht nur dieCOPY ros2/...-Schichten und das anschließendecolcon buildungültig; die apt- und Git-Schichten kommen aus dem Cache. - Die DDS-Erkennung über Container und Rechner hinweg; Teleop oder RViz in einem anderen Container oder auf einem Laptop würden die Topics des Roboters nicht mehr sehen.
- udev auf dem Host legt die Symlinks an, und wieder angesteckte Geräte erhalten neue Knoten; eine statische Geräteliste würde sie verpassen.
- Auf dem Host, in
/etc/udev/rules.d/; der Container sieht das Ergebnis über den Bind-Mount von/dev.