Migrer vers Podman pour exécuter vos conteneurs

L'adoption de Podman comme moteur de conteneur par défaut sous RHEL 8 est un signal fort pour considérer cette solution comme mature et crédible pour enfin se passer de Docker. En effet, Red Hat ne fait pas de choix technologiques à la légère quand il s'agit ensuite d'assurer le support pendant 10 ans de la solution.

Dans cet article, je vais expliquer comment migrer un conteneur, d'une complexité normale, vers Podman et j'explorerai ensuite certain des apports de Podman.

Présentation de Podman

Podman est un moteur de conteneur, conforme aux spécifications de l'Open Container Initiative (OCI). Le projet a initialement émergé au sein du projet CRI-O, sous le nom de kpod.

Podman est spécialisé dans l'exécution des conteneurs, il est développé parallèlement à d'autres projets qui se concentrent sur d'autres étapes du cycle de vie des conteneurs :

  • Buildah : spécialisé dans la construction d'images

  • Skopeo : spécialisé dans la gestion des images et des registry

Podman se distingue de Docker par, entre autres, son fonctionnement sans démon et sa possibilité de fonctionner sans droits root.

Migration d'un conteneur

Podman est compatible, à quelques exceptions près, avec les commandes Docker. Red Hat va jusqu'à proposer une commande docker qui pointe sur le binaire podman. Personnellement, je préfère prendre l'habitude d'appeler directement le bon binaire.

J'ai sur mon infrastructure personnelle une instance de Collabora, qui me permet d'intégrer une possibilité d'éditer des documents Libre Office, de manière collaborative, depuis mon instance Nextcloud. Voilà le docker-compose.yaml que j'utilisais :

version: '3'
services:
  collabora:
    ports:
    - "9980:9980"
    image: collabora/code:latest
    environment:
      domain: nextcloud\.mondomaine\.fr
      username: superuser
      password: securepassword
      allowed_languages: en_US fr_FR
      dictionaries: en_US fr_FR
    cap_add:
      - MKNOD
    restart: always

L'utilisation de docker-compose ici n'apportait rien de particulier d'autre que ce qui pouvait être fait via un simple docker run. On peut donc simplement remplacer cela par un podman run (exécuté en root)

podman run --name collabora --env-file collabora.env --cap-add MKNOD -p 9980:9980 collabora/code:latest

Avec les variables d'environnement externalisées dans un fichier collabora.env :

domain=nextcloud\.mondomaine\.fr
username=superuser
password=securepassword
allowed_languages=en_US fr_FR
dictionaries=en_US fr_FR

On peut vérifier la bonne exécution de notre conteneur avec les commandes podman ps et podman logs.

Maintenant, afin de permettre de gérer ce conteneur comme un service, on crée le service systemd qui va bien. Dans le fichier /etc/systemd/system/collabora.service :

[Unit]
Description=Custom Collabora Podman Container
After=network.target

[Service]
Type=simple
TimeoutStartSec=5m
ExecStartPre=-/usr/bin/podman rm "collabora"

ExecStart=/usr/bin/podman run --name collabora --env-file /etc/systemd/system/collabora.env --cap-add MKNOD -p 9980:9980 collabora/code:latest
ExecReload=-/usr/bin/podman stop "collabora"
ExecReload=-/usr/bin/podman rm "collabora"
ExecStop=-/usr/bin/podman stop "collabora"
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

On peut alors vérifier via systemctl qu'on est capable de contrôler le conteneur comme un service. Il ne reste plus ensuite que faire un systemctl enable pour démarrer automatiquement le conteneur avec le serveur.

A noter qu'il existe une commande podman generate systemd mais le résultat ne me convenait pas car il permet uniquement l'arrêt relance d'un conteneur existant.

Conteneur sans root

Podman permet d'être utilisé et d'exécuter des conteneur avec des utilisateurs non privilégiés. Cela ne fonctionne pas (encore) pour tous les scénarios, notamment il n'est pas possible d'ajouter une Capability Linux à l'exécution (ce qui exclu donc notre conteneur Collabora qui a besoin de MKNOD).

Espaces de nommage utilisateur

Pour fonctionner avec un utilisateur lambda, Podman utilise le concept de subuid qui permet de déléguer la gestion d'une plage d'UID à un utilisateur. Ce mécanisme, fournit par shadow-utils se configure dans le fichier /etc/subuid (et de même pour les GID dans /etc/subgid). Par exemple :

cedric:100000:65536

Cela signifie que l'utilisateur cedric pourra utiliser 65536 UID, à partir de l'UID 100000. Attention à bien spécifier des UID qui ne sont pas utilisés par le système hôte et de ne pas se faire recouvrir les subuid de plusieurs utilisateurs.

Concrètement, cela veut dire que l'UID 1 dans un conteneur exécuté par cet utilisateur correspondra à l'UID 100000 du point de vue de l'hôte, l'UID 2 à l'UID 100001, et ainsi de suite. À noter que l'UID 0 (root) sera lui mappé à l'UID de l'utilisateur.

Stockage

Lorsque Podman fonctionne avec l'utilisateur root, il utilise l'espace de stockage configuré dans /etc/containers/storage.conf. Lorsqu'il est exécuté par un autre utilisateur, il utilise un espace de stockage spécifique situé dans ~/.local/share/containers.

Cela permet d'isoler les espaces de stockage des différents utilisateurs du système avec tous les avantages (sécurité, isolation,...) mais aussi tous les inconvénients (duplication éventuelle des images et autres données) que cela comporte.

OverlayFS fonctionnant en mode Kernel, il n'est évidemment pas possible pour un utilisateur non privilégié d'en tirer parti. Il est donc nécessaire d'utiliser fuse-overlayfs qui est une implémentation FUSE d'OverlayFS. Sans cette bibliothèque, Podman utilise VFS qui n'a aucun mécanisme de déduplication et donc peut être très problématique en terme d'espace dès que l'utilisateur multiplie le nombre de conteneurs utilisés.

Pods

Podman ne s'appellerait pas ainsi sans la gestion de pods. Les pods sont un concept hérité de Kubernetes. Il s'agit d'une abstraction permettant de gérer de manière cohérente un ou plusieurs conteneurs.

La gestion des pods dans Podman est initialement dû à l'objectif initial de ce dernier : Podman a été développé pour faciliter le débogage de conteneurs sur un nœud Kubernetes (lorsque le démon CRI-o n'est pas accessible ou pour permettre une investigation plus fine).

Cela permet également de faciliter la migration de conteneurs vers Kubernetes en utilisant le même type d'objets. Il est d'ailleurs possible d'extraire sous forme de fichier YAML importable par Kubernetes, des conteneurs Podman, grâce à la commande podman generate kube. Le résultat peut alors être réimporté par Podman avec la commande podman play kube (qui officiellement ne supporte que les YAML générés par lui-même, Podman ne supportant pas tous les objets et les paramètres de Kubernetes) ou importé dans Kubernetes avec la commande kubectl create -f.

En dehors de Kubernetes, ce concept de pod permet surtout de pouvoir gérer un groupe de conteneurs de manière cohérente comme on peut le faire avec un docker-compose.

Pour manipuler des pods avec Podman, rien de plus simple. Il suffit d'utiliser podman pod create pour créer un pod et de rajouter l'option --pod à une commande podman run pour créer un conteneur dans un pod.

Conclusion

La facilité de migration (pour les cas les plus simple) de Docker vers Podman est rassurante. À iso-fonctionnalités, Podman à l'avantage d'être pleinement basé sur des standards ouverts (OCI) et d'être un vrai projet Open Source.

Les fonctionnalités apportées par Podman sont très intéressantes. Notamment la possibilité de faire fonctionner des conteneurs sans être root qui sera très appréciée dans le monde de l'entreprise.

Les projets frères que sont Buildah et Skopeo méritent eux aussi d'être explorés pour des besoins avancés (il reste tout à fait possible de faire un podman build, par exemple, pour les besoins standards).