Add content for missing namespaces
This commit is contained in:
parent
ab3341bc54
commit
822dc619b8
7 changed files with 1650 additions and 6 deletions
305
tutorial/4/cgroupns.md
Normal file
305
tutorial/4/cgroupns.md
Normal 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
|
||||||
|
|
@ -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
|
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.
|
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}
|
::::: {.question}
|
||||||
|
|
||||||
#### Quel est le rôle du *flag* `SIGCHLD` ? {-}
|
#### Quel est le rôle du *flag* `SIGCHLD` ? {-}
|
||||||
|
|
|
||||||
514
tutorial/4/ipcns.md
Normal file
514
tutorial/4/ipcns.md
Normal 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
|
||||||
|
|
@ -189,7 +189,7 @@ On considère préalablement que l'environnement est propice à la réalisation
|
||||||
```
|
```
|
||||||
42sh# mkdir -p /mnt/newroot
|
42sh# mkdir -p /mnt/newroot
|
||||||
42sh# mount -t tmpfs none /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# tar xpf alpine-minirootfs-*.tar.gz -C /mnt/newroot
|
||||||
42sh# cd /
|
42sh# cd /
|
||||||
```
|
```
|
||||||
|
|
|
||||||
532
tutorial/4/namespaces-recap.md
Normal file
532
tutorial/4/namespaces-recap.md
Normal 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.
|
||||||
|
|
@ -82,7 +82,7 @@ virli
|
||||||
42sh# ip netns
|
42sh# ip netns
|
||||||
foo virli
|
foo virli
|
||||||
42sh# ip netns exec foo ip link
|
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
|
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,7 +99,7 @@ des interfaces :
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```
|
```
|
||||||
42sh# ip netns exec virli ip link
|
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
|
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -175,11 +175,13 @@ struct nlmsghdr {
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS
|
Parmi les fonctionnalités de Netlink, nous allons utiliser RTNetlink
|
||||||
(Network Interface Service)[^RFC3549NIS]. Il spécifie le format par lequel
|
(Routing Netlink)[^RFC3549RTNETLINK]. Il spécifie le format par lequel
|
||||||
doivent commencer les données liées à l'administration d'interfaces réseau.
|
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">
|
<div lang="en-US">
|
||||||
```c
|
```c
|
||||||
|
|
|
||||||
263
tutorial/4/timens.md
Normal file
263
tutorial/4/timens.md
Normal 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>
|
||||||
|
|
||||||
|
Où `<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/>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue