Container mit Bordmitteln - systemd-nspawn

1 Diese Webseite …

… befindet sich unter https://notes.defaultroutes.de

2 Namespaces

  • Dieses Kapitel zeigt, das Namespaces (und damit Container) eine Standard-Technologie des Linux-Kernel sind und keiner weiteren speziellen Software benötigt. systemd-nspawn, Podman, LXC/LXD und auch Docker sind nur Verwaltungsprogramme für die Namespaces und Controll-Groups des Linux-Kernels.
    • Dies ist ein Unterschied zu Virtuelisierungs-Lösungen wie QEMU, VirtualBox, VMWare, Xen etc. Bei diesen gibt es technische Unterschiede in der Implementation der Virtualisierung, welche sich auf den Speicherverbrauch und der Performance dieser Lösungen auswirkt.
    • Bei Containers (Namespaces) gibt es diese Unterschiede nicht, es wird immer die gleiche Namespace-Funktion des Linux-Kernels benutzt
  • Namepspaces 'virtualisieren' die Sicht auf Ressourcen des Linux-Kernels
  • Programme wie Docker, Podman, Chrome, systemd-nspawn, LXC/LXD, Firejail etc. benutzen die Namespaces im Linux-Kernel
  • Verfügbare Namespaces in Linux
Namespace Konstante Isolation
Cgroup CLONE_NEWCGROUP Cgroup root Verzeichnis (Ressourcen wie CPU/RAM)
IPC CLONE_NEWIPC System V IPC, POSIX message queues
Network CLONE_NEWNET Network devices, stacks, ports, Firewall etc.
Mount CLONE_NEWNS Mount points (Dateisysteme)
PID CLONE_NEWPID Process IDs
User CLONE_NEWUSER Benutzer und Gruppen IDs
UTS CLONE_NEWUTS Hostname und NIS Domain Name

2.1 Namespace Beispielprogramm

apt install build-essential
  • Unser Ausgangs-C-Programm: Leeres Verzeichnis anlegen und die leere Quelltextdatei im Editor öffnen
cd /usr/src
mkdir namespaces
cd namespaces
nano namespaces.c
  • Inhalt des C-Programms
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
  "/bin/bash",
  NULL
};

int child_main(void* arg)
{
  printf("Namespace aktiv\n");
  execv(child_args[0], child_args);
  printf("Ooops\n");
  return 1;
}

int main()
{
  printf("Namespace wird erzeugt\n");
  int child_pid = clone(child_main, child_stack+STACK_SIZE, \
   CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL);
  waitpid(child_pid, NULL, 0);
  printf("Namespace beendet\n");
  return 0;
}

  • Programm übersetzen
gcc -o namespaces namespaces.c
  • Programm ausführen
./namespaces
  • Den Namespace mit exit verlassen, dann einen Process-Namespace zum Programm hinzufügen
...
 int child_pid = clone(child_main, child_stack+STACK_SIZE, \
   CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | SIGCHLD, NULL);
...
  • Neu übersetzen und ausführen (im Namespace das Proc-Dateisystem neu mounten um die Prozess-Isolierung via top oder ps zu sehen: mount -t proc proc /proc)
gcc -o namespaces namespaces.c
  • Namespace via exit beenden und das proc Dateisystem auf dem Host neu mounten mount -t proc proc /proc
  • Einen Netzwerk-Namespace hinzufügen
...
  int child_pid = clone(child_main, child_stack+STACK_SIZE, \
   CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID |  CLONE_NEWNET | SIGCHLD, NULL);
...
  • Die Netzwerkconfiguration im Namespace mit ip oder ifconfig anschauen

3 Container/Namespace mit Systemd

  • Jedes Linux mit Systemd bringt mächtige Container-Verwaltungs-Werkzeuge mit
  • systemd-nspawn arbeitet neben Image-Dateien für Container auch mit installieren Linux-Root-Dateien in einem beliebigen Verzeichnis auf dem Container-Host-System
    • Damit ist es sehr einfach, ein Container-System aufzusetzen und Dateien zwischen dem Container-Host und dem Linux im Container auszutauschen

3.1 Container Linux manuell installieren

  • In diesem Kapitel installieren wir als Beispiel ein Rocky-Linux (eine freie Red Hat Enterprise Linux Distribution) in einem Verzeichnis unter Debian
    • Andere Linux-Distributionen wie Ubuntu, Suse, Arch-Linux oder ältere Debian-Versionen wären auch möglich
    • Wichig ist das die Dateien im Container-Verzeichnis für die CPU-Architektur des Container-Hosts übersetzt wurden und die Programme keinen neueren Kernel benötigen (keine neuen System-Calls)
  • Seit Debian 9 müssen die Systemd-Container-Tools separat installiert werden:
apt install systemd-container
  • Wir erstellen ein Verzeichnis für den neuen Container
mkdir -p /srv/container/rocky-linux8
  • Initialisieren eine neue RPM-Instanz im Container-Verzeichnis (bei Debian mit debboostrap, bei SUSE mit RPM und zypper)
apt -y install rpm dnf
rpm --root /srv/container/rocky-linux8 --initdb
  • Das Basis-Paket für Rocky-Linux laden
wget https://ftp.plusline.net/rockylinux/8/BaseOS/x86_64/os/Packages/r/rocky-release-8.8-1.8.el8.noarch.rpm
wget https://ftp.plusline.net/rockylinux/8/BaseOS/x86_64/os/Packages/r/rocky-repos-8.8-1.8.el8.noarch.rpm
rpm --nodeps --root /srv/container/rocky-linux8 -ivh rocky-release-8.8-1.8.el8.noarch.rpm rocky-repos-8.8-1.8.el8.noarch.rpm
  • Das Rocky-Linux Basis-System installieren
dnf -y --nogpg --releasever=8 --installroot=/srv/container/rocky-linux8 \
  install systemd passwd dnf procps-ng iproute tmux dhcp-client rocky-gpg-keys
echo 8 > /srv/container/rocky-linux8/etc/dnf/vars/releasever
  • Eine Shell im Container starten
systemd-nspawn -D  /srv/container/rocky-linux8
  • Root-Passwort setzen und neuen Benutzer anlegen
-bash-4.2$ passwd
-bash-4.2$ adduser nutzerXX
-bash-4.2$ passwd nutzerXX
  • Shell im Container verlassen
-bash-4.2# exit
  • Container booten
systemd-nspawn -bD  /srv/container/rocky-linux8
  • Weitere Software im Container installieren (hier den Apache Webserver)
dnf install httpd
  • Anwendung im Rocky Linux Container starten (prüfen das kein andere Prozess auf dem Container-Host die Ports 80 und/oder 443 belegt)
systemctl enable --now httpd
  • Der Apache Webserver innerhalb des Rocky Linux Containers sollte nun vom Firefox des Debian unter http://localhost erreichbar sein.
  • Container stoppen (im Container eingeben)
conainter# poweroff
  • Container mit privatem Netzwerk-Namespace starten:
systemd-nspawn -bD  /srv/container/rocky-linux8 --private-network

Container mit eigener (virtuellen) Netzwerkkarte (neue MAC-Adresse). enp1s0f1 ist der Name der physischen Netzwerkschnittstelle des Container-Hosts.

systemd-nspawn --network-macvlan=enp1s0f1 -M rocky01 \
  -bD /srv/container/rocky-linux8
container# ip links
container# dhclient mv-enp1s0f1
container# ip address show
container# ping 1.1.1.1
  • Systemd-Befehle um einen Container vom Host aus zu kontrollieren
machinectl list
machinectl status rocky-linux8
machinectl poweroff rocky-linux8
machinectl terminate rocky-linux8
machinectl kill rocky-linux8
  • Per nsenter mit dem Container verbinden (es wird eine Prozess-ID eines Container-Prozesses benötigt!)
nsenter -m -u -i -n -p -t <pid-im-container> /bin/bash

3.2 Container aus dem NSpawn-Image-Hub laden und starten

  • Unter https://hub.nspawn.org können fertige Systemd-NSpawn Container-Images geladen werden. Eigene NSpawn-Container-Repositories können mittels beliebiger HTTP-Server erstellt werden. Die Systemd-Container-Images werden mittels GNU-Privacy-Guard nach dem Download geprüft
    • Die Images sind mit dem GPG Schlüssel des NSpawn-Projektes signiert. Damit der Befehl machinectl die Images nach dem Download verifizieren kann, muss der öffentliche Schlüssel des Projektes in einen eigenen GPG-Schlüsselring importiert werden
    • Schlüsselring anlegen:
sudo gpg --no-default-keyring \
         --keyring=/etc/systemd/import-pubring.gpg \
	 --fingerprint
  • Schlüssel laden und importieren:
sudo gpg --no-default-keyring \
         --keyserver=keys.openpgp.org \
         --keyring=/etc/systemd/import-pubring.gpg \
 	 --search 9E31BD4963FC2D19815FA7180E2A1E4B25A425F6
  • Container-Image laden (hier ein Arch-Linux)
machinectl pull-tar https://hub.nspawn.org/storage/archlinux/archlinux/tar/image.tar.gz ArchLinux
  • NSpawn-Container-Images können in zwei Formaten bereitgestellt werden:
    • TAR: ein Tape-Archiv mit den Inhalten eines Linux-Root-Dateisystems
    • RAW: Ein Storage-Abbild einer Linux-Installation (Format QCOW2 wie von QEMU und anderen Virtualisierungs-Lösungen benutzt, oder ein per dd erstelltes Abbild eines Dateisystems
  • Docker-Images können mittels des docker oder podman Befehls in ein Verzeichnis mit einem Linux-Root-Dateisystems exportiert werden
  • Auflisten der verfügbaren Images:
machinectl list-images
  • Starten eines Systemd-nspawn Containers mittels machinectl
machinectl start ArchLinux
  • Status eines Systemd-NSpawn Container abfragen
machinectl status ArchLinux
  • Alle laufenden Container anzeigen (NSPawn und andere)
machinectl list
  • Mit dem Login-Prompt des Containers verbinden (Benutzer/Password: root=/=root)
machinectl login ArchLinux
  • Eine Root-Shell im Namensraum des Containers erzeugen
machinectl shell ArchLinux
  • Einen Container herunterfahren
machinectl poweroff ArchLinux
  • Einen Container hart (!) stoppen
machinectl terminate ArchLinux
  • Die per machinectl verwalteten Container liegen im Dateisystem unter /var/lib/machines und können auch direkt von dort gestartet werden:
systemd-nspawn -bD /var/lib/machines/ArchLinux