Add content for missing namespaces

This commit is contained in:
nemunaire 2026-01-07 22:18:11 +07:00
commit 822dc619b8
7 changed files with 1650 additions and 6 deletions

305
tutorial/4/cgroupns.md Normal file
View file

@ -0,0 +1,305 @@
Le *namespace* `cgroup` {#cgroupns}
-----------------------
### Introduction
L'espace de noms `cgroup`, introduit dans Linux 4.6, permet de virtualiser la
vue d'un processus sur l'arborescence des *Control Groups*. Au sein d'un
*namespace* `cgroup`, la racine vue par le processus correspond en fait à un
sous-groupe de l'arborescence globale.
### Pourquoi virtualiser les *cgroups* ?
Sans le *namespace* `cgroup`, un processus dans un conteneur pourrait :
- Voir l'arborescence complète des *cgroups* du système hôte
- Obtenir des informations sur d'autres conteneurs
- Comprendre la structure organisationnelle du système
Le *namespace* `cgroup` permet de :
- Masquer l'arborescence globale des *cgroups*
- Faciliter la migration de conteneurs (l'arborescence reste cohérente)
- Renforcer l'isolation entre conteneurs
### Utilisation pratique
#### Observer les *cgroups* sans isolation\
Commençons par observer notre position dans l'arborescence des *cgroups* :
<div lang="en-US">
```bash
42sh$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-3.scope
```
</div>
Cette ligne nous indique que nous nous trouvons dans le *cgroup* unifié
`/user.slice/user-1000.slice/session-3.scope`.
Nous pouvons également explorer l'arborescence complète :
<div lang="en-US">
```bash
42sh$ ls /sys/fs/cgroup/
cgroup.controllers cpuset.cpus.effective memory.pressure
cgroup.max.depth cpuset.mems.effective sys-fs-fuse-connections.mount
cgroup.max.descendants dev-hugepages.mount sys-kernel-config.mount
cgroup.procs dev-mqueue.mount sys-kernel-debug.mount
cgroup.stat init.scope sys-kernel-tracing.mount
cgroup.subtree_control io.pressure system.slice
cgroup.threads memory.numa_stat user.slice
cpu.pressure memory.stat
```
</div>
#### S'isoler dans un nouveau *namespace* `cgroup`\
Créons maintenant un nouveau *namespace* `cgroup` :
<div lang="en-US">
```bash
42sh$ unshare --cgroup /bin/bash
incgroupns$ cat /proc/self/cgroup
0::/
```
</div>
Notre processus se voit maintenant à la racine de l'arborescence des *cgroups* !
En réalité, il se trouve toujours au même endroit dans l'arborescence globale,
mais sa vue a été virtualisée.
Si nous regardons le contenu de `/sys/fs/cgroup/` :
<div lang="en-US">
```bash
incgroupns$ ls /sys/fs/cgroup/
cgroup.controllers cgroup.threads io.pressure
cgroup.events cpu.pressure memory.numa_stat
cgroup.freeze cpu.stat memory.pressure
cgroup.max.depth cpuset.cpus memory.stat
cgroup.max.descendants cpuset.cpus.effective
cgroup.procs cpuset.mems
cgroup.stat cpuset.mems.effective
cgroup.subtree_control io.stat
```
</div>
Nous voyons seulement notre sous-arbre, pas l'arborescence complète du système.
#### Créer des sous-*cgroups*\
Dans notre nouveau *namespace*, nous pouvons créer des sous-groupes :
<div lang="en-US">
```bash
incgroupns$ mkdir /sys/fs/cgroup/mysubgroup
incgroupns$ ls /sys/fs/cgroup/
cgroup.controllers cgroup.threads mysubgroup
cgroup.events cpu.pressure ...
...
```
</div>
Ces sous-groupes sont en réalité créés dans notre branche de l'arborescence
globale. Depuis l'extérieur du *namespace*, ils apparaîtraient sous un chemin
différent.
### Exemple avec Docker
Docker utilise le *namespace* `cgroup` pour isoler la vue des *cgroups* dans
les conteneurs. Observons cela :
<div lang="en-US">
```bash
42sh$ docker run --rm -it debian /bin/bash
indocker# cat /proc/self/cgroup
0::/
indocker# ls /sys/fs/cgroup/
cgroup.controllers cpu.max memory.events
cgroup.events cpu.pressure memory.high
cgroup.freeze cpu.stat memory.low
cgroup.kill cpu.weight memory.max
cgroup.max.depth io.max memory.min
cgroup.max.descendants io.pressure memory.numa_stat
cgroup.procs io.stat memory.oom.group
cgroup.stat io.weight memory.pressure
cgroup.subtree_control memory.current memory.stat
cgroup.threads memory.events.local memory.swap.current
cgroup.type ...
```
</div>
Le conteneur se voit à la racine, bien qu'il soit en réalité placé dans un
sous-groupe dédié de l'arborescence globale.
### Vérifier la position réelle dans l'arborescence
Depuis l'hôte, nous pouvons voir la véritable position d'un conteneur :
<div lang="en-US">
```bash
42sh$ docker inspect mycontainer | grep -A5 CgroupParent
"CgroupParent": "",
"CgroupDriver": "systemd",
42sh$ ps aux | grep containerd-shim
root 12345 /usr/bin/containerd-shim-runc-v2 ...
42sh$ cat /proc/12345/cgroup
0::/system.slice/docker-abc123def456.scope
```
</div>
Le processus du conteneur se trouve dans un sous-groupe bien spécifique, mais
grâce au *namespace* `cgroup`, il se perçoit à la racine.
### Combinaison avec le *namespace* `mount`
Pour que l'isolation soit complète, il faut généralement combiner le *namespace*
`cgroup` avec le *namespace* `mount` afin de remonter `/sys/fs/cgroup` :
<div lang="en-US">
```bash
42sh# unshare --cgroup --mount /bin/bash
incgroupns# mount -t cgroup2 none /sys/fs/cgroup
incgroupns# ls /sys/fs/cgroup/
# Vue limitée à notre sous-arbre
```
</div>
Sans remonter le *cgroup*, nous verrions toujours l'arborescence complète via
l'ancien point de montage.
### Cas d'usage
#### Migration de conteneurs\
Lors de la migration d'un conteneur avec des outils comme CRIU (Checkpoint/Restore
In Userspace), le *namespace* `cgroup` garantit que l'arborescence des *cgroups*
reste cohérente du point de vue du processus, même si la structure de l'hôte
cible est différente.
#### Isolation de sécurité\
Sans *namespace* `cgroup`, un processus malveillant dans un conteneur pourrait :
- Lire `/sys/fs/cgroup/` pour découvrir d'autres conteneurs
- Analyser les métriques d'autres processus
- Déduire des informations sur l'architecture du système
#### Délégation de contrôle\
Le *namespace* `cgroup` permet de donner à un utilisateur non-privilégié le
contrôle d'une sous-arborescence de *cgroups*, sans lui donner accès à
l'arborescence globale.
### Exemple en C
Voici un exemple de création d'un *namespace* `cgroup` en C :
<div lang="en-US">
```c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
static int child_func(void *arg)
{
FILE *fp;
char line[256];
printf("=== Dans le namespace cgroup ===\n");
fp = fopen("/proc/self/cgroup", "r");
if (fp == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
while (fgets(line, sizeof(line), fp) != NULL) {
printf(" %s", line);
}
fclose(fp);
return 0;
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(void)
{
pid_t pid;
FILE *fp;
char line[256];
printf("=== Avant le namespace cgroup ===\n");
fp = fopen("/proc/self/cgroup", "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp) != NULL) {
printf(" %s", line);
}
fclose(fp);
}
printf("\n");
pid = clone(child_func, child_stack + STACK_SIZE,
CLONE_NEWCGROUP | SIGCHLD, NULL);
if (pid == -1) {
perror("clone");
exit(EXIT_FAILURE);
}
waitpid(pid, NULL, 0);
return 0;
}
```
</div>
::::: {.code}
Pour compiler et exécuter :
<div lang="en-US">
```bash
42sh$ gcc -o cgroup_ns_demo cgroup_ns_demo.c
42sh$ ./cgroup_ns_demo
=== Avant le namespace cgroup ===
0::/user.slice/user-1000.slice/session-3.scope
=== Dans le namespace cgroup ===
0::/
```
</div>
:::::
### Aller plus loin {-}
Pour plus d'informations :
- `cgroup_namespaces(7)` - page de manuel
- [Cgroup Namespace](https://lwn.net/Articles/621006/) : <https://lwn.net/Articles/621006/>
- [Control Group v2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) : Documentation du noyau sur cgroup v2

View file

@ -121,6 +121,34 @@ pid_t pid = clone(do_execvp, // First function executed by child
Dans cet exemple, le processus fils créé disposera d'un nouvel espace de noms
pour les *CGroups* et disposera d'une nouvelle pile réseau.
::::: {.more}
#### Compilation des exemples C {-}
\
Pour compiler les programmes C utilisant les namespaces, aucune bibliothèque
spéciale n'est requise au-delà de la bibliothèque C standard :
<div lang="en-US">
```bash
42sh$ gcc -o mon_programme mon_programme.c
```
</div>
Notez que certains appels système utilisés avec les namespaces nécessitent la
définition de `_GNU_SOURCE` au début du fichier source pour accéder aux
extensions GNU :
<div lang="en-US">
```c
#define _GNU_SOURCE
#include <sched.h>
// ...
```
</div>
:::::
::::: {.question}
#### Quel est le rôle du *flag* `SIGCHLD` ? {-}

514
tutorial/4/ipcns.md Normal file
View file

@ -0,0 +1,514 @@
Le *namespace* `IPC` {#ipc-ns}
-------------------
### Introduction
L'espace de noms `IPC`, introduit dans Linux 2.6.19, isole les objets de
communication inter-processus (IPC) System V et les files de messages POSIX.
Les objets isolés incluent :
- Les files de messages System V (`msgget`, `msgsnd`, `msgrcv`)
- Les sémaphores System V (`semget`, `semop`)
- Les segments de mémoire partagée System V (`shmget`, `shmat`)
- Les files de messages POSIX (`mq_open`, `mq_send`, `mq_receive`)
Une fois dans un *namespace* IPC différent, un processus ne peut communiquer
qu'avec les processus du même *namespace* via ces mécanismes IPC.
### Pourquoi isoler les IPC ?
Sans isolation IPC, des processus dans différents conteneurs pourraient :
- Communiquer entre eux via des files de messages partagées
- Accéder à la mémoire partagée d'autres conteneurs
- Interférer avec les sémaphores d'autres applications
L'isolation IPC garantit que chaque conteneur dispose de son propre ensemble
d'objets IPC, renforçant ainsi la sécurité et l'isolation.
### Utilisation pratique
#### Observer les objets IPC du système\
La commande `ipcs` permet de lister tous les objets IPC du système :
<div lang="en-US">
```bash
42sh$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 32768 alice 600 524288 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
0x12345678 0 bob 666 1
```
</div>
Cette commande nous montre tous les objets IPC visibles dans notre *namespace*
actuel.
#### Créer une file de messages System V\
Créons d'abord une file de messages dans notre *namespace* initial :
<div lang="en-US">
```c
// ipc_create.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
int main(void)
{
key_t key;
int msgid;
// Créer une clé unique
key = ftok("/tmp", 'A');
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
// Créer une file de messages
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
printf("File de messages créée avec l'ID : %d\n", msgid);
printf("Clé : 0x%08x\n", key);
return 0;
}
```
</div>
::::: {.code}
Compilation et exécution :
<div lang="en-US">
```bash
42sh$ gcc -o ipc_create ipc_create.c
42sh$ touch /tmp/ipc_key_file
42sh$ ./ipc_create
File de messages créée avec l'ID : 0
Clé : 0x41000001
42sh$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x41000001 0 alice 666 0 0
```
</div>
:::::
#### S'isoler dans un nouveau *namespace* IPC\
Maintenant, créons un nouveau *namespace* IPC :
<div lang="en-US">
```bash
42sh$ unshare --ipc /bin/bash
inipcns$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
```
</div>
Notre nouveau *namespace* ne contient aucun objet IPC ! La file de messages
créée précédemment n'est pas visible ici.
Si nous créons une nouvelle file de messages dans ce *namespace* :
<div lang="en-US">
```bash
inipcns$ ./ipc_create
File de messages créée avec l'ID : 0
Clé : 0x41000001
inipcns$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x41000001 0 alice 666 0 0
```
</div>
Cette nouvelle file a le même ID (0) et la même clé que celle créée dans le
*namespace* initial, mais il s'agit d'un objet complètement distinct.
#### Communication entre processus dans le même *namespace*\
Créons un exemple de communication par file de messages :
<div lang="en-US">
```c
// ipc_send.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
struct message {
long mtype;
char mtext[100];
};
int main(void)
{
key_t key;
int msgid;
struct message msg;
key = ftok("/tmp", 'A');
msgid = msgget(key, 0666 | IPC_CREAT);
msg.mtype = 1;
snprintf(msg.mtext, sizeof(msg.mtext), "Hello from PID %d", getpid());
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("Message envoyé : %s\n", msg.mtext);
return 0;
}
```
</div>
<div lang="en-US">
```c
// ipc_receive.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
struct message {
long mtype;
char mtext[100];
};
int main(void)
{
key_t key;
int msgid;
struct message msg;
key = ftok("/tmp", 'A');
msgid = msgget(key, 0666 | IPC_CREAT);
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("Message reçu : %s\n", msg.mtext);
return 0;
}
```
</div>
::::: {.code}
Compilation :
<div lang="en-US">
```bash
42sh$ gcc -o ipc_send ipc_send.c
42sh$ gcc -o ipc_receive ipc_receive.c
```
</div>
Test dans le même *namespace* :
<div lang="en-US">
```bash
# Terminal 1
42sh$ ./ipc_receive
# Attend un message...
# Terminal 2
42sh$ ./ipc_send
Message envoyé : Hello from PID 12345
# Terminal 1 reçoit :
Message reçu : Hello from PID 12345
```
</div>
:::::
#### Isolation entre *namespaces*\
Maintenant, testons avec deux *namespaces* IPC différents :
<div lang="en-US">
```bash
# Terminal 1 - namespace IPC A
42sh$ unshare --ipc /bin/bash
ipcns-A$ ./ipc_receive
# Attend un message...
# Terminal 2 - namespace IPC B (différent)
42sh$ unshare --ipc /bin/bash
ipcns-B$ ./ipc_send
Message envoyé : Hello from PID 23456
# Terminal 1 ne reçoit RIEN car les namespaces sont différents
```
</div>
Les deux processus utilisent la même clé IPC, mais comme ils sont dans des
*namespaces* différents, ils ne peuvent pas communiquer.
### Exemple avec la mémoire partagée
Voici un exemple utilisant la mémoire partagée System V :
<div lang="en-US">
```c
// shm_demo.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
key_t key;
int shmid;
char *data;
// Créer une clé
key = ftok("/tmp", 'S');
// Créer un segment de mémoire partagée de 1024 octets
shmid = shmget(key, 1024, 0644 | IPC_CREAT);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
// Attacher le segment
data = shmat(shmid, NULL, 0);
if (data == (char *)(-1)) {
perror("shmat");
exit(EXIT_FAILURE);
}
printf("Segment de mémoire partagée attaché\n");
printf("Écriture dans la mémoire partagée...\n");
sprintf(data, "Hello from PID %d in namespace", getpid());
printf("Contenu : %s\n", data);
printf("Appuyez sur Entrée pour détacher...\n");
getchar();
// Détacher
shmdt(data);
printf("Voulez-vous supprimer le segment ? (o/n) : ");
char choice = getchar();
if (choice == 'o' || choice == 'O') {
shmctl(shmid, IPC_RMID, NULL);
printf("Segment supprimé\n");
}
return 0;
}
```
</div>
::::: {.code}
Compilation et test :
<div lang="en-US">
```bash
42sh$ gcc -o shm_demo shm_demo.c
# Dans le namespace initial
42sh$ ./shm_demo &
Segment de mémoire partagée attaché
Écriture dans la mémoire partagée...
Contenu : Hello from PID 12345 in namespace
42sh$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x53000001 0 alice 644 1024 1
# Dans un nouveau namespace IPC
42sh$ unshare --ipc /bin/bash
inipcns$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
# Aucun segment visible !
```
</div>
:::::
### Files de messages POSIX
Le *namespace* IPC isole également les files de messages POSIX (qui sont
différentes des files System V) :
<div lang="en-US">
```c
// mqueue_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
int main(void)
{
mqd_t mq;
struct mq_attr attr;
char buffer[1024];
// Configurer les attributs de la file
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = 1024;
attr.mq_curmsgs = 0;
// Créer ou ouvrir une file de messages POSIX
mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0644, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
printf("File de messages POSIX créée : /myqueue\n");
// Envoyer un message
sprintf(buffer, "Hello from PID %d", getpid());
if (mq_send(mq, buffer, strlen(buffer) + 1, 0) == -1) {
perror("mq_send");
exit(EXIT_FAILURE);
}
printf("Message envoyé : %s\n", buffer);
// Recevoir le message
if (mq_receive(mq, buffer, 1024, NULL) == -1) {
perror("mq_receive");
exit(EXIT_FAILURE);
}
printf("Message reçu : %s\n", buffer);
mq_close(mq);
mq_unlink("/myqueue");
return 0;
}
```
</div>
::::: {.code}
Pour compiler (nécessite la bibliothèque `librt`) :
<div lang="en-US">
```bash
42sh$ gcc -o mqueue_demo mqueue_demo.c -lrt
42sh$ ./mqueue_demo
File de messages POSIX créée : /myqueue
Message envoyé : Hello from PID 12345
Message reçu : Hello from PID 12345
```
</div>
Les files de messages POSIX peuvent être listées dans `/dev/mqueue/` :
<div lang="en-US">
```bash
42sh$ ls -l /dev/mqueue/
total 0
-rw-r--r-- 1 alice alice 80 Nov 15 10:30 myqueue
```
</div>
Dans un nouveau *namespace* IPC, ce répertoire sera vide.
:::::
### Combinaison avec d'autres *namespaces*
En pratique, le *namespace* IPC est souvent combiné avec d'autres *namespaces*
pour une isolation complète :
<div lang="en-US">
```bash
42sh# unshare --ipc --pid --mount --fork --mount-proc /bin/bash
incontainer# # Conteneur isolé avec IPC, PID et mount namespaces
```
</div>
### Nettoyage des objets IPC
Les objets IPC persistent généralement après la terminaison des processus qui
les ont créés. Pour les nettoyer :
<div lang="en-US">
```bash
# Supprimer une file de messages
42sh$ ipcrm -q <msqid>
# Supprimer un segment de mémoire partagée
42sh$ ipcrm -m <shmid>
# Supprimer un sémaphore
42sh$ ipcrm -s <semid>
# Ou supprimer par clé
42sh$ ipcrm -Q 0x41000001
```
</div>
Lorsqu'un *namespace* IPC est détruit (tous les processus terminés), tous ses
objets IPC sont automatiquement nettoyés.
### Aller plus loin {-}
Pour plus d'informations :
- `ipc_namespaces(7)` - page de manuel
- `ipcs(1)` - lister les objets IPC
- `ipcrm(1)` - supprimer les objets IPC
- `svipc(7)` - aperçu de System V IPC
- `mq_overview(7)` - aperçu des files de messages POSIX

View file

@ -189,7 +189,7 @@ On considère préalablement que l'environnement est propice à la réalisation
```
42sh# mkdir -p /mnt/newroot
42sh# mount -t tmpfs none /mnt/newroot
42sh# wget https://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.8-x86_64.tar.gz
42sh# wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.3-x86_64.tar.gz
42sh# tar xpf alpine-minirootfs-*.tar.gz -C /mnt/newroot
42sh# cd /
```

View file

@ -0,0 +1,532 @@
\newpage
Récapitulatif des *namespaces*
===============================
Nous venons d'explorer en détail les huit types d'espaces de noms disponibles
dans le noyau Linux. Avant de conclure cette partie, récapitulons les concepts
clés et les meilleures pratiques pour utiliser efficacement ces mécanismes
d'isolation.
## Tableau comparatif des *namespaces*
Le tableau suivant résume les caractéristiques principales de chaque type
d'espace de noms :
| *Namespace* | Flag `clone()` | Depuis | Isole | Privilèges requis* |
|-------------|----------------|--------|-------|-------------------|
| **mount** | `CLONE_NEWNS` | 2.4.19 | Points de montage | Oui |
| **UTS** | `CLONE_NEWUTS` | 2.6.19 | Hostname, domaine NIS | Oui |
| **IPC** | `CLONE_NEWIPC` | 2.6.19 | Objets IPC System V et POSIX | Oui |
| **PID** | `CLONE_NEWPID` | 2.6.24 | Arbre de processus | Oui |
| **network** | `CLONE_NEWNET` | 2.6.29 | Interfaces, routes, ports | Oui |
| **user** | `CLONE_NEWUSER` | 3.8 | UIDs, GIDs, capabilities | **Non** |
| **cgroup** | `CLONE_NEWCGROUP` | 4.6 | Vue arborescence cgroup | Oui |
| **time** | `CLONE_NEWTIME` | 5.6 | Horloges monotones | Oui |
*\* Sauf si combiné avec un *namespace* `user`*
## Ce que chaque *namespace* vous permet de faire
### `mount` -- Systèmes de fichiers isolés
**Fonctionnalité :** Isole l'arborescence des points de montage.
**Permet de :**
- Créer un système de fichiers racine différent (`pivot_root`, `switch_root`)
- Monter/démonter des partitions sans affecter l'hôte
- Implémenter un `chroot` sécurisé (impossible de s'échapper)
- Utiliser des systèmes de fichiers en couches (OverlayFS)
**Cas d'usage typiques :**
- Conteneurs avec leur propre filesystem
- Environnements de build isolés
- Systèmes d'installation et de récupération
### `UTS` -- Identité système
**Fonctionnalité :** Isole le nom d'hôte et le domaine NIS.
**Permet de :**
- Donner un nom unique à chaque conteneur
- Éviter les conflits de nommage
- Faciliter l'identification des conteneurs
**Cas d'usage typiques :**
- Conteneurs avec des noms de machine distincts
- Environnements multi-tenants
- Tests de configurations réseau
### `IPC` -- Communication inter-processus
**Fonctionnalité :** Isole les objets IPC (files de messages, sémaphores,
mémoire partagée).
**Permet de :**
- Empêcher la communication IPC entre conteneurs
- Isoler les applications utilisant IPC System V ou POSIX
- Protéger contre les fuites d'informations via IPC
**Cas d'usage typiques :**
- Isolation de sécurité entre conteneurs
- Applications legacy utilisant IPC System V
- Environnements de test pour applications IPC
### `PID` -- Arbre de processus virtualisé
**Fonctionnalité :** Isole l'arbre des processus avec renumérotation.
**Permet de :**
- Avoir un processus `init` (PID 1) par conteneur
- Masquer les processus de l'hôte et des autres conteneurs
- Gérer le cycle de vie des processus indépendamment
- Récolter les processus orphelins proprement
**Cas d'usage typiques :**
- Conteneurs avec gestion complète du cycle de vie
- Isolation de sécurité (masquage des processus)
- Environnements avec plusieurs niveaux d'`init`
**Particularité :** Le processus doit forker après `unshare(CLONE_NEWPID)`
pour que l'isolation soit effective.
### `network` -- Pile réseau dédiée
**Fonctionnalité :** Isole les interfaces réseau, routes, règles de pare-feu et
ports.
**Permet de :**
- Donner une pile réseau complète à chaque conteneur
- Réutiliser les mêmes ports dans différents conteneurs
- Configurer des routes et pare-feu indépendants
- Créer des topologies réseau complexes (bridges, VLAN, MACVLAN)
**Cas d'usage typiques :**
- Conteneurs avec leur propre IP
- Tests réseau isolés
- Micro-services avec ports identiques
- Simulations de topologies réseau
**Note :** Nécessite généralement des interfaces virtuelles (`veth`, `macvlan`,
etc.) pour connecter les conteneurs.
### `user` -- Privilèges virtualisés
**Fonctionnalité :** Isole les UIDs, GIDs et les capabilities.
**Permet de :**
- Être root dans le conteneur sans être root sur l'hôte
- Lancer des conteneurs sans privilèges (rootless containers)
- Mapper des plages d'UIDs entre conteneur et hôte
- Déléguer des capacités limitées
**Cas d'usage typiques :**
- Conteneurs rootless (podman, systemd-nspawn)
- Environnements multi-utilisateurs sécurisés
- Délégation contrôlée de privilèges
**Particularité :** Seul *namespace* créable sans privilèges. Permet ensuite de
créer tous les autres types de *namespaces*.
**Attention :** Historiquement source de vulnérabilités ; à utiliser avec
précaution.
### `cgroup` -- Vue filtrée des control groups
**Fonctionnalité :** Virtualise la vue de l'arborescence des cgroups.
**Permet de :**
- Masquer l'organisation des cgroups de l'hôte
- Faciliter la migration de conteneurs
- Empêcher la fuite d'informations système via `/sys/fs/cgroup`
- Déléguer la gestion de sous-arbres de cgroups
**Cas d'usage typiques :**
- Renforcement de l'isolation de sécurité
- Migration de conteneurs (CRIU)
- Systèmes multi-tenants
**Note :** Souvent combiné avec un *namespace* `mount` pour remonter
`/sys/fs/cgroup`.
### `time` -- Horloges virtualisées
**Fonctionnalité :** Virtualise `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME`.
**Permet de :**
- Décaler les horloges monotones pour un conteneur
- Préserver l'état temporel lors de migrations
- Accélérer le temps pour les tests
**Cas d'usage typiques :**
- Migration de conteneurs avec CRIU
- Tests d'applications sensibles au temps
- Simulations temporelles
**Limitation :** Ne virtualise **pas** l'heure système (CLOCK_REALTIME).
**Particularité :** Les offsets doivent être configurés avant de créer le
premier processus enfant.
## Combinaisons recommandées
Les *namespaces* sont rarement utilisés seuls. Voici les combinaisons courantes
selon les besoins d'isolation :
### Isolation minimale (développement)
Pour un environnement de développement simple avec un filesystem isolé :
<div lang="en-US">
```bash
unshare --mount --pid --fork --mount-proc /bin/bash
```
</div>
**Isolation :** Filesystem et processus
**Avantages :** Rapide, simple, suffisant pour tester des installations
**Limitations :** Partage le réseau, les utilisateurs, IPC avec l'hôte
### Conteneur standard (production light)
Pour une isolation raisonnable sans réseau séparé :
<div lang="en-US">
```bash
unshare --mount --uts --ipc --pid --fork --mount-proc /bin/bash
```
</div>
**Isolation :** Filesystem, processus, IPC, hostname
**Avantages :** Bonne isolation, partage le réseau de l'hôte
**Cas d'usage :** Services système, environnements de build
### Conteneur avec réseau isolé
Pour une isolation complète incluant le réseau :
<div lang="en-US">
```bash
unshare --mount --uts --ipc --net --pid --fork --mount-proc /bin/bash
```
</div>
**Isolation :** Filesystem, processus, IPC, hostname, réseau
**Avantages :** Isolation réseau complète
**Note :** Nécessite configuration réseau supplémentaire (veth, bridge, etc.)
### Conteneur rootless (sans privilèges)
Pour lancer un conteneur en tant qu'utilisateur normal :
<div lang="en-US">
```bash
unshare --user --map-root-user \
--mount --uts --ipc --net --pid \
--fork --mount-proc /bin/bash
```
</div>
**Isolation :** Complète + virtualisation des privilèges
**Avantages :** Aucun privilège requis, sécurité renforcée
**Cas d'usage :** Environnements multi-utilisateurs, CI/CD non-privilégiée
### Conteneur type Docker (isolation maximale)
Pour une isolation complète similaire à Docker :
<div lang="en-US">
```bash
unshare --user --map-root-user \
--mount --uts --ipc --net --pid --cgroup \
--fork --mount-proc /bin/bash
```
</div>
**Isolation :** Tous les namespaces (sauf `time`)
**Avantages :** Isolation maximale
**Note :** Équivalent approximatif de `docker run --rm -it`
## Ordre de création des *namespaces*
L'ordre dans lequel les *namespaces* sont créés peut être important :
1. **`user` en premier** : Si vous voulez créer des *namespaces* sans
privilèges, commencez toujours par le *namespace* `user`.
2. **`mount` avant `pid`** : Pour pouvoir remonter `/proc` correctement avec
l'option `--mount-proc`.
3. **`pid` avec `--fork`** : N'oubliez jamais l'option `--fork` avec le
*namespace* `pid`, sinon le processus actuel ne sera pas réellement isolé.
4. **`time` et `cgroup` avant le fork** : Ces deux namespaces nécessitent des
configurations avant la création du premier processus enfant.
## Commandes essentielles
Voici un récapitulatif des commandes les plus utiles pour travailler avec les
*namespaces* :
### Création et entrée dans des *namespaces*
<div lang="en-US">
```bash
# Créer de nouveaux namespaces et exécuter une commande
unshare [options] [commande]
# Principales options :
# -m, --mount Namespace mount
# -u, --uts Namespace UTS
# -i, --ipc Namespace IPC
# -n, --net Namespace network
# -p, --pid Namespace PID
# -U, --user Namespace user
# -C, --cgroup Namespace cgroup
# -T, --time Namespace time
# -f, --fork Fork avant d'exécuter la commande (requis avec --pid)
# --mount-proc Monter /proc après création du namespace
# Entrer dans les namespaces d'un processus existant
nsenter [options] [commande]
nsenter --target <PID> --all [commande]
```
</div>
### Inspection des *namespaces*
<div lang="en-US">
```bash
# Lister tous les namespaces du système
lsns
# Lister les namespaces d'un processus spécifique
lsns -p <PID>
# Voir les namespaces d'un processus
ls -l /proc/<PID>/ns/
# Comparer les namespaces de deux processus
readlink /proc/<PID1>/ns/net
readlink /proc/<PID2>/ns/net
```
</div>
### Gestion réseau (namespace network)
<div lang="en-US">
```bash
# Créer un namespace network nommé
ip netns add <nom>
# Lister les namespaces network
ip netns list
# Exécuter une commande dans un namespace network
ip netns exec <nom> <commande>
# Créer une paire veth
ip link add veth0 type veth peer name veth1
# Déplacer une interface dans un namespace
ip link set veth1 netns <nom>
```
</div>
## Appels système
Pour les développeurs, voici les appels système principaux :
<div lang="en-US">
```c
// Créer un nouveau processus dans de nouveaux namespaces
pid_t clone(int (*fn)(void *), void *stack, int flags, void *arg);
// flags: CLONE_NEWNS, CLONE_NEWUTS, CLONE_NEWIPC, CLONE_NEWPID,
// CLONE_NEWNET, CLONE_NEWUSER, CLONE_NEWCGROUP, CLONE_NEWTIME
// Se dissocier de namespaces actuels
int unshare(int flags);
// Rejoindre les namespaces d'un autre processus
int setns(int fd, int nstype);
// fd: file descriptor de /proc/<PID>/ns/<type>
// nstype: 0 ou CLONE_NEW* pour vérification de type
// Obtenir un file descriptor vers un processus (pour setns)
int pidfd_open(pid_t pid, unsigned int flags);
```
</div>
## Bonnes pratiques
### Sécurité
1. **Privilèges minimaux** : Utilisez le *namespace* `user` quand possible pour
éviter d'exécuter des conteneurs en tant que root.
2. **Combinaison d'isolations** : Un seul namespace ne suffit généralement pas
pour une isolation sécurisée. Combinez plusieurs namespaces.
3. **User namespace avec précaution** : Bien que pratique, le *namespace*
`user` a été source de nombreuses vulnérabilités. Assurez-vous d'utiliser un
noyau récent et correctement patché.
4. **Nettoyage des ressources** : Les namespaces persistent tant qu'ils sont
référencés. Utilisez `umount` pour les bind mounts qui maintiennent des
namespaces actifs.
5. **Capabilities** : Même dans un *namespace* `user`, les capabilities sont
limitées à l'intérieur du namespace. Ne comptez pas sur elles pour des
actions hors namespace.
### Performance
1. **Coût minimal** : La création de namespaces a un coût négligeable en termes
de performance. N'hésitez pas à les utiliser.
2. **Réseau** : Le type d'interface virtuelle utilisé (`veth`, `macvlan`,
`ipvlan`) peut avoir un impact significatif sur les performances réseau.
3. **OverlayFS vs device-mapper** : Pour le stockage, OverlayFS est
généralement plus performant que les snapshots LVM (device-mapper).
### Débogage
1. **`lsns` est votre ami** : Utilisez `lsns` pour comprendre rapidement
l'organisation des namespaces sur votre système.
2. **Inspecter /proc** : Les fichiers dans `/proc/<PID>/ns/` sont essentiels
pour comprendre dans quels namespaces se trouve un processus.
3. **Bind mounts pour persistence** : Utilisez `mount --bind` sur les fichiers
de `/proc/<PID>/ns/` pour maintenir un namespace actif même après la
terminaison du processus.
4. **Logs et `strace`** : En cas de problème, `strace` peut vous montrer
exactement quels appels système échouent et pourquoi.
## Limitations et pièges courants
### Namespace `PID`
- **Oubli du `--fork`** : Sans fork, le processus actuel reste dans l'ancien
namespace PID.
- **Bash et PID 1** : Bash peut avoir des comportements inattendus en tant que
PID 1 (voir pidns.md pour les détails).
- **`/proc` doit être remonté** : Sinon `ps`, `top` montrent tous les processus
du système.
### Namespace `mount`
- **Propagation des montages** : Attention aux politiques `shared`, `slave`,
`private`. Par défaut, utilisez `mount --make-rslave /` pour éviter les
fuites.
- **`pivot_root` vs `chroot`** : `pivot_root` est plus sûr mais plus complexe à
mettre en œuvre.
### Namespace `network`
- **Pas d'interface par défaut** : Un nouveau namespace network ne contient
qu'une interface loopback désactivée. Il faut configurer le réseau
manuellement.
- **Complexité de la configuration** : Bridges, veth, routes, NAT... la
configuration réseau peut devenir complexe rapidement.
### Namespace `user`
- **MappingUID/GID limité** : Sans privilèges, vous ne pouvez mapper qu'un
seul UID (le vôtre vers root).
- **Fichiers setuid** : Ne fonctionnent pas correctement dans un namespace
user.
- **Restrictions par distribution** : Certaines distributions désactivent le
namespace user par défaut (voir setup.md).
### Namespace `time`
- **Configuration unique** : Les offsets ne peuvent être configurés qu'une fois,
avant le premier fork.
- **Pas d'heure réelle** : Ne virtualise pas `CLOCK_REALTIME`, seulement les
horloges monotones.
## Pour aller plus loin
### Documentation officielle
- Pages de manuel : `namespaces(7)`, `pid_namespaces(7)`, `user_namespaces(7)`,
`mount_namespaces(7)`, `network_namespaces(7)`, `ipc_namespaces(7)`,
`uts_namespaces(7)`, `cgroup_namespaces(7)`, `time_namespaces(7)`
- Documentation du noyau :
<https://www.kernel.org/doc/Documentation/admin-guide/namespaces/>
### Articles de référence
- [Namespaces in operation (série LWN)](https://lwn.net/Articles/531114/) :
série d'articles exhaustive par Michael Kerrisk
<https://lwn.net/Articles/531114/>
- [Anatomy of a user namespaces vulnerability](https://lwn.net/Articles/543273/) :
pour comprendre les enjeux de sécurité
<https://lwn.net/Articles/543273/>
### Projets utilisant les namespaces
- **Docker/Podman/containerd** : Containerisation d'applications
- **systemd-nspawn** : Conteneurs légers pour systemd
- **LXC/LXD** : Conteneurs système complets
- **Flatpak/Snap** : Sandboxing d'applications
- **Chrome/Firefox** : Sandboxing de processus de rendu
- **bubblewrap** : Outil de sandboxing générique
## Conclusion
Les *namespaces* Linux constituent une brique fondamentale de la virtualisation
légère moderne. Leur conception modulaire permet de créer des niveaux
d'isolation sur mesure, depuis un simple environnement de développement isolé
jusqu'à des conteneurs de production complets.
La maîtrise des *namespaces* nécessite de comprendre :
1. **Ce que chaque type isole** et dans quel contexte l'utiliser
2. **Comment les combiner** pour atteindre le niveau d'isolation souhaité
3. **Leurs interactions** et dépendances mutuelles
4. **Leurs limitations** et les cas particuliers à gérer
Combinés avec les *cgroups* (pour la limitation de ressources) et les *security
modules* (AppArmor, SELinux pour les politiques de sécurité), les *namespaces*
permettent de construire des environnements d'exécution sûrs, isolés et
performants.
Que vous développiez des outils de containerisation, construisiez des
plateformes cloud, ou cherchiez simplement à mieux isoler vos applications, les
*namespaces* sont un outil incontournable de l'écosystème Linux moderne.

View file

@ -82,7 +82,7 @@ virli
42sh# ip netns
foo virli
42sh# ip netns exec foo ip link
1: lo: <LOOPBACK> mut 65536 qdisc noop state DOWN mode DEFAULT group default
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
</div>
@ -99,7 +99,7 @@ des interfaces :
<div lang="en-US">
```
42sh# ip netns exec virli ip link
1: lo: <LOOPBACK> mut 65536 qdisc noop state DOWN mode DEFAULT group default
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
</div>
@ -175,11 +175,13 @@ struct nlmsghdr {
```
</div>
Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS
(Network Interface Service)[^RFC3549NIS]. Il spécifie le format par lequel
Parmi les fonctionnalités de Netlink, nous allons utiliser RTNetlink
(Routing Netlink)[^RFC3549RTNETLINK]. Il spécifie le format par lequel
doivent commencer les données liées à l'administration d'interfaces réseau.
Le module RTNetlink utilise la famille `NETLINK_ROUTE` pour communiquer avec
le noyau concernant les interfaces, routes et règles de routage.
[^RFC3549NIS]: Network Interface Service Module <https://tools.ietf.org/html/rfc3549#section-2.3.3.1>
[^RFC3549RTNETLINK]: RTNetlink - Linux Routing Socket <https://tools.ietf.org/html/rfc3549#section-2.3.3.1>
<div lang="en-US">
```c

263
tutorial/4/timens.md Normal file
View file

@ -0,0 +1,263 @@
Le *namespace* `time` {#time-ns}
---------------------
### Introduction
L'espace de noms `time`, introduit dans Linux 5.6, permet de virtualiser les
horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` pour des processus. Cela est
particulièrement utile pour la migration de conteneurs ou pour tester des
applications sensibles au temps.
::::: {.warning}
Contrairement à ce que son nom pourrait suggérer, le *namespace* `time` ne
permet **pas** de virtualiser l'heure système (l'horloge temps réel). Il ne
virtualise que les compteurs monotones.
:::::
### Utilisation pratique
Voyons comment utiliser le *namespace* `time` pour modifier le temps
d'exécution perçu par un processus.
#### Lire les horloges monotones\
Commençons par observer les valeurs actuelles de nos horloges :
<div lang="en-US">
```bash
42sh$ cat /proc/uptime
123456.78 987654.32
```
</div>
Le premier nombre représente le temps depuis le démarrage du système (équivalent
à `CLOCK_BOOTTIME`), et le second le temps CPU cumulé en mode idle.
On peut également utiliser un petit programme C pour afficher les horloges :
<div lang="en-US">
```c
#include <stdio.h>
#include <time.h>
int main(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
clock_gettime(CLOCK_BOOTTIME, &ts);
printf("CLOCK_BOOTTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
return 0;
}
```
</div>
::::: {.code}
Pour compiler ce programme :
<div lang="en-US">
```bash
42sh$ gcc -o show_clocks show_clocks.c
```
</div>
:::::
#### Créer un *namespace* `time` avec décalage\
Pour créer un nouveau *namespace* `time`, il faut d'abord le dissocier puis
configurer les décalages (offsets) avant de lancer un processus enfant.
Le fichier `/proc/<PID>/timens_offsets` permet de configurer les décalages pour
les horloges. Le format est le suivant :
<div lang="en-US">
```
<clock-id> <offset-secs> <offset-nanosecs>
```
</div>
`<clock-id>` peut être :
- `monotonic` pour `CLOCK_MONOTONIC`
- `boottime` pour `CLOCK_BOOTTIME`
Voici un script shell pour créer un conteneur avec un temps décalé de 10 jours
dans le futur :
<div lang="en-US">
```bash
42sh# unshare --time /bin/bash
intimens# echo "monotonic 864000 0" > /proc/self/timens_offsets
intimens# echo "boottime 864000 0" > /proc/self/timens_offsets
intimens# exec /bin/bash
intimens-child# ./show_clocks
CLOCK_MONOTONIC: 987654.123456789
CLOCK_BOOTTIME: 987654.123456789
```
</div>
::::: {.warning}
**Important :** Les décalages doivent être configurés **avant** de créer le
premier processus enfant. Une fois qu'un processus enfant existe dans le
*namespace*, les offsets ne peuvent plus être modifiés. C'est pourquoi nous
utilisons `exec` pour remplacer le shell actuel.
:::::
#### Exemple en C\
Voici un exemple complet en C pour créer un *namespace* `time` avec un décalage :
<div lang="en-US">
```c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
static int child_func(void *arg)
{
struct timespec ts;
// Attendre que le parent configure les offsets
sleep(1);
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("Child - CLOCK_MONOTONIC: %ld.%09ld\n",
ts.tv_sec, ts.tv_nsec);
clock_gettime(CLOCK_BOOTTIME, &ts);
printf("Child - CLOCK_BOOTTIME: %ld.%09ld\n",
ts.tv_sec, ts.tv_nsec);
return 0;
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(void)
{
pid_t pid;
int fd;
char path[256];
struct timespec ts;
// Afficher les horloges du parent
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("Parent - CLOCK_MONOTONIC: %ld.%09ld\n",
ts.tv_sec, ts.tv_nsec);
// Se dissocier du namespace time
if (unshare(CLONE_NEWTIME) == -1) {
perror("unshare");
exit(EXIT_FAILURE);
}
// Configurer les offsets (décalage de 1000 secondes)
fd = open("/proc/self/timens_offsets", O_WRONLY);
if (fd == -1) {
perror("open timens_offsets");
exit(EXIT_FAILURE);
}
if (write(fd, "monotonic 1000 0\n", 17) != 17) {
perror("write monotonic offset");
exit(EXIT_FAILURE);
}
if (write(fd, "boottime 1000 0\n", 16) != 16) {
perror("write boottime offset");
exit(EXIT_FAILURE);
}
close(fd);
// Créer un processus enfant qui verra les nouvelles horloges
pid = clone(child_func, child_stack + STACK_SIZE,
SIGCHLD, NULL);
if (pid == -1) {
perror("clone");
exit(EXIT_FAILURE);
}
waitpid(pid, NULL, 0);
return 0;
}
```
</div>
::::: {.code}
Pour compiler :
<div lang="en-US">
```bash
42sh$ gcc -o time_ns_demo time_ns_demo.c
42sh$ ./time_ns_demo
Parent - CLOCK_MONOTONIC: 123456.789012345
Child - CLOCK_MONOTONIC: 124456.789012345
Child - CLOCK_BOOTTIME: 124456.789012345
```
</div>
On voit bien le décalage de 1000 secondes entre le parent et l'enfant.
:::::
### Cas d'usage pratiques
#### Migration de conteneurs\
Lors de la migration d'un conteneur d'une machine à une autre, les processus
peuvent avoir des timers ou des alarmes basées sur `CLOCK_MONOTONIC`. Si on ne
virtualise pas ces horloges, tous ces timers seraient incorrects après la
migration.
Le *namespace* `time` permet de restaurer l'état des horloges monotones lors
de la restauration d'un conteneur.
#### Tests et simulation\
Pour tester des applications qui utilisent des timeouts ou des mécanismes de
retry avec backoff exponentiel, il peut être utile de virtualiser le temps pour
accélérer les tests sans modifier le code de l'application.
### Limitations
- Seules les horloges `CLOCK_MONOTONIC` et `CLOCK_BOOTTIME` peuvent être
virtualisées
- L'heure système (`CLOCK_REALTIME`) reste identique pour tous les processus
- Les offsets ne peuvent être configurés qu'une seule fois, avant la création
du premier processus enfant
- Les décalages ne peuvent pas être négatifs (on ne peut pas remonter dans le
temps)
### Aller plus loin {-}
Pour plus d'informations sur le *namespace* `time`, consultez :
- `time_namespaces(7)` - page de manuel
- [Time Namespace support](https://lwn.net/Articles/766089/) : <https://lwn.net/Articles/766089/>
- [Checkpoint/Restore and Time Namespace](https://lwn.net/Articles/801652/) : <https://lwn.net/Articles/801652/>