Installez un moteur Docker rootless sous un compte ordinaire, qualifiez ses processus, ses UID, son réseau et sa persistance, et connaissez ses limites.
L’issue s’ouvre déjà remplie avec le titre et l’adresse de cette page.
Avec le mode rootless, le démon Docker et les conteneurs tournent sous un
compte ordinaire, sans aucun droit root sur l'hôte. Cette leçon vous apprend à
installer un moteur rootless à côté du moteur habituel, à vérifier qu'il
appartient bien au compte, puis à qualifier ce qu'il change : correspondance des
UID, stockage, réseau, limites de ressources et persistance. Elle dit aussi ce
que ce mode ne sait pas faire, pour décider s'il répond à votre besoin. Les
mesures viennent d'une VM Debian 13 jetable avec Docker Engine 29.9.2 et
RootlessKit 3.3.0 ; le test qui distingue modifier et supprimer vient d'un
second passage sur la même VM et les mêmes versions. Ne rejouez pas
l'installation sur un hôte partagé sans l'avoir comprise.
Chaque contrôle se lit sur l'état réel du système, pas sur la configuration
annoncée.
Vous expliquez ce que le mode rootless change par rapport au groupe docker.
Vous vérifiez les prérequis : plage d'UID subordonnés (et comment en donner
une à un compte qui n'en a pas), uidmap, cgroup v2 et paquet
docker-ce-rootless-extras.
Vous installez un moteur rootless avec dockerd-rootless-setuptool.sh, sans
toucher au moteur rootful.
Vous prouvez que les processus et le stockage appartiennent au compte.
Vous lisez la correspondance des UID et vous en déduisez ses conséquences sur
les fichiers d'un volume.
Vous qualifiez le réseau (ports, adresse source, --network host), les limites
de ressources et le démarrage sans session ouverte.
Vous désinstallez le mode rootless sans rien laisser derrière.
La démonstration utilise deux rôles que le texte nomme toujours. Le
moteur rootful est le moteur Docker habituel, lancé par root. Le compte
c37-dev, créé pour l'occasion, joue le développeur qui veut lancer ses
conteneurs sans droit root. Les commandes d'administration, qui demandent
sudo, sont précédées du commentaire # administrateur. Celles du compte sont
précédées de # c37-dev, en session SSH : ouvrez une vraie connexion SSH sous ce
compte, car un sudo -u ne crée pas la session dont le mode rootless a besoin.
Tous les conteneurs partent de la même image, épinglée à son digest, que vous
définissez dans chaque session qui lance des conteneurs :
Le mode rootless fait tourner le démon, ses conteneurs et son socket sous un
compte ordinaire, dans un user namespace. Un user namespace est un
mécanisme du noyau qui donne à un groupe de processus sa propre vision des UID :
le root de l'intérieur correspond, vu de l'hôte, à un UID sans privilège (la
page Les namespaces Linux
détaille le mécanisme).
La leçon Retirer les accès directs au
moteur
(C36) montre le problème de l'autre côté : un compte du groupe docker parle à
un démon root, donc il est root. Le mode rootless est l'alternative pour un
utilisateur qui doit vraiment lancer ses conteneurs : il ne reçoit pas l'accès à
un démon puissant, il en démarre un à lui, qui n'a de pouvoir que celui du
compte. Podman fonctionne ainsi par défaut, comme le décrit la page Les
concepts de Podman.
Le schéma met face à face les deux moteurs sur le même hôte. Regardez les
propriétaires, les chemins du socket et du stockage, l'espace réseau détaché où
vivent seulement les conteneurs en réseau bridge, et la correspondance des UID
en bas, que la section sur les UID mesure ensuite.
Ce moteur est entièrement séparé du premier : socket, processus, images,
volumes et réseaux ne sont pas partagés. Ce qui s'y exécute ne voit pas les
conteneurs du moteur rootful, et inversement.
Quels prérequis la machine et le compte doivent-ils remplir ?
Trois choses doivent exister avant l'installation : une plage d'UID
subordonnés pour le compte, les deux programmes newuidmap et newgidmap
du paquet uidmap, et des cgroups v2. Un UID subordonné est un UID que
l'administrateur réserve à un compte, dans /etc/subuid (et /etc/subgid pour
les groupes), pour qu'il les utilise dans son user namespace : sans plage, le
compte n'a qu'un seul UID à y mettre, le sien. Docker rootless exige cette
plage : la section de dépannage de sa documentation cite l'erreur No subuid ranges found pour un compte qui n'en a pas. Dans la démonstration ci-dessous,
useradd attribue sa plage au compte de la leçon, et un compte système reçoit
la sienne par usermod. Commencez par relever l'état de la machine : noyau,
cgroups et paquets déjà installés.
cgroup2fs indique des cgroups v2. docker-ce-rootless-extras apporte
dockerd-rootless.sh, dockerd-rootless-setuptool.sh et rootlesskit
(RootlessKit, le programme qui crée l'espace de noms et son réseau) ; la leçon
d'installation l'a déjà épinglé avec docker-ce, à la même version 29.9.2.
dbus-user-session donne au gestionnaire de services du compte son bus de
session. La documentation de Docker le cite dans son dépannage : sans ce bus,
les conteneurs échouent au démarrage.
Sous Debian 13, useradd attribue la plage tout seul à un compte normal (pas à
un compte système créé avec -r), grâce aux réglages SUB_UID_COUNT et
SUB_GID_COUNT de /etc/login.defs, tant que /etc/subuid existe :
Chaque nouveau compte reçoit donc 65 536 UID subordonnés, pris entre
SUB_UID_MIN et SUB_UID_MAX. La documentation officielle demande une plage
d'au moins 65 536 : c'est une recommandation, mais une plage plus courte fait
échouer le téléchargement d'une image dont les fichiers appartiennent à des UID
plus hauts, avec une erreur lchown … invalid argument. Créez le compte de
travail, sans le mettre dans le groupe docker, puis relevez son UID et ses
plages :
Le compte a l'UID 1002 et les UID 231072 à 296607 (65 536 valeurs) lui sont
réservés. La plage de départ, 231072, est propre à cette VM : elle dépend des
comptes déjà créés, ce n'est pas une valeur par défaut. Ces chiffres reviennent
dans la correspondance des UID, plus bas.
Un compte système, lui, n'a pas de plage. Pour en donner une, relevez d'abord
les plages déjà prises dans /etc/subuid : deux comptes dont les plages se
chevauchent se partageraient des UID. Ici, les trois lignes existantes
s'arrêtent à 296607, et la plage 300000-365535 ne chevauche aucune d'elles.
La valeur 300000 est propre à cette VM : calculez la vôtre à partir de votre
fichier.
userdel: c37-svc mail spool (/var/mail/c37-svc) not found
userdel_svc_rc=0
0
Le premier 0 confirme que le compte système n'a aucune ligne dans
/etc/subuid. usermod --add-subuids … --add-subgids … lui en attribue une,
dans les deux fichiers, et userdel -r la retire avec le compte (le dernier 0).
L'avertissement sur la boîte aux lettres est sans conséquence. Le compte de la
leçon, c37-dev, n'a pas besoin de cette étape : useradd lui a déjà donné sa
plage.
Pour ouvrir la session SSH du compte, l'administrateur lui copie ici sa propre clé
autorisée ; adaptez ce geste à votre façon d'accéder à la machine :
Dans la session du compte, trois lectures donnent l'état de départ : qui il est,
le répertoire d'exécution de sa session, et ce que lui répond le moteur rootful.
L'outil d'installation vérifie ensuite ses prérequis avec le sous-programme
check :
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
[ERROR] Missing system requirements. Run the following commands to
[ERROR] install the requirements and run this tool again.
########## BEGIN ##########
sudo sh -eux <<EOF
# Install newuidmap & newgidmap binaries
apt-get install -y uidmap
EOF
########## END ##########
XDG_RUNTIME_DIR=/run/user/1002 est le répertoire que crée une vraie session,
et le socket du moteur rootless y sera placé. Le compte, hors du groupe
docker, se voit refuser le socket du moteur rootful : c'est voulu. Quant à
check, il refuse parce que uidmap manque, et recopie la commande à lancer,
que seul un administrateur peut taper : le compte ne peut pas installer lui-même
uidmap, dont les deux programmes sont setuid root pour écrire la
correspondance des UID. Le sed retire les codes de couleur de la sortie.
Comment installer un moteur rootless à côté du moteur rootful ?
L'installation se fait en deux temps : l'administrateur prépare la machine, puis
le compte lance dockerd-rootless-setuptool.sh install. L'administrateur
installe uidmap, puis pose le lingering du compte, avant l'installation. Le
lingering est un réglage de systemd-logind qui garde le gestionnaire de
services d'un compte en vie même quand personne n'est connecté. Sans lui, ce
gestionnaire s'arrête avec la dernière session du compte et emporte le démon
rootless : le poser d'abord donne un démon stable pendant toute la leçon, avec
les mêmes PID. La section sur la persistance mesure ce qui se passe sans lui.
La sortie d'apt-get est redirigée vers /dev/null pour rester courte, et
DEBIAN_FRONTEND=noninteractive évite toute question. Le s du mode
-rwsr-xr-x signale le bit setuid : ces deux programmes s'exécutent avec les
droits de root, ce qui leur permet d'écrire dans un user namespace des UID que
le compte ne possède pas. Linger=yes confirme le lingering.
Le compte relance alors check, puis install. L'outil ne demande pas
--force ici : il s'interrompt quand le socket du moteur rootful est
accessible en écriture au compte, et le compte, hors groupe docker, n'y a pas
accès. L'option --force existe aussi pour passer outre un échec de
RootlessKit : ne l'ajoutez pas par réflexe, elle masque des erreurs. Les codes
de retour se lisent avec PIPESTATUS[0], qui donne celui de l'outil et non celui
de sed. Les extraits ci-dessous ne gardent que les lignes utiles de
l'installation :
Cinq faits se lisent ici. L'outil a créé une unité systemd utilisateur
(~/.config/systemd/user/docker.service), c'est-à-dire un service géré par le
systemd du compte et non par celui du système. Cette unité lance rootlesskit
(PID 5090), qui lance à son tour le démon. Le pilote réseau est
gvisor-tap-vsock, avec le pilote de ports builtin. L'API écoute sur
/run/user/1002/docker.sock, un chemin dans /run/user/<uid>. Enfin, l'outil a
créé un contexte Docker nommé rootless et l'a activé, ce qui évite
d'exporter DOCKER_HOST à la main pour la ligne de commande docker (l'outil
rappelle que certaines applications demandent cette variable). La dernière ligne
[INFO] rappelle le lingering : il est déjà posé.
L'état après installation se lit en une commande par objet :
Fenêtre de terminal
# c37-dev, en session SSH
ls~/.config/systemd/user/
systemctl--useris-activedocker.service
systemctl--useris-enableddocker.service
dockercontextls
default.target.wants
docker.service
active
enabled
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
Les deux moteurs ont chacun leur contexte : default vise toujours le socket
rootful, rootless (marqué par l'étoile) vise le socket du compte. Le moteur
rootful n'a pas été arrêté ni modifié.
Comment vérifier que le démon et ses processus sont bien ceux du compte ?
La preuve du mode rootless est triple : le démon se déclare rootless, ses
processus appartiennent au compte, et son stockage est distinct. Le démon
déclare son mode dans SecurityOptions, avec l'entrée name=rootless, et il
donne son répertoire de données et son pilote de stockage :
Regardez trois éléments. name=rootless dans SecurityOptions est l'annonce
du démon. DockerRootDir pointe dans le répertoire personnel du compte :
~/.local/share/docker, non /var/lib/docker. Et ps liste deux jeux de
processus : containerd et dockerd de root (le moteur rootful, toujours là), et
rootlesskit, dockerd et containerd du compte c37-dev. Le pilote de
stockage est overlayfs, le snapshotter de containerd, que le démon choisit
sans configuration.
Le rootlesskit de ps porte le PID 5090, le même que dans la sortie de
l'installation, et le démon (5132) et containerd (5154) aussi : le lingering
posé avant l'installation garde le gestionnaire du compte, donc le démon, en vie
entre les sessions.
Le magasin d'images est lui aussi séparé. Le compte part de zéro, télécharge
l'image, puis mesure ce qu'elle occupe chez lui :
Le moteur du compte ne contenait aucune image au départ (0). Après le
téléchargement, 13 Mo occupent ~/.local/share/docker. L'administrateur regarde
maintenant le magasin du moteur rootful :
Si votre moteur système a déjà cette image, comme ici, le magasin la liste avec
le même identifiant, parce que c'est le même digest. Le compte, lui, est parti
de 0 image et a dû la télécharger : chaque moteur garde sa propre copie.
Corollaire pratique : chaque compte rootless retélécharge ses images, et ses
données comptent dans son répertoire personnel.
Comment les UID du conteneur se traduisent-ils sur l'hôte ?
Dans un conteneur rootless, root n'est pas root de l'hôte : c'est le compte
lui-même. La table de correspondance/proc/self/uid_map l'écrit en trois
colonnes : le premier UID dans le conteneur, le premier UID correspondant sur
l'hôte, et la longueur de la plage. Lancez deux conteneurs, l'un en root par
défaut, l'autre avec --user 1000:1000, et regardez-les depuis l'hôte :
La sortie se lit dans l'ordre des commandes. 0 est l'UID du premier conteneur,
suivi de sa table. La première ligne de la table, 0 1002 1, dit que l'UID 0 du
conteneur est l'UID 1002 de l'hôte : le root du conteneur est le compte
c37-dev, et ps le confirme (c37-dev 1002). La seconde, 1 231072 65536,
place les UID 1 à 65536 du conteneur sur les UID subordonnés 231072 à 296607.
Le second conteneur, 1000 à l'intérieur, a donc, vu de l'hôte, l'UID 232071,
soit 231072 + (1000 - 1). C'est le schéma de la section précédente, mesuré.
La conséquence concrète touche les fichiers d'un volume. Un fichier créé par
root dans le conteneur appartient au compte sur l'hôte. Un fichier créé pour
l'UID 1000 appartient à l'UID 232071, qu'aucun compte réel ne porte :
Les deux premiers listings montrent le même volume vu de deux côtés : 0 et
1000 dans le conteneur, 1002 et 232071 sur l'hôte. Le compte modifie sans
difficulté le fichier du conteneur-root (ecriture_hote_rc=0), mais la
commande touch sur le fichier de l'UID 1000 est refusée (touch_1000_rc=1),
parce que l'hôte voit un autre propriétaire. Un bind mount d'un répertoire de
votre dépôt demande donc la même vigilance : un conteneur qui écrit en tant
qu'un UID autre que root dépose des fichiers que le compte ne pourra plus
modifier depuis l'hôte.
Modifier et supprimer ne suivent pas la même règle. Supprimer une entrée
dépend des droits sur le répertoire qui la contient, non sur le fichier
lui-même. Le test suivant crée, depuis un conteneur, un fichier et un répertoire
de l'UID 1000 dans un volume neuf, puis les modifie et les supprime depuis
l'hôte :
drwxr-xr-x 2 232071 232071 4096 Nov 4 20:47 rep-1000
=== nettoyage
c37-m-droits
Dans cette capture, le message de touch s'affiche sous la ligne « supprimer » :
la sortie et les erreurs y ont voyagé par deux canaux distincts. Dans votre
terminal, l'erreur apparaîtra juste après la commande qui l'a produite.
Suivez les codes de retour. Le compte ne modifie
pas le fichier de l'UID 1000 (touch_rc=1), pourtant il le supprime
(rm_fichier_rc=0) : le répertoire parent _data lui appartient, et c'est ce
droit qui compte pour retirer une entrée. Il ne peut pas vider rep-1000
(rm_rep_rc=1, Permission denied) : ce répertoire appartient à l'UID 232071,
le compte n'y a aucun droit d'écriture, donc il ne peut pas en retirer
dedans, et rep-1000 reste visible dans le dernier ls. Pour effacer ce type
de données, passez par le démon tant qu'il tourne (docker volume rm), ou par
rootlesskit après la désinstallation, comme le propose uninstall.
Le mode rootless change trois choses pour le réseau : le service ne reçoit plus
l'adresse source des clients par défaut, un port inférieur à 1024 ne se publie
pas, et --network host expose le service sur le réseau réel de la machine. Un
port élevé, lui, se publie normalement. Le service de l'exemple est un petit
serveur nc qui répond une ligne et affiche l'adresse de ses clients ; HOTE
reçoit la première adresse IPv4 globale de la machine :
connect to [::ffff:172.17.0.2]:8080 from [::ffff:172.17.0.1]:57558 ([::ffff:172.17.0.1]:57558)
La publication sur 127.0.0.1:8088 répond (curl_boucle_rc=0), et l'adresse
de la VM, qui n'est pas dans la publication, ne répond pas (curl_ip_hote_rc=7).
La ligne de journal montre une limite : le service (172.17.0.2) reçoit la
connexion depuis 172.17.0.1, une adresse interne au réseau des conteneurs, et
non depuis 127.0.0.1, l'adresse réelle du client. Avec la configuration par
défaut (pilote de ports builtin), l'adresse source réelle n'est pas
transmise, ce qui gêne un service qui journalise ou filtre par adresse.
La documentation de Docker annonce que RootlessKit 3 transmet l'adresse source
en TCP, à condition de désactiver le userland-proxy. La parade jouée dans
cette leçon est le pilote de ports slirp4netns, présenté plus bas.
Le pont rootless prend 172.17.0.0/16, comme le moteur rootful, sans conflit
d'adresses. L'explication probable : le pont du compte vit dans l'espace de noms
réseau de RootlessKit, distinct de celui de l'hôte où se trouve le pont du
moteur rootful.
Passons au port 80. Un compte ordinaire ne peut pas écouter sur un port
inférieur à 1024, et il en va de même pour RootlessKit. Publier le port 80 échoue
donc :
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint c37-m-web80 (398ba7ab05c922a3581a9647ba99db1e738e3d8473793fe17fedfa83f466b200): error while calling RootlessKit PortManager.AddPort(): cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp4 0.0.0.0:80: bind: permission denied
Run 'docker run --help' for more information
run80_rc=126
Le conteneur a été créé (son identifiant s'affiche) mais n'a pas démarré, d'où
le docker rm -f. Le code de retour est 126, et le message cite sa cause,
cannot expose privileged port 80, puis trois sorties qu'il propose : abaisser
net.ipv4.ip_unprivileged_port_start dans /etc/sysctl.conf (valeur actuelle
1024), donner la capacité CAP_NET_BIND_SERVICE au binaire rootlesskit, ou
choisir un port supérieur ou égal à 1024. La première est un réglage du noyau
valable pour toute la machine. La deuxième est posée sur le fichier : elle vaut
pour tout utilisateur qui lance ce binaire, pas seulement pour votre compte. La
documentation de ce mode les formule ainsi :
sudo setcap cap_net_bind_service=ep $(which rootlesskit) puis un
redémarrage du démon, ou net.ipv4.ip_unprivileged_port_start dans
/etc/sysctl.d puis sudo sysctl --system. Ces deux réglages modifient la
machine entière ou un binaire partagé : ils sortent du périmètre de la
démonstration, qui reste sur un compte isolé. Le plus simple reste de publier un
port élevé, derrière un reverse proxy ou une redirection placés par
l'administrateur si le service doit être joint sur le port 80.
Sur cette VM, la sortie vers l'extérieur fonctionne, ping compris :
Le ping a répondu sur cette Debian 13. La documentation de Docker précise que
son fonctionnement dépend du paramètre noyau net.ipv4.ping_group_range, dont
la valeur varie selon la distribution : sur une autre, vérifiez-le.
L'option --network host demande une attention particulière. La ligne de
commande de RootlessKit vue à l'installation porte --detach-netns : le démon
reste dans l'espace réseau de l'hôte, et seuls les conteneurs en réseau bridge
vivent dans l'espace réseau détaché de RootlessKit (leur adresse IP n'est donc
pas joignable depuis l'hôte). Un conteneur lancé avec --network host voit
ainsi les interfaces réelles de l'hôte, et un service qui y écoute est joignable
depuis l'hôte :
Fenêtre de terminal
# c37-dev, en session SSH
dockerrun--rm--networkhost"$ALP"sh-c'ip -4 -o addr show | awk "{print \$2, \$4}"'
Le conteneur voit lo, enp1s0 (l'interface qui porte l'adresse 10.10.130.11
que HOTE a relevée) et docker0, le pont du moteur rootful : ce sont les
interfaces de l'hôte. Le service répond depuis l'hôte (curl_hote_8080_rc=0),
et le port 8080 écoute sur *, toutes les adresses de la machine. Le dernier
0 montre que le port est libéré une fois le conteneur retiré. C'est une
limite de sécurité : le mode rootless ne cloisonne pas le réseau d'un
conteneur lancé avec --network host, qui expose son service au réseau de la
machine. Réservez-le à un besoin identifié, et préférez la publication de ports
précis.
Le pilote réseau vu à l'installation, gvisor-tap-vsock, n'est pas imposé :
d'après le script dockerd-rootless.sh, il choisit slirp4netns s'il est
installé, sinon pasta, sinon vpnkit, et se rabat sur gvisor-tap-vsock. Ce
pilote est embarqué dans RootlessKit, donc aucun binaire de plus à installer,
mais RootlessKit le qualifie d'expérimental : à ne pas présenter comme un
pilote éprouvé pour une production. La parade ci-dessous change justement de
pilote.
La documentation Docker propose, dans sa section de dépannage « docker run -p
does not propagate source IP addresses », une parade pour garder l'adresse
source : remplacer le pilote de ports par slirp4netns. Cela demande
l'administrateur, qui installe le paquet Debian (le moteur n'embarque pas
slirp4netns), puis le compte :
Le compte redémarre son démon, puis compare les pilotes avant et après avec la
fonction pilotes. Le paquet seul suffit à changer le réseau, mais pas le
pilote de ports :
Le premier appel montre le réseau par défaut, gvisor-tap-vsock builtin.
Après le redémarrage, le réseau est slirp4netns, mais le pilote de ports reste
builtin. Pour changer aussi ce dernier, le compte écrit une surcharge de
l'unité utilisateur, recharge systemd et redémarre le démon, puis rappelle le
service avec le même programme nc. ALP, HOTE et SRV sont ceux définis
plus haut : si vous avez ouvert une nouvelle session, définissez-les à nouveau.
connect to [::ffff:172.17.0.2]:8080 from [::ffff:10.0.2.2]:58892 ([::ffff:10.0.2.2]:58892)
page-c37
curl_ip_hote_rc=0
connect to [::ffff:172.17.0.2]:8080 from [::ffff:10.10.130.11]:35134 ([::ffff:10.10.130.11]:35134)
Après la surcharge, pilotes répond slirp4netns slirp4netns. Les deux
journaux se lisent ainsi. Un appel sur 127.0.0.1 est vu depuis 10.0.2.2, la
passerelle de slirp4netns. Un appel sur l'adresse de la machine est vu depuis
10.10.130.11 : l'adresse du client est conservée. Deux réserves : le client
était l'hôte lui-même, faute d'une autre machine, et la documentation classe ce
pilote de ports comme lent (« Slow »). Choisissez-le donc quand le service a
besoin de l'adresse source, pas par défaut.
Le retour en arrière se fait en trois gestes : le compte retire la surcharge,
l'administrateur purge le paquet, puis le compte redémarre son démon.
Le compte ne peut limiter que les ressources dont systemd lui délègue le
contrôleur de cgroup. Sur Debian 13 (systemd 257), le gestionnaire du compte
reçoit par défaut cpu, memory et pids, pas io ni cpuset. La
documentation officielle du mode rootless dit que seuls memory et pids sont
typiquement délégués : sur cette Debian, cpu l'est aussi, la liste dépend donc
de la machine. Pour déléguer plus
(io, cpuset), la documentation décrit une surcharge de user@.service avec
Delegate=. Pour le vérifier ici, lancez des conteneurs avec ces trois limites
et relisez le cgroup depuis l'intérieur, puis lisez la liste des contrôleurs
délégués :
--memory 64m donne memory.max à 67 108 864 octets, --pids-limit 20 donne
pids.max à 20, et --cpus 0.5 donne cpu.max à 50000 100000, soit la moitié
d'un cœur. La dernière ligne explique pourquoi : seuls cpu, memory et
pids sont délégués au compte. L'absence d'io se voit aussi au démarrage du
démon, qui avertit No io.max (rbps) support (et la même ligne pour wbps,
riops et wiops), dans le journal du démon à l'installation. Aucun contrôleur
io n'étant délégué, les limites d'E/S comme --device-read-bps ne peuvent
pas s'appliquer ; ce cas sort du périmètre de la démonstration. Si votre besoin
en dépend, testez-le sur votre machine ou demandez à l'administrateur de
déléguer ce contrôleur.
Non : en mode rootless, aucun profil AppArmor n'est appliqué, et l'option qui
en demande un est ignorée sans erreur. La documentation Docker liste d'ailleurs
AppArmor parmi les fonctions non prises en charge dans ce mode. Le démon
l'indique dans ses options de sécurité, et le conteneur le confirme dans
/proc/1/attr/current :
SecurityOptions ne contient pas apparmor, AppArmorProfile est vide et le
processus 1 du conteneur est runc (unconfined). Demander explicitement
--security-opt apparmor=docker-default réussit (code 0, mesuré sans tube) et ne
change rien : une option de sécurité est ignorée en silence, ce qui est plus
trompeur qu'une erreur. Le filtre seccomp reste actif
(name=seccomp,profile=builtin). La leçon Confiner par profils : seccomp et
AppArmor
détaille ces deux couches.
Que peut atteindre un conteneur rootless qui s'échappe ?
Un conteneur rootless qui s'échappe par le démon ou le runtime obtient les
pouvoirs du compte, pas ceux de root. La table des UID l'a montré : le root du
conteneur est l'UID 1002, celui du compte, et les autres UID du conteneur
tombent dans la plage subordonnée du compte. Une évasion atteindrait donc ce
que ce compte peut lire ou
écrire : ses clés SSH, son code, ses jetons. Le démon rootless n'est pas un démon
sans pouvoir, c'est un démon qui n'a que ceux de son compte.
D'où le choix de la démonstration : un compte dédié, qui ne contient que son
moteur rootless et ses données, et qui n'a ni les clés ni le code d'un autre
projet. Lancer le mode rootless sous le compte personnel d'un développeur donne
au démon l'accès à tout ce qu'il possède. Ajoutez à cela deux limites
mesurées plus haut : aucun profil AppArmor, et un réseau hôte ouvert dès qu'un
conteneur est lancé avec --network host.
Comment garder le démon rootless actif sans session ouverte ?
Sans lingering, le démon s'arrête quand la dernière session du compte se
ferme : le gestionnaire du compte s'arrête, rootlesskit disparaît et les
commandes lancées ensuite échouent faute de socket. Le lingering, posé par
l'administrateur avec sudo loginctl enable-linger, évite cela : c'est lui qui
permet à une unité utilisateur de démarrer au boot et de survivre à la
déconnexion. La mesure ci-dessous montre rootlesskit relancé (PID 8886) dès
que enable-linger est rétabli.
Les contrôles qui suivent se font sans aucune session ouverte du compte :
fermez votre session SSH de c37-dev avant ce test. Ils sont joués depuis
l'administrateur. La fonction D() lance une commande Docker sous le compte,
avec le socket du démon rootless. Un conteneur y est déclaré avec --restart unless-stopped, puis le lingering est retiré et rétabli. Le premier contrôle
liste les sessions de la machine :
failed to connect to the docker API at unix:///run/user/1002/docker.sock; check if the path is correct and if the daemon is running: dial unix /run/user/1002/docker.sock: connect: no such file or directory
La première ligne de loginctl list-sessions appartient à c37-dev (UID 1002)
et sa classe est manager : ce n'est pas une connexion, puisque vous avez fermé
la vôtre, mais la session que systemd-logind ouvre pour le gestionnaire de
services du compte, que le lingering maintient. Les deux lignes d'ansible, le
compte de l'administrateur, portent une classe manager et une classe user,
sa propre connexion.
La boucle for attend la fin de rootlesskit au lieu d'une durée fixe :
attente=13s dit que l'arrêt a pris 13 secondes (la boucle abandonne à 90 s).
Sans lingering, le compte est dans l'état closing, plus aucun rootlesskit
ne tourne et le socket a disparu : l'API répond no such file or directory,
et le conteneur n'est plus
servi. Avec le lingering, l'état passe à lingering, rootlesskit revient (PID
8886) et le conteneur c37-m-veille, Up 19 seconds, est reparti grâce à sa
politique de redémarrage. Pour qu'un service rootless survive à la déconnexion
de son propriétaire, activez donc le lingering, comme le conseille la dernière
ligne [INFO] de l'installation. Un point voisin : la documentation précise que
lancer Docker rootless comme service système (/etc/systemd/system/docker.service),
même avec User=, n'est pas pris en charge. Gardez l'unité utilisateur créée
par l'outil.
La fonction D() ne donne accès qu'au démon, par DOCKER_HOST. Pour que
l'administrateur pilote le gestionnaire de services du compte avec
systemctl --user, un sudo -u ne suffit pas : il faut en plus que
XDG_RUNTIME_DIR désigne le répertoire d'exécution du compte, et
DBUS_SESSION_BUS_ADDRESS son bus quand le défaut ne convient pas. La
démonstration n'en a pas besoin : elle passe par DOCKER_HOST.
Reste le redémarrage de la machine. Après un reboot, toujours sans que le compte
se connecte, l'administrateur relève :
docker0 DOWN ee:06:6b:a4:eb:6c <NO-CARRIER,BROADCAST,MULTICAST,UP>
La machine a redémarré à 20:25:19, et le démon rootless et son conteneur sont
revenus (le conteneur affiche Up 14 seconds) sans que le compte se connecte :
le lingering maintient le gestionnaire de services du compte. Le pont docker0
du moteur rootful est lui aussi présent sur l'hôte, à l'état DOWN.
Comment désinstaller proprement le mode rootless ?
La désinstallation se fait en trois étapes : le compte arrête et retire son
moteur, il supprime ses données, puis l'administrateur retire le lingering, le
compte et le paquet uidmap. Rouvrez une session SSH sous le compte, qui
supprime ses objets et lance uninstall :
[INFO] Configured CLI to use the "default" context.
[INFO]
[INFO] Make sure to unset or update the environment PATH, DOCKER_HOST, and DOCKER_CONTEXT environment variables if you have added them to `~/.bashrc`.
[INFO] This uninstallation tool does NOT remove Docker binaries and data.
[INFO] To remove data, run: `/usr/bin/rootlesskit rm -rf /home/c37-dev/.local/share/docker`
uninstall_rc=0
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
L'outil arrête et désactive l'unité, supprime le contexte rootless et
réactive default. Il dit aussi ce qu'il ne fait pas : il ne retire ni les
binaires ni les données, et il propose rootlesskit rm -rf pour les données.
Il faut passer par RootlessKit, parce que ces données appartiennent en partie à
des UID subordonnés : le compte ne modifie pas ces fichiers depuis l'hôte, et
il ne peut pas vider un répertoire de l'UID 1000, comme le rm refusé plus haut
le montre. rootlesskit crée un nouvel espace de noms avec la même
correspondance que le démon, où le compte est root : il n'entre pas dans un
espace existant. La suppression s'y exécute donc avec les droits nécessaires :
ls: cannot access '/home/c37-dev/.local/share/docker': No such file or directory
Le dossier de 13 Mo a disparu. Ici, la commande jouée est rootlesskit find … -delete, qui fait le même travail que le rootlesskit rm -rf proposé par
l'outil. Reste le côté administrateur. Fermez d'abord votre session SSH de
c37-dev : l'administrateur retire ensuite le lingering, vérifie qu'aucun
processus ne subsiste, supprime le compte et ses plages d'UID. La première
commande compte les sessions du compte, pour montrer qu'il ne reste que celle du
lingering :
pgrep-uc37-dev-a.||echo"aucun processus de c37-dev"
sudouserdel-rc37-dev; echo"userdel_rc=$?"
grep-c'^c37-dev:'/etc/subuid/etc/subgid
ls-d/home/c37-dev2>&1
1
disable_rc=0
aucun processus de c37-dev
userdel: c37-dev mail spool (/var/mail/c37-dev) not found
userdel_rc=0
/etc/subuid:0
/etc/subgid:0
ls: cannot access '/home/c37-dev': No such file or directory
Le 1 du début est la session manager du lingering, pas une connexion. Après
disable-linger, plus aucun processus ne reste au compte, et userdel -r a
retiré le compte, sa plage dans /etc/subuid et /etc/subgid (:0 partout) et
son répertoire personnel. L'avertissement sur la boîte aux lettres est sans
conséquence. Il reste le paquet uidmap, installé pour la leçon :
uidmap est purgé, et aucun fichier de l'UID 1002 ne traîne dans /tmp (le
dernier 0) : la machine est revenue à son état de départ, sans rien laisser
derrière, hors les paquets Docker installés avant la leçon.
Le lab prépare un moteur rootless pour un compte dédié, par une unité
utilisateur propre plutôt que par l'outil d'installation vu dans cette leçon, et
laisse le moteur rootful du système intact. Il vous demande de qualifier ce
moteur. Les tests mesurent l'état réel : l'unité utilisateur est active, le
processus du démon appartient au compte non privilégié, le démon annonce le mode
rootless, un vrai service HTTP répond sur la boucle locale, un pair du réseau est
autorisé et un pair sans réseau refusé, et une donnée écrite dans un volume
survit à la recréation du conteneur.
Le mode rootless remplace l'accès à un démon root par un démon qui n'a que les
pouvoirs de son compte ordinaire, et ces pouvoirs sont ceux du compte : ses
clés, son code.
Vous distinguez le groupe docker (un accès au démon root) du mode rootless
(un démon et des conteneurs qui appartiennent au compte).
Vous vérifiez les prérequis : 65 536 UID subordonnés dans /etc/subuid et
/etc/subgid (usermod --add-subuids … --add-subgids … pour un compte qui n'en
a pas), le paquet uidmap, des cgroups v2, et docker-ce-rootless-extras à la
version du moteur.
Vous posez le lingering avant l'installation, puis vous installez par
dockerd-rootless-setuptool.sh install sous le compte, en session SSH ;
check dit ce qui manque, et l'outil crée une unité systemd utilisateur, un
contexte rootless et le socket /run/user/<uid>/docker.sock.
Vous prouvez le mode par name=rootless dans SecurityOptions, les processus
ps du compte et DockerRootDir dans son répertoire personnel ; le magasin
d'images est distinct de celui du moteur rootful.
Vous lisez /proc/self/uid_map : le root du conteneur est le compte, les
autres UID tombent dans la plage subordonnée, et les fichiers d'un volume
portent ces UID sur l'hôte : le compte ne les modifie pas, peut supprimer un
fichier dont il possède le répertoire, mais ne vide pas un répertoire de
l'UID 1000, d'où rootlesskit pour effacer les données.
Vous savez que les ports inférieurs à 1024 ne se publient pas, que l'adresse
source des clients n'est pas transmise par défaut (le pilote de ports
slirp4netns la conserve, plus lentement), et que --network host expose
le service sur le réseau réel de la machine.
Vous savez que cpu, memory et pids sont délégués par défaut sur Debian 13,
pas io, et qu'AppArmor n'est pas appliqué en rootless : seccomp reste actif.
Vous activez le lingering pour que le démon survive à la déconnexion et au
redémarrage, et vous supprimez les données par rootlesskit find … -delete.
Vérifiez que l'essentiel est acquis : ce que le mode rootless change, comment
lire la correspondance des UID et quelles contraintes il impose au réseau, aux
ressources et à la persistance.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
6 questions
6 min.
70% requis
Informations
Le chronomètre démarre au clic sur Démarrer
Questions à choix multiples, vrai/faux et réponses courtes
Vous pouvez naviguer entre les questions
Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
⏱️--:--
0 / 6
Vérification
(0/0)
--:--
0%
0Correctes
0À revoir
0:00Temps
-Niveau
Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.