systemd-nspawn

Permet de faire des conteneur/chroot gérés directement par systemd. C'est plus simple que chroot dans le sens ou une grande partie des opération préliminaire sont automatisées.

Les système guest utilisant systemd peuvent être facilement démarré comme des conteneur. Il est aussi possible de démarrer (booter) un système sous System V dans un nspawn mais cela nécessite quelques manipulations. C'est à dire qu'au lancement du guest, ce n'est pas un shell qui est lancé mais init (comme au boot d'une machine physique).

Par la suite j'utiliserai le mot nspawn pour désigner un guest dont le premier procéssus est un shell (comme un chroot classique). J'utiliserai le mot conteneur pour désigner un système guest dont le premier procéssus est init.

Basic

Le système guest est installé dans /opt/arch64.

nspawn

Démarrage

# systemd-nspawn -D /opt/arch64

Arrêt depuis le nspawn :

  • Ctrl+D
  • exit

Arrêt depuis l'hôte :

# machinectl terminate arch64

conteneur

Démarrage :

# systemd-nspawn -b -D /opt/arch64

Lancement d'une autre console dans le conteneur :

# machinectl login arch64

Arrêt depuis le nspawn :

# systemctl poweroff

Arrêt depuis l'hôte :

# machinectl poweroff arch64

Options

Quelques options qui m'ont déjà été utiles (classées par ordre d'utilitée) :

  • -b, --boot permet de démarrer le système en temps que conteneur (init) plutôt qu'en temps que nspawn (bash).
  • --bind=, --bind-ro= permet de créer un montage bind automatique entre le host et le nspawn. L'argument peut être soit de type « path » soit de type « path1:path2 ». Dans le cas « path » le point de montage sera identique dans le host et dans le nspawn. Par exemple avec --bind=/super/path, la commande ls /super/path renvera la même chose quelle soit lancée dans le host ou le nspawn. Dans le cas « path1:path2 », le repertoire « path1 » dans le host sera monté dans le répertoire « path2 » dans le nspawn. Par exemple avec --bind=/super1/path:/super2/path, la commande ls /super1/path lancée sur le host renvera le même résultat que la commande ls /super2/path lancée dans le nspawn.
  • --template= utilise le répertoire passé en paramètre pour créer la répertoire de destination/d'execution du nspawn (spécifié par --directory). Si le répertoire de destination est déjà présent cette option n'a aucun effet. Si le répertoire de « template » est un subvolume btrfs et que le répertoire de destination se trouve sur une partition btrfs, systemd-nspawn créé un snapshot du subvolume template. Si l'une des conditions n'est pas remplie, systemd-nspawn fait juste une copie complète du repertoire. Cette option est incompatible avec --ephemeral et --image.
  • -x, --ephemeral fait en sorte que le nspawn soit temporaire. Cette option ne fonctionne que si le répertoire spécifié par l'option --directory est un subvolume btrfs. Elle créé un subvolume temporaire caché, dont le nom commence par « .# », pour lancer le nspawn et le détruit si tôt le npsawn arrété.
  • -M, --machine= permet de spécifier le nom du nspawn.
  • --personality= permet de lancer un nspawn en simulant une architecture différente (retour de uname) que celle sur laquelle il est en réalité. Ça à le même effet que setarch. Actuellement (26/03/2015) seul les arguments « x86 » et « x86-64 » sont acceptées. Pour éviter l'oubli de cette option je vous conseille de mettre dans le « bashrc » ou le fichier « profile » (suivant la distribution) de vos nspawn 32 bits :

    if test `arch` != "i686"
    then
            echo -e '\n\n!!!!! WRONG PERSONALITY !!!!!\n\n'
    fi
    
  • --capability= permet d'ajouter des capability supplémentaire au container. Ça permet de donner certains droit/capacité (des « capability ») au processus du conteneur. Dans l'exemple suivant je donne la capability CAP_SYSLOG au conteneur ce qui lui permet d'utiliser la commande klogctl. Attention cependant aux conséquences pour le système hôte.

    max@max-laptop % sudo systemd-nspawn -x -b -D debian-wheezy-test
    Spawning container debian-wheezy-test-0e3ef4ca8d80cd88 on /home/max/nspawn/…
    
    Starting enhanced syslogd: rsyslogd.
    dmesg: klogctl failed: Operation not permitted
    
    Container debian-wheezy-test-0e3ef4ca8d80cd88 has been shut down.
    max@max-laptop % sudo systemd-nspawn -x -b --capability=CAP_SYSLOG -D debian-wheezy-test
    Spawning container debian-wheezy-test-2d2de4df260a6ff3 on /home/max/nspawn/…
    
    Container debian-wheezy-test-2d2de4df260a6ff3 has been shut down.
    
  • -u, --user= permet de spécifier l'utilisateur (par défaut « root ») qui lancera la commande à l'interieur du nspawn (par défaut « bash »). Le nspawn est toujours lancé en temps que root et l'utilisateur doit être enregistré (useradd) dans le nspawn. Cette option ne semble pas fonctionner pas avec l'option -b, --boot. Par exemple :

    (shell 1)# systemd-nspawn -u max -D test
    (shell 2)% pstree -us 19115
    …───sudo(root)───zsh───systemd-nspawn───bash(max)
    

Copies

Pour copier depuis l'hôte vers le nspawn et vice-versa :

% machinectl copy-to … path path
% machinectl copy-from … path path

Les « path » doivent être des chemins absolue (pas de « ~ », de « . », de « .. ») et désigner un fichier (même pour la destination). Il ne semble pas possible de copier un dossier avec ces commandes.

# machinectl copy-to sles12-build-5852b195cc46204a `pwd`/build.sh /usr/src/packages/build.sh
Failed to copy: Access denied
# machinectl copy-from sles12-build-5852b195cc46204a /usr/src/packages/changes_file.patch `pwd`/changes_file.patch

J'obtient un « Access denied » à la première commande mais le fichier a été copié.

Networking

Dans cette section je vais décrire comment faire en sorte que chaque nspawn ai sa propre adresse IP NATé ; qu'il puisse communiquer avec l'exterieur et qu'il puisse communiquer entre eux et avec le host via leurs hostnames.

Plusieurs options correspondant au réseau sont disponible (voir la page man de systemd-nspawn). Nous allons nous intéresser à deux options en particulier :

  • -n, --network-veth : permet de créé un lien ethernet virtuel (veth) entre le host et le nspawn. Sur le host, une nouvelle interface sera créée, le nom de cette interface correspond au prefix « ve- » suivit du nom du nspawn (cf. option --machine). Sur le nspawn, l'interface sera nomée « host0 ». Seul cette interface fait le lien entre le host et le nspawn, les autres interface ne sont plus partagée comme c'est le cas par défaut ; cette option implique --private-network.
  • --network-bridge= : implique --network-veth mais sur le host, ajoute l'interface virtuel dans le bridge spécifié. Le préfix de l'interface virtuel sur le host n'est plus « ve- » mais « vb- ». Cette option vas nous permettre de faire communiquer différent nspawn via leur hostname (utilisation du protocole LLMNR directement implémenté dans systemd-resolved).

Si vous lancez directement le nspawn avec l'option --network-veth (avant de faire d'autre configuration), les interfaces virtuelles seront créé mais n'aurons pas d'IPv4.

Pour faire en sorte d'assigner automatiquement une IPv4 à l'interface « ve-… » sur le host nous allons utiliser le service systemd-networkd. Créé le fichier /etc/systemd/network/50-containers.network :

[Match]
Name=ve-*

[Network]
Address=0.0.0.0/28

(Re)lancer le service et le nspawn :

# systemctl restart systemd-networkd.service
# machinectl poweroff …
# systemd-nspawn --network-veth -D …

Sur le host l'interface host0 a maintenant une IPv4. Cette IP a été choisie automatiquement par systemd grace à l'option Address=0.0.0.0/28 ; elle a été choisie pour ne pas créer de conflit avec les adresse des autres interfaces.

L'interface virtuelle du host a maintenant une IPv4 mais pas celle du nspawn. Pour l'instant vous ne pouvez pas en obtenir automatiquement, vous devrez la fixer manuellement.

Nous allons configurer systemd-networkd pour qu'il active un mini serveur DHCP sur l'interface virtuel du host de manière à allouer automatiquement une adresse au nspawn.

Modifier le fichier /etc/systemd/network/50-containers.network pour ajouter l'option DHCPServer=yes dans la section « Network ». Puis relancer le service systemd-networkd et le nspawn.

[Match]
Name=ve-*

[Network]
Address=0.0.0.0/28
DHCPServer=yes

Si le nspawn contient un service pour configurer ses interface via DHCP, vous obtiendrez une IPv4. Sinon, dans le nspawn, lancer simplement dhclient host0 ou dhcpcd host0 ou autre pour obtenir une IP.

Le host et le nspawn peuvent maintenant communiquer via leur IP respective.

Pour faire en sorte que le nspawn puisse communiquer avec l'exterieur (routage) et que les paquets venant du nspawn soit vue par l'exterieur comme venant du host (NAT) nous allons encore utiliser une fonctionnalité de systemd-networkd.

Modifier le fichier /etc/systemd/network/50-containers.network pour ajouter l'option IPMasquerade=yes dans la section « Network ». Puis relancer le service systemd-networkd et le nspawn.

[Match]
Name=ve-*

[Network]
Address=0.0.0.0/28
DHCPServer=yes
IPMasquerade=yes

Cependant, « IPMasquerade » n'a fixé que la valeur sysctl « net.ipv4.conf.ve-….forwarding » à 1 (en plus d'avoir ajouté une entré dans la table « nat » de netfilter, cf. iptables -t nat -L). Pour que les paquets puissent être routé dans le chemin inverse (de l'exterieur vers le nspawn) vous devrez placer « net.ipv4.ip_forward » à 1 (active le forwarding sur toutes les interfaces) ou « net.ipv4.conf.enp3s0.forwarding » à 1 (où enp3s0 est l'interface connectée à l'exterieur).

Pour « net.ipv4.ip_forward=1 » :

# cat > /etc/sysctl.d/50-containers.conf
net.ipv4.ip_forward=1
^C

Pour « net.ipv4.conf.enp3s0.forwarding=1 » :

# cat > /etc/sysctl.d/50-containers.conf
net.ipv4.conf.enp3s0.forwarding=1
^C

ou, pour « net.ipv4.conf.enp3s0.forwarding=1 » :

# cat > /etc/systemd/network/enp3s0.network
[Match]
Name=enp2s0

[Network]

IPForward=yes

Pour que le host et les nspawn puisse communiquer entre eux via leur hostname respectif, nous allons utiliser une fonctionnalité LLMNR de systemd-resolvd.

iptable -A POSTROUTING -s 10.0.0.0/24 -o enp3s0 -j SNAT --to-source 192.168.1.105

TODO : à terminer

  • http://lists.freedesktop.org/archives/systemd-devel/2015-March/028942.html
  • https://nmap.org/nsedoc/scripts/llmnr-resolve.html
  • sudo dnsmasq -d --interface=br0 --dhcp-range=192.168.122.2,192.168.122.254

Containers d'application

J'ai pu transformer un nspawn en un conteneur d'application, c'est à dire qui ne lance que cetraines applications bien précise et leurs dépendances. Il suffit que systemd soit installé dans le nspawn. La solution que j'ai utilisé (il en existe sans doute d'autre), consiste a créer une systemd.target ne contenant que les applications que je souhaite lancer.

Par exemple pour créé un conteneur ne lançant qu'apache j'ai créé le fichier « nspawn/etc/systemd/system/personal.target » :

[Unit]
Description=Personal default target
Wants=dbus.service httpd.service
AllowIsolate=yes
  • Wants : permet de lister les services qui vont être lancés. Leurs dépendances seront lancé automatiquement par systemd (si leurs unitfile sont fait correctement). Si un des service échoue, systemd passe au suivant. Les services ne sont pas forcément lancé dans l'order listé.
  • AllowIsolate : rend une target « isolable ». Permet de lancer systemctl isolate personal.target, cette commande à pour effet d'arréter tous les services/targets/… sauf la target mentionnée ainsi que ses dépences.

Dans le nspawn j'ai indiqué que cette target est la target lancé au démarrage.

root@nspawn # systemctl set-default personal.target

Après ça il suffit de rebooter le nspawn ou isoler la target :

root@nspawn # systemctl isolate personal.target

dbus

Tant que kdbus n'a pas été mergé dans le noyau (prévu pour la version 4.3) je vous conseille de toujours démarrer dbus dans vos nspawn. systemd utilise dbus en interne. Grace au service dbus qui tourne dans le nspawn vous pouvez par exemple le controler (systemctl) de l'exterieur.

tty

Pour lancer un « prompt » dans notre conteneur d'application il faut lancer la target « getty.target » depuis l'exterieur (d'ou l'interet d'avoir lancé dbus dans le nspawn).

# systemctl -M ma_machine start getty.target

target par défaut

Si vous ne voulez pas construire votre target « from scratch » mais voulez utiliser les targets du système voici un descriptif détaillé du démarrage d'un système par systemd et notamment des différentes targets. Voire aussi systemd.special.

Création d'environnement

Je liste dans les sections suivantes les méthodes que j'ai utilisées pour créer des environnements basiques correspondant à différentes distributions. La section Virtualbox j'indique une méthode générique permettant de transformer n'importe quelle machine virtuelle VirtualBox en nspawn.

Archlinux

Source : wiki Archlinux

Archlinux 64bits

Mon système hôte est une archlinux 64bits, pacstrap installe donc par défaut un environnement 64bits.

# CHROOTDIR="/opt/arch64"
# mkdir -p $CHROOTDIR
# pacstrap -c -i -d $CHROOTDIR base
  • -c utilise les paquet du système local s'il le peux, l'installation est donc généralement très rapide.
  • -i laisse à l'utilisateur le choix d'installer ou non tous les paquets du/des groups séléctionnés (ici base) ainsi que des paquets par défauts.

Lors de la selection des paquet il faut déselectionner (avec ^) le paquet linux (le noyau). Il ne sert à rien dans un conteneur car le noyau utiliser est celui du système hôte.

Archlinux 32bits

Pour faire en sorte que pacstrap installe les paquets d'une architecture différente de l'architecture du système hôte il faut lui spécifier un fichier pacman.conf différent.

# sed '/Architecture/ s#auto#i686#' /etc/pacman.conf > /tmp/pacman.conf
# CHROOTDIR="/opt/arch32"
# mkdir -p $CHROOTDIR
# pacstrap -C /tmp/pacman.conf -i -d -G $CHROOTDIR base
  • -C utilise un fichier pacman.conf différent de celui par défaut
  • -G ne pas copier le keyring du host dans la destination (TODO : je ne sais pas si cette option est pertinante)

Il est aussi possible d'utiliser la commande linux32 ou setarch

# CHROOTDIR="/opt/arch32"
# mkdir -p $CHROOTDIR
# linux32
# pacstrap -i -d -G $CHROOTDIR base
# exit

N'oublier pas de démarrer le nspawn avec l'option --personality=x86.

bind home

Pour créer un nspawn qui permette de travaillé sur dans le home de mon utilisateur standard « max » j'ai effectué les opérations suivante.

Dans le nspawn, création de l'utilisateur max avec les même uid et gid qui sur mon installation.

# systemd-nspawn -D nspawn
# useradd -m -g users -u 1000 max
# passwd -d max

Par défaut systemd essaye de démarrer la target « graphical » ce qui n'a pas de sens ici. Je réassigne donc la target par défaut à « multi-user ».

# systemctl set-default multi-user.target

Pour pouvoir ouvrir plusieurs sessions (avec machinectl login …) :

# echo "pts/0" >> /etc/securetty

J'ai eu un problème d'encodage :

# systemd-nspawn -u max --bind /home/max -D nspawn
% ls ~

T??l??chargements

Pour le résoudre j'ai copié les fichiers de « locale » utilisés dans mon installation principal.

# cp /etc/locale.gen nspawn/etc/locale.gen
# cp /etc/locale.conf nspawn/etc/locale.conf
# systemd-nspawn -D nspawn
# locale-gen

CentOS

Sources :

Je n'ai pas envie d'installer yum dans mon Archlinux donc j'ai créé une VM CentOS (e.g. avec VirtualBox) pour bénéficier des outils de la distribution et créer facilement l'arborescence du système guest. J'ai ensuite transféré cet arborescence sur mon Archlinux pour en faire un nspawn. Une fois le premier nspawn créé la VM peut-être détruite.

Si, par la suite, on à besoin de créer un autre nspawn pour une autre version de CentOS ou une autre distribution utilisant le gestionnaire de paquets yum on peut se service du nspawn déjà créé (plus besoin de VM).

Création du répertoire ou sera installé le nspawn et initialisation de la base de donnée utilisé par yum.

# CHROOTDIR="/opt/chroot"
# mkdir $CHROOTDIR
# rpm --root=$CHROOTDIR --initdb

Téléchargement et installation de la version actuelle de centos-release via yumdownloader (fait partie du paquet « yum-utils ») :

# yumdownloader centos-release
# rpm --root=$CHROOTDIR -ivh --nodeps centos-release*rpm

Ou téléchargement et installation d'une autre version (eg 6) de centos-release via yumdownloader :

# yumdownloader --releasever=6  centos-release
# rpm --root=$CHROOTDIR -ivh --nodeps centos-release*rpm

Installation du système de base

# cp $CHROOTDIR/etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-* /etc/pki/rpm-gpg/
# yum --installroot=$CHROOTDIR -y install yum
# tar cvf chroot.tar $CHROOTDIR

32 bits ↔ 64 bits

Si vous voulez installer un système correspondant à une architecture différente de l'architecture actuelle, commencez la procédure (toute première commande) par setarch. Par exemple, si on veut créer un nspawn 32 bits sur une installation 64 bits.

# setarch i686

Vous devrez cependant télécharger le rpm « centos-release » à la main sur le dépôt officiel (ou le dépôt des release depréciées) ; ou vider et re-synchroniser le cache yum pour qu'il utilise les paquets 32 bits (n'oubliez pas de refaire la manip après avoir quitté le setarch).

# yum clean all
# yum makecache

Si setarch n'est pas disponible et que le nspawn (2) est construit en utilisant un autre nspawn (1), on peut lancer le nspawn (1) avec l'option --personality=x86.

Si vous aller utilisez un nspawn 32 bits sur une architecture 64 bits (l'inverse n'est pas possible), pour que yum continu à utiliser (lors de l'utilisation du nspawn) des paquets 32 bits et non 64 bits vous devez lancer le nspawn avec l'option --personality=x86 ou créer le fichier /etc/yum/vars/basearch.

# echo i386 > $CHROOTDIR/etc/yum/vars/basearch

Packet introuvable

Il m'ai déjà arrivé de ne pas trouver un paquet qui était pourtant dans les dépôts… j'ai du faire une mauvaise manipulation à un moment. J'ai peu être utilisé yum après avoir démarré le nspawn sans l'option --personality=x86

Bref pour réparer le système j'ai du lancer ces deux commandes :

# yum clean all
# yum makecache

La permière commande vide le cache que maintient yum sur les dépôts qui sont activé dans la conf. La deuxième force le téléchargement de toutes les infos sur les dépôts.

releasever & basearch

Pour connaitre le « releasever » utilisé par yum.

# distro=`sed -n 's/^distroverpkg=//p' /etc/yum.conf`
# rpm -q --qf "%{VERSION}\n" $distro

(pour avoir la liste des tags possibles rpm --querytags)

Ou utilisez yum-debug-dump (donne beaucoup d'autres informations) disponible dans le paquet « yum-utils ».

Ou utiliser une commande python.

# python -c 'import yum, pprint; yb = yum.YumBase(); pprint.pprint(yb.conf.yumvar, width=1)'

Vous pouvez overwriter ces variables en créant des fichiers dans /etc/yum/vars.

groupinstall

Plutôt que d'installer uniquement yum il est possible (sur les installation récentes) de faire une installation du système de base « complet » (comme une installation via le CD officiel).

Vérifier le nom et la présence du groupe Base et installer le :

# yum grouplist
# yum --installroot=$CHROOTDIR -y groupinstall Base

Sur CentOS 4 le groupe Base n'existe pas. J'ai aussi essayé sous CentOS 5 et le groupe apporte énormément de paquet inutile pour un nspawn (mais qui peuvent l'être pour un conteneur). Je préfère rester sur l'installation de yum uniquement.

Certain groups ne sont pas affiché par défaut, utilisez la commande yum grouplist hidden pour afficher tous les groups. Vous pouvez aussi ajouter ids à la fin de la commande pour avoir l'id de chaque groupe.

Le group permettant de créer un système de compilation minimal de paquet est « buildsys-build » (voir les chroot_setup_cmd des fichiers de configuration mock). Attention, il ne fait pas partie des dépots officiels mais des dépôts EPEL.

Anciennes releases

Les anciennes versions de CentOS sont disponible sur le site vault.centos.org.

Vous devrez télécharger le paquets centos-release de ce dépôts. Par exemple pour CentOS 4.9 :

# curl -O "http://vault.centos.org/4.9/updates/x86_64/RPMS/centos-release-4-9.1.x86_64.rpm"

Il faudra aussi adapter le fichier $CHROOTDIR/etc/yum.repos.d/CentOS-Base.repo pour utiliser les dépots vault.centos.org, sur une CentOS 4 j'ai adapté le fichier comme suit :

--- a/CentOS-Base.repo  2014-12-04 11:46:10.222679891 +0100
+++ b/CentOS-Base.repo  2014-12-04 11:46:15.662628514 +0100
@@ -13,8 +13,9 @@

 [base]
 name=CentOS-$releasever - Base
-mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
+#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
 #baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
+baseurl=http://vault.centos.org/4.9/os/$basearch/
 gpgcheck=1
 gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
 priority=1
@@ -23,8 +24,9 @@
 #released updates 
 [update]
 name=CentOS-$releasever - Updates
-mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
+#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
 #baseurl=http://mirror.centos.org/centos/$releasever/updates/$basearch/
+baseurl=http://vault.centos.org/4.9/updates/$basearch/
 gpgcheck=1
 gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
 priority=1
@@ -33,8 +35,9 @@

Erreur rpmdb

En cas d'erreur (cf. ci-dessous) au lancement de yum dans le nspawn :

# yum update

rpmdb: Program version 4.2 doesn't match environment version

TypeError: rpmdb open failed

# rm -f /var/lib/rpm/__db*
# rpmdb --rebuilddb
# yum clean all
# yum update

Erreur « cpio: Bad magic »

Lors de la création d'un nspawn CentOS 6 depuis une CentOS 5 j'ai eu l'erreur suivante :

# rpm --root=$CHROOTDIR -ivh --nomd5 --nodeps /tmp/centos-release*rpm

warning: /tmp/centos-release-7-0.1406.el7.centos.2.3.x86_64.rpm: Header V3 RSA/SHA256 signature: NOKEY, key ID f4a80eb5
error: failed to open /etc/mtab: No such file or directory
Preparing...                ########################################### [100%]
   1:centos-release         ########################################### [100%]
error: unpacking of archive failed: cpio: Bad magic

Il s'agit d'un problème connue dû à un changement de méthode de compression (gzip → xz), d'un changement de méthode de « résumé » (md5 → sha-256). Il est possible de modifier l'installation CentOS 5 pour pouvoir gérer les paquet prévue pour la 6. Mais je trouve que la procédure est compliqué. Il est plus simple de faire une nouvelle installation CentOS 6 dans une machine virtuelle et de créé un nspawn depuis cet environnement.

Erreur « ping: Operation not permitted »

Sous CentOS 7, par défaut il n'est pas possible d'utiliser ping. C'est un problème connu (et bien expliqué) qui devrait être corrigé dans les prochaines version. On peut le contourner en ajoutant la « capability » « CAP_NET_ADMIN » au nspawn.

# systemd-nspawn --capability=CAP_NET_ADMIN -D centos7

Ou fixer les bonnes « capability » sur le binaire.

# setcap cap_net_admin,cap_net_raw+p centos7/usr/bin/ping

Erreur « Failed to get machine PTY… »

Lorsqu'on essai de se connecter à un nspawn CentOS 7, on peut obtenir l'erreur suivante

# machinectl login centos7
Failed to get machine PTY: Unit container-getty@0.service failed to load: No such file or directory.

Cela est dû à une version trop ancienne de systemd dans le nspawn.

On peut remédier au problème en copiant le fichier container-getty@.service depuis l'environnement hôte dans le nspawn :

# cp /usr/lib/systemd/system/container-getty@.service …nspawn/usr/lib/systemd/system/

Transfert depuis une machine virtuelle

Pour transférer l'archive du chroot de la machine virtuel (utilisant une adresse IP NATée) vers ma machine physique sans pour autant installer les « guest addons » j'utilise woof et curl.

Depuis ma machine physique :

$ woof -U

Sur la machine virtuelle :

$ curl -F upfile=@chroot.tar http://10…:8080/

RedHat

Sources : Upgrading the System Off-line with ISO and Yum

Les paquets sont récupéré de l'ISO du CD d'installation. En mode online, il suffit de prendre exemple sur CentOS (« centos-release » → « redhat-release »).

Dans l'exemple suivant j'utilise un nspawn CentOS 6 pour construire un nspawn RedHat 6.

root@host # mkdir /tmp/iso
root@host # mount -o loop /path/rhel-server…iso /tmp/iso
root@host # systemd-nspawn -x --bind-ro=/tmp/iso -D centos6-64

root@nspawn # CHROOTDIR="/opt/chroot"
root@nspawn # mkdir $CHROOTDIR
root@nspawn # rpm --root=$CHROOTDIR --initdb
root@nspawn # rpm --root=$CHROOTDIR -ivh --nodeps /tmp/iso/Packages/redhat-release*rpm
root@nspawn # cp /tmp/iso/media.repo $CHROOTDIR/etc/yum.repos.d
root@nspawn # echo 'baseurl=file:///tmp/iso/' >> $CHROOTDIR/etc/yum.repos.d/media.repo
root@nspawn # yum --installroot=$CHROOTDIR grouplist

Base

root@nspawn # yum --installroot=$CHROOTDIR -y groupinfo Base

Description: The basic installation of Red Hat Enterprise Linux.

root@nspawn # yum --installroot=$CHROOTDIR -y groupinstall Base
root@nspawn # tar cvf chroot.tar $CHROOTDIR

J'ai fait une installation du système de base complet. Une installation où seul yum est installée est vraiment limité en mode offline.

Il est possible d'utiliser une machine virtuelle RedHat pour construire le nspawn. La procédure est exactement la même.

Pour pouvoir installer d'autre paquets depuis l'iso sur le nspawn RedHat 6 nouvellement créé il suffit de monter l'iso et de rendre accessible le répertoire de montage dans /tmp/iso dans le nspawn.

root@host # mkdir /tmp/iso
root@host # mount -o loop /path/rhel-server…iso /tmp/iso
root@host # systemd-nspawn -x --bind-ro=/tmp/iso -D rhel6-64

root@nspawn # yum install mon_paquet

Fedora

Je suis parti d'une Fedora 22 pour construire une Fedora 22 minimal, Fedora n'utilise plus yum mais dnf.

# dnf --releasever=22 --installroot=$CHROOTDIR --assumeyes install dnf

Pour avoir un système bootable, il vaut mieux utiliser installer un group. Vous pourrez obtenir la list des groups via la commande dnf group list.

# dnf --releasever=22 --installroot=$CHROOTDIR --assumeyes group install 'Installation minimale'

OpenSuse

Sources :

Je n'ai pas envie d'installer zypper dans mon Archlinux donc j'ai créé une VM OpenSuse (e.g. avec VirtualBox) pour bénéficier des outils de la distribution et créer facilement l'arborescence du système guest. J'ai ensuite transféré cet arborescence sur mon Archlinux pour en faire un nspawn. Une fois le premier nspawn créé la VM peut-être détruite.

Si, par la suite, on à besoin de créer un autre nspawn pour une autre version d'OpenSuse on peut se service du nspawn déjà créé (plus besoin de VM).

Toutes manipulations suivant sont faite dans la VM OpenSuse.

# chrootdir=/data/opensuse
# mkdir $chrootdir

zypper (le gestionnaire de paquet) a besoin de /dev

# mkdir $chrootdir/dev
# mount -o bind /dev $chrootdir/dev

On détermine l'url du dépo repo-oss (le dépôt de base sur OpenSuse). La liste des dépôts officiel se trouve sur le site OpenSuse.

# cat /etc/zypp/repos.d/repo-oss.repo

# zypper --root $chrootdir ar http://download.opensuse.org/distribution/11.4/repo/oss/ repo-oss

Installation du système de base et démontage de /dev :

# zypper --root $chrootdir install rpm zypper timezone
# umount $chrootdir/dev

J'ai installé timezone car sans ce paquet, lors du lancement du nspawn OpenSuse, systemd indique qu'il ne peut créé le lien /etc/localtime.

On peut aussi installer des groups de paquets plutot qu'uniquement rpm, zypper et timezone. Pour obtenir la liste des groups :

# zypper search -t pattern

Pour installer un système minimaliste :

# zypper --root $chrootdir install -t pattern Minimal

TODO voir si pour OpenSuse il faut aussi créer le liens baseproduct.

SUSE Linux Enterprise

Le principe est quasiment le même que sous OpenSuse excepté que j'utilise le dvd comme dépot.

depuis une machine virtuelle

L'exemple ci-dessous correspond à la création d'un nspawn SLES-11-SP3 depuis une machine virtuelle SLES-11-SP3.

# chrootdir=/data/sles
# mkdir -p $chrootdir/dev
# mount -o bind /dev $chrootdir/dev/
# zypper --root $chrootdir ar -t yast2 'cd:///?devices=/dev/sr0' SUSE-Linux-Enterprise-Server-11-SP3
# zypper --root $chrootdir refresh
# zypper --root $chrootdir search -t pattern
S | Name               | Summary                                | Type
--+--------------------+----------------------------------------+--------

  | Minimal            | Minimal System (Appliances)            | pattern

# zypper --root $chrootdir install -t pattern Minimal
# umount $chrootdir/dev
# ln -s /etc/products.d/SLES.prod $chrootdir/etc/products.d/baseproduct

J'ai trouvé l'url correspondant à l'utilisation du DVD dans la documentation du Libzypp.

La création du lien /etc/products.d/baseproduct est util à zypper pour déterminer sur quel type d'installation il s'éxecute. Ce lien est mentionné entre autre dans :

depuis un nspawn

Dans un nspawn, pour construire un SLES-11-SP4 à partir d'un nspawn SLES-11-SP3

root@host # mkdir /tmp/iso
root@host # mount -o loop SLES-11-SP4-DVD-x86_64-GM-DVD1.iso /tmp/iso
root@host # systemd-nspawn --bind-ro=/tmp/iso -x -D sles11-build

root@nspawn # chrootdir=/chrootdir
root@nspawn # mkdir -p $chrootdir/dev
root@nspawn # mount -o bind /dev $chrootdir/dev/
root@nspawn # zypper --root $chrootdir ar -t yast2 'dir:/tmp/iso' SUSE-Linux-Enterprise-Server-11-SP4
root@nspawn # zypper --root $chrootdir refresh
root@nspawn # zypper --root $chrootdir search -t pattern
S | Name               | Summary                                | Type
--+--------------------+----------------------------------------+--------

  | Minimal            | Minimal System (Appliances)            | pattern

root@nspawn # zypper --root $chrootdir install -t pattern Minimal
root@nspawn # ln -s /etc/products.d/SLES.prod $chrootdir/etc/products.d/baseproduct

J'ai du faire un bind de /dev car certains paquets on besoins de /dev/null. Voir la section « depuis une machine virtuelle » pour des explications sur la création du lien /etc/products.d/baseproduct.

J'ai eu des problèmes de mknod avec les paquets udev et mdadm. Je les ai ignorés pour les réinstaller (uniquement ces paquets) une fois le nspawn relancé avec --share-system. Lors de la création d'un nspawn SLES-12 je n'ai pas eu ces problèmes.

root@nspawn # zypper --root $chrootdir install -t pattern Minimal
Retrieving package udev-147-0.105.4.x86_64 (215/281), 235.0 KiB (938.0 KiB unpacked)

error: unpacking of archive failed on file /lib/udev/devices/console;55ba302f: cpio: mknod failed - Operation not permitted
Abort, retry, ignore? [a/r/i] (a): i

root@nspawn # exit

root@host # systemd-nspawn --bind-ro=/tmp/iso --share-system -D sles11-build

root@nspawn # chrootdir=/chrootdir
root@nspawn # zypper --root $chrootdir install udev mdadm

Mageia

Sources :

Je n'ai pas envie d'installer urpmi dans mon Archlinux donc j'ai créé une VM Mageia (e.g. avec VirtualBox) pour bénéficier des outils de la distribution et créer facilement l'arborescence du système guest. J'ai ensuite transféré cet arborescence sur mon Archlinux pour en faire un nspawn. Une fois le premier nspawn créé la VM peut-être détruite.

Pour créer un nspawn de la version courante de mageia

# CHROOTDIR=/opt/chroot
# mkdir $CHROOTDIR
# urpmi --auto --root $CHROOTDIR basesystem-minimal urpmi

Pour créer un nspawn d'une version spécifique de mageia

# CHROOTDIR=/opt/chroot
# urpmi.addmedia --distrib --urpmi-root $CHROOTDIR http://distrib-coffee.ipsl.jussieu.fr/pub/linux/Mageia/distrib/4/x86_64/
# urpmi --auto --urpmi-root $CHROOTDIR basesystem-minimal urpmi

L'option --auto permet d'éviter que urpmi soit lancé en mode intéractif et propose des choix à l'utilisateur.

TODO : voir l'option --use-distrib de urpmi.

Debian

Sources :

J'utilise debootstrap qui est disponible dans les dépôts de la plupart des distribution (il est dans le dépôt AUR d'Archlinux).

# CHROOTDIR=/opt/chroot
# mkdir $CHROOTDIR
# debootstrap jessie $CHROOTDIR http://ftp2.fr.debian.org/debian/

À la place de « jessie » on peut mettre n'importe quelle nom de code de release debian (sid, wheezy…), ou un nom symbolique (eg. unstable, testing, stable, oldstable).

Si on veut une architecture différente de celle du système hôte il faut utiliser l'option --arch=ARCH de debootstrap. La liste des architectures supportées est affiché à coté de chaque mirroir sur la page suivante.

On peut aussi ajouter l'option --variant=minbase qui permet de n'installer que les paquets essentiel et apt. D'autre variant sont possible et sont détaillé dans le manuel de debootstrap.

« WARNING: The following packages cannot be authenticated! »

Pour résoudre ce problème vous devez (re)installer dans le paquet « debian-archive-keyring » puis faire un update.

# apt-get install --reinstall debian-archive-keyring
# apt-get update

Ubuntu

De la même façon que pour Debian, j'utilise debootstrap. Les mêmes options sont applicables.

# mkdir /opt/ubuntu-hardy
# debootstrap hardy /opt/ubuntu-hardy http://archive.ubuntu.com/ubuntu/

Les anciennes version de Ubuntu sont disponible sur le site old-releases.ubuntu.com.

Le moyen le plus simple de trouver un dépôts français pour une version spécifique d'Ubuntu est d'aller sur la page correspondante du wiki ubuntu-fr.

Les noms des différentes versions d'Ubuntu sont disponible sur le wiki.

VirtualBox (générique)

Source : page man de vdfuse

Il est possible de copier une image VirtualBox pour en faire un nspawn. Cela à l'avantage d'offrir un nspawn strictement identique à une installation complète de votre distribution et parfois d'être plus rapide que les méthodes citées précédemment (utiliser debootstrap peut d'avérer très long).

Installer la distribution dans une machine virtuel VirtualBox en utilisant le type de fichier de disque dure « VDI (Image Disque VirtualBox) ».

Monter ensuite le disque VDI dans un dossier via l'utilitaire vdfuse (disponible dans le dépôt AUR sur Archlinux).

# mkdir /tmp/vdimount
# vdfuse -r -g -f VirtualBox/….vdi /tmp/vdimount
  • -r monte l'image en « read only »
  • -g garde le processus au premier plan (permet de le stopper facilement via « Ctrl+C »)

Copier et adapter les fichiers (par exemple modifier /etc/fstab) pour créer le nspawn.

D'autres méthodes permettent de monter des images de machine virtuel. Pour l'instant je n'ai testé que celle utilisant vdfuse car c'est celle qui m'a parue la plus simple et rapide.

System V

Il est possible de lancer un système sous System V comme un conteneur systemd-nspawn.

Sources : systemd-nspawn old guests openvz : Physical to container

Debian Wheezy

Je créé d'abord le dossier (ici j'utilise un subvolume btrfs) dans lequel je vais mettre le système et je le peuple avec le système de base de debian wheezy :

max@max-laptop % btrfs subvolume create debian-wheezy
max@max-laptop % sudo debootstrap wheezy debian-wheezy http://ftp2.fr.debian.org/debian/

Je modifie le fichier inittab pour utiliser /dev/console à la place de /dev/tty*.

max@max-laptop % cp debian-wheezy/etc/inittab /tmp/inittab.save
max@max-laptop % sudo vim debian-wheezy/etc/inittab
max@max-laptop % diff /tmp/inittab.save debian-wheezy/etc/inittab
54,59c54
< 1:2345:respawn:/sbin/getty 38400 tty1
< 2:23:respawn:/sbin/getty 38400 tty2
< 3:23:respawn:/sbin/getty 38400 tty3
< 4:23:respawn:/sbin/getty 38400 tty4
< 5:23:respawn:/sbin/getty 38400 tty5
< 6:23:respawn:/sbin/getty 38400 tty6
---
> 1:2345:respawn:/sbin/getty 38400 console

Je donne un mot de passe à l'utilisateur root.

max@max-laptop % sudo systemd-nspawn -D debian-wheezy
Spawning container debian-wheezy on /home/max/nspawn/debian-wheezy.
Press ^] three times within 1s to kill container.
root@debian-wheezy:~# passwd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@debian-wheezy:~# logout
Container debian-wheezy exited successfully.

Je désactive le système de montage, de udev, de hostname, de réseau… au démarrage et à l'extinction. Ces opérations sont géré par systemd-nspawn.

Je ne désactive pas rsyslog car apparemment les logs du conteneur n'apparaisse pas dans le journald du système hôte (comme c'est le cas avec des conteneur sous systemd).

Je créé aussi le dossier /run/lock car je suis tombé sur le problème apache2mktemp: failed to create directory via template …: No such file or directory en installant apache dans le conteneur.

max@max-laptop % echo | sudo tee debian-wheezy/etc/fstab

max@max-laptop % ls debian-wheezy/etc/rcS.d/
README           S01mountkernfs.sh@  S03mountdevsubfs.sh@  S05checkroot.sh@            S06kmod@     S07checkfs.sh@   S09mountall-bootclean.sh@  S10udev-mtab@  S11networking@   S13mountnfs-bootclean.sh@
S01hostname.sh@  S02udev@            S04hwclock.sh@        S06checkroot-bootclean.sh@  S06mtab.sh@  S08mountall.sh@  S10procps@                 S10urandom@    S12mountnfs.sh@  S14bootmisc.sh@
max@max-laptop % ls debian-wheezy/etc/rc0.d/
K01sendsigs@  K01urandom@  K02rsyslog@  K03hwclock.sh@  K03umountnfs.sh@  K04networking@  K05umountfs@  K06umountroot@  K07halt@  README
max@max-laptop % sudo rm debian-wheezy/etc/rcS.d/*
max@max-laptop % sudo rm debian-wheezy/etc/rc0.d/K01urandom
max@max-laptop % sudo rm debian-wheezy/etc/rc0.d/K0[3456]*
max@max-laptop % ls debian-wheezy/etc/rc0.d/
K01sendsigs@  K02rsyslog@  K07halt@  README
max@max-laptop % sudo mkdir debian-wheezy/run/lock

Le conteneur est fonctionnel.

max@max-laptop % sudo systemd-nspawn -x -b -D debian-wheezy
Spawning container debian-wheezy-ed51e6229a25fec0 on /home/max/nspawn/.#machine.debian-wheezy5cc1c0766da104f4.
Press ^] three times within 1s to kill container.
INIT: version 2.88 booting
Using makefile-style concurrent boot in runlevel S.
INIT: Entering runlevel: 2
Using makefile-style concurrent boot in runlevel 2.
dmesg: klogctl failed: Operation not permitted
Starting enhanced syslogd: rsyslogd.
Starting periodic command scheduler: cron.

Debian GNU/Linux 7 debian-wheezy-ed51e6229a25fec0 console

debian-wheezy-ed51e6229a25fec0 login: root
Linux debian-wheezy-ed51e6229a25fec0 4.4.1-2-ARCH #1 SMP PREEMPT Wed Feb 3 13:12:33 UTC 2016 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@debian-wheezy-ed51e6229a25fec0:~# poweroff

The system is going down for system halt NOW!t-ed51e6229a25fec0 (console) (Su
INIT: Switching to runlevel: 0
INIT: Sending processes the TERM signal0:~#
Using makefile-style concurrent boot in runlevel 0.
Asking all remaining processes to terminate...done.
All processes ended within 1 seconds...done.
Stopping enhanced syslogd: rsyslogd.
Will now halt.
Container debian-wheezy-ed51e6229a25fec0 has been shut down.

Docker

Il est possible d'importer une image docker. Pour être plus précis « un container » et pas « une image ».

max@max-laptop % docker images
… IMAGE ID …
… 123      …
max@max-laptop % docker run 123 /bin/bash
max@max-laptop % docker ps -a
CONTAINER ID  IMAGE ID …          CREATED …
         456       123     5 secondes ago …
max@max-laptop % docker export 456 | gzip > dkr.tar.gz
max@max-laptop % sudo machinectl import-tar dkr.tar.gz dkr

Pour lancer le nspawn avec le même environement et le même binaire que docker :

max@max-laptop % docker inspect 123
[
    {

        "Config": {

            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "SSODOMAIN=example.com"
            ],

            "Entrypoint": [
                "/usr/sbin/apache2ctl",
                "-D",
                "FOREGROUND"
            ],

        },
        "Architecture": "amd64",

    }
]
max@max-laptop % sudo systemd-nspawn --setenv=PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" --setenv=SSODOMAIN="example.com" --as-pid2 -M lemonldap-ng /usr/sbin/apache2ctl -D FOREGROUND

Problèmes

reboot

Ce bug n'est sans doute plus d'actualité.

Un bug empêche le redémarrage du conteneur après sont arrêt. Pour contourner le problème on peut redémarrer le système hôte ou lancer la commande :

# rmdir /sys/fs/cgroup/systemd/system.slice/machine-arch64.scope/system.slice/systemd-journald.service

modprobe

29/11/2014 : je ne sais pas si c'est un bug

Il n'est pas possible de charger ou décharger un module noyau dans un conteneur. On est obligé de le faire sur le système host pour l'avoir sur le guest.

Mais si on fait un modprobe sur un module déjà chargé sur la machine ou si on essai de charger un module qui est en fait buildin dans le noyau modprobe doit retourner le code d'erreur 0. Dans un conteneur monté sans option spécifique modprobe retourne 1.

En fait il n'a pas accès au fichier de modules et s'arrête là.

Il faut binder le repertoire de modules du système hôte dans le conteneur :

# systemd-nspawn --bind-ro /usr/lib/modules …

Sur archlinux /lib est juste un lien symbolique vers /usr/lib ; il ne sert à rien de faire un bind sur /lib.

J'utilise --bind-ro par ce que c'est plus « safe »… mais on peut aussi utiliser --bind tout court.

mknod

Si vous obtenez une erreur de type Operation not permitted lors d'appels à mknod cela est normal. Vous pouvez autoriser l'utilisation de la commande (les risques sont détaillé dans le man) en lançant le nspawn avec l'option --share-system.

Je n'ai pas réussi à autoriser les mknod via l'option --capability. Je ne pense pas que ce soit possible.

dbus

Lorsqu'on boot un nspawn sans que n'y soit installé ou démarré dbus il est impossible d'y ouvrire une deuxième console virtuel.

# machinectl login nspawn-name
Failed to get container bus: Input/output error

Il suffit d'installer et de lancer dbus dans le nspawn pour résoudre le problème.

Source : « Failed to get container bus » sur le bug tracker debian

/dev/pts/0

S'il vous est impossible de vous logger sur votre nspawn via machinectl et que vous observez dans les log (du nspawn) :

Jan 22 18:02:17 max-laptop login[79]: pam_securetty(login:auth): access denied: tty '/dev/pts/0' is not secure !
Jan 22 18:02:19 max-laptop login[79]: FAILED LOGIN (1) on '/dev/pts/0' FOR 'root', Authentication failure

il s'agit très propablement d'un problème de securetty, lancez la commande suivante (dans le nspawn) pour corriger le problème :

# echo "pts/0" >> /etc/securetty

Source :

Could not resolve

Au démarrage du nspawn il est impossible de résoudre les FQDN. La résolution DNS ne fonctionne pas.

Au démarrage du nspawn, systemd-nspawn essaye de copier le fichier resolv.conf de l'hôte dans le nspawn. Certaine distribution remplace le fichier /etc/resolv.conf par un lien symbolique. Dans ce cas systemd-nspawn essaye de suivre le liens symbolique pour modifier le bon fichier mais cela peut échouer. Dans ce cas la solution la plus simple est de supprimer le fichier /etc/resolv.conf dans le nspawn et de redémarrer le nspawn (pour que systemd-nspawn copie le resolv.conf de l'hôte dans le nspawn).

PolicyKit1

Si vous observez l'erreur suivante, la solution au problème est d'installer polkit.

The name org.freedesktop.PolicyKit1 was not provided by any .service files