Compare commits
11 commits
3d19dc71be
...
25aef1af17
| Author | SHA1 | Date | |
|---|---|---|---|
| 25aef1af17 | |||
| 8c3ea223e5 | |||
| 54c3aa3643 | |||
| 593fbe3148 | |||
| 51d964d0bc | |||
| 822dc619b8 | |||
| ab3341bc54 | |||
| c12f3e684b | |||
| 594f853605 | |||
| 15cc8d404c | |||
| 46d1f91e0e |
70 changed files with 2126 additions and 228 deletions
|
|
@ -14,7 +14,7 @@ privilégiés outrepassaient ces tests, tandis que les autres devaient passer le
|
|||
tests de l'*effective UID*, *effective GID*, et autres groupes
|
||||
supplémentaires...
|
||||
|
||||
Dans les années 90, ce système s'est rélévé être un peu trop basique et
|
||||
Dans les années 90, ce système s'est révélé être un peu trop basique et
|
||||
conduisait régulièrement à des abus, au moyen de vulnérabilités trouvées dans
|
||||
les programmes *setuid root*.
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ contient les hashs des mots de passe).
|
|||
C'est ainsi qu'est apparu le `suid-bit` parmi les modes de fichiers. Lorsque
|
||||
ce bit est défini sur un binaire exécutable, au moment de l'exécution, le
|
||||
contexte passe à celui du propriétaire du fichier (`root` si le propriétaire
|
||||
est `root`, mais cela fonctionne quelque soit le propriétaire du fichier : on
|
||||
est `root`, mais cela fonctionne quel que soit le propriétaire du fichier : on
|
||||
ne devient pas `root`, mais bien l'utilisateur propriétaire).\
|
||||
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ Tout d'abord, il faut noter que chaque *thread* dispose de 5 ensembles de
|
|||
|
||||
- ***inheritable*** (I) : est utilisé au moment de la résolution des *capabilities*
|
||||
lors de l'exécution d'un nouveau processus. Il s'agit des *capabilities* qui
|
||||
seront transmises au processus fil. À moins d'avoir la *capability*
|
||||
seront transmises au processus fils. À moins d'avoir la *capability*
|
||||
`CAP_SETPCAP`, cet ensemble ne peut pas avoir plus de *capability* que celles
|
||||
présentent dans l'ensemble *permitted* ;
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ intervenir et par exemple les ACL POSIX[^ACLPOSIX] (espace *system*) :
|
|||
|
||||
[^ACLPOSIX]: Les ACL POSIX sont des permissions supplémentaires qui viennent
|
||||
s'ajouter aux modes standards du fichier (propriétaire, groupe, reste du
|
||||
monde). Avec les ACL POSIX, on peut doonner des droits à un ou plusieurs
|
||||
monde). Avec les ACL POSIX, on peut donner des droits à un ou plusieurs
|
||||
utilisateurs ou groupe, de manière spécifique.
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -331,7 +331,7 @@ struct vfs_cap_data {
|
|||
La valeur `magic` contient la version sur 1 octet, puis 3 octets sont réservés
|
||||
pour des *flags*. Actuellement un seul *flag* existe, il s'agit de
|
||||
`VFS_CAP_FLAGS_EFFECTIVE` qui détermine si la liste effective de *capabilities*
|
||||
du programme doit être remplie avec les *capabilities* *permitted* si elle doit
|
||||
du programme doit être remplie avec les *capabilities* *permitted* ou si elle doit
|
||||
rester vide (auquel cas ce sera au programme de s'ajouter les *capabilities* au
|
||||
cours de l'exécution).\
|
||||
|
||||
|
|
@ -698,5 +698,5 @@ Et de ces quelques articles :
|
|||
<https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271>
|
||||
* [Linux Capabilities on HackTricks](https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities) :\
|
||||
<https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities>
|
||||
- [POSIX Access Control Lists on Linux](https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html) :\
|
||||
* [POSIX Access Control Lists on Linux](https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html) :\
|
||||
<https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ La principale différence entre les deux est la fusion des différents
|
|||
sous-systèmes au sein d'une même arborescence. Dans la première version, chaque
|
||||
sous-système disposait de sa propre arborescence et il fallait créer les
|
||||
groupes et associer les tâches pour chaque sous-système. Avec la seconde
|
||||
version, une seule création est nécessaire, quelque soit le nombre de
|
||||
version, une seule création est nécessaire, quel que soit le nombre de
|
||||
sous-systèmes que l'on souhaite utiliser.
|
||||
|
||||
:::::
|
||||
|
|
@ -331,18 +331,18 @@ quantité de ressources mises à disposition à un groupe de processus.
|
|||
|
||||
Pour définir une limite, nous allons écrire la valeur dans le fichier
|
||||
correspondant à une valeur limite, comme par exemple
|
||||
`memory.max_usage_in_bytes` (v1) ou `memory.max` (v2), qui limite le nombre
|
||||
`memory.limit_in_bytes` (v1) ou `memory.max` (v2), qui limite le nombre
|
||||
d'octets que notre groupe de processus va pouvoir allouer au maximum :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
# cgroup v1
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.limit_in_bytes
|
||||
0
|
||||
# 0 = Aucune limite
|
||||
42sh$ echo 4M > /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
42sh$ echo 4M > /sys/fs/cgroup/memory/virli/memory.limit_in_bytes
|
||||
# Maintenant, la limite est à 4MB, vérifions...
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
|
||||
42sh$ cat /sys/fs/cgroup/memory/virli/memory.limit_in_bytes
|
||||
4194304
|
||||
```
|
||||
</div>
|
||||
|
|
@ -360,7 +360,7 @@ max
|
|||
```
|
||||
</div>
|
||||
|
||||
Chaque *cgroup*s définit de nombreux indicateurs et possède de nombreux
|
||||
Chaque *cgroup* définit de nombreux indicateurs et possède de nombreux
|
||||
limiteurs, n'hésitez pas à consulter la documentation associée à chaque
|
||||
*cgroup*.
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ limites que vous avez définies :
|
|||
<div lang="en-US">
|
||||
```
|
||||
42sh# mkdir /sys/fs/cgroup...
|
||||
42sh# echo 512M > /sys/fs/cgroup.../memory.max_usage_in_bytes
|
||||
42sh# echo 512M > /sys/fs/cgroup.../memory.limit_in_bytes
|
||||
42sh# ./monitor group_name memhog 500
|
||||
~~~ 13595 ~~~ Current memory usage: 75194368/550502400 (13%)
|
||||
~~~ 13595 ~~~ Current memory usage: 150290432/550502400 (27%)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ d'avoir de quoi bidouiller : un shell sera amplement suffisant pour commencer.
|
|||
|
||||
### `busybox`
|
||||
|
||||
Queques mots, pour commencer, à propos du projet Busybox : c'est un programme
|
||||
Quelques mots, pour commencer, à propos du projet Busybox : c'est un programme
|
||||
couteau-suisse qui implémente tous les binaires vitaux pour avoir un système
|
||||
fonctionnel et utilisable : `ls`, `sh`, `cat`, mais aussi `init`, `mdev` (un
|
||||
`udev`-like, cela permet de découvrir les périphériques attachés afin de les
|
||||
|
|
@ -171,7 +171,7 @@ chroot newroot/ bash
|
|||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
tar xpf alpine-minirootfs-*.tar.xz -C newroot/
|
||||
tar xpf alpine-minirootfs-*.tar.gz -C newroot/
|
||||
```
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Gestion de la mémoire
|
|||
Linux a une gestion de la mémoire bien particulière[^vm-overcommit] : en effet,
|
||||
par défaut, `malloc(3)` ne retournera jamais `NULL`. En se basant sur
|
||||
l'euristique qu'un bloc mémoire demandé ne sera pas utilisé directement et que
|
||||
de nombreux process ne feront pas un usage total des blocs qu'ils ont alloués,
|
||||
de nombreux processus ne feront pas un usage total des blocs qu'ils ont alloués,
|
||||
le noyau permet d'allouer plus de mémoire qu'il n'y en a réellement
|
||||
disponible. La mémoire est ainsi utilisée de manière plus efficace.
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ trouve dans l'impossibilité d'attribuer un bloc physiquement disponible, car il
|
|||
n'y en a tout simplement plus (y compris via le swap).
|
||||
|
||||
Puisque le noyau ne peut pas honorer sa promesse et qu'il n'a plus la
|
||||
possibilité de retourner `NULL` au programme qui réclamme sa mémoire (il s'agit
|
||||
possibilité de retourner `NULL` au programme qui réclame sa mémoire (il s'agit
|
||||
sans doute d'une simple assignation de variable à ce stade), il faut trouver
|
||||
une solution si l'on veut pouvoir continuer l'exécution du programme.
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ mémoire autorisée au sein du `cgroup` ?
|
|||
:::::
|
||||
|
||||
Eh oui, l'OOM-killer passe également lorsqu'un `cgroup` atteint la limite de
|
||||
mémoire qui lui est réservé. Dans ce cas évidemment, les processus pris en
|
||||
mémoire qui lui est réservée. Dans ce cas évidemment, les processus pris en
|
||||
compte sont ceux contenus dans le `cgroup`.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Rendu
|
|||
|
||||
Est attendu d'ici le TP suivant :
|
||||
|
||||
- le rendu des exercice de ce TP ;
|
||||
- le rendu des exercices de ce TP ;
|
||||
- vos réponses à [l'évaluation du cours](https://virli.nemunai.re/quiz/14).
|
||||
|
||||
Pour les GISTRE (et en bonus pour les SRS), [un
|
||||
|
|
|
|||
|
|
@ -69,4 +69,4 @@ sur `rendu3`, ... ce qui vous permet d'avoir une arborescence
|
|||
correspondant à ce qui est demandé, sans pour autant perdre votre
|
||||
travail (ou le rendre plus difficile d'accès).
|
||||
|
||||
::::
|
||||
:::::
|
||||
|
|
|
|||
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
|
||||
|
|
@ -31,7 +31,7 @@ combinaison de chaque couche.
|
|||
|
||||
### Historique
|
||||
|
||||
Les premières implémentations de ce type de systèmes de fichiers est apparu
|
||||
Les premières implémentations de ce type de systèmes de fichiers sont apparues
|
||||
avec les LiveCD : on disposait d'une distribution Linux complètement
|
||||
opérationnelle sur un support en lecture seule, mais on pouvait dédier un
|
||||
espace de stockage sur son disque dur (ou en RAM, au travers d'un `tmpfs`) pour
|
||||
|
|
@ -42,7 +42,7 @@ Historiquement, le noyau Linux devait être *patché* pour supporter ce type de
|
|||
système de fichiers (que ce soit `unionfs` ou `aufs`, les deux principaux
|
||||
*patch* apportant cette fonctionnalité). Les systèmes BSD disposent d'une
|
||||
implémentation depuis au moins 1995 et c'est SunOS qui fut le premier OS à
|
||||
développer cette technique dès 1986 (pour un système de fichier appelé
|
||||
développer cette technique dès 1986 (pour un système de fichiers appelé
|
||||
*Translucent File Service*). Pour Linux, il aura fallu attendre 2014 pour voir
|
||||
l'arrivée du système de fichier OverlayFS dans un noyau sans *patch*.
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ processus se fait en fonction des *flags* qui sont passés. On retrouve donc :
|
|||
Le nom du *flag* `CLONE_NEWNS` est historique et assez peu explicite
|
||||
contrairement aux autres : il désigne en fait l'espace de nom `mount`.
|
||||
|
||||
Au départ, les *namespace*s ont étés pensés pour former un tout : une couche
|
||||
Au départ, les *namespace*s ont été pensés pour former un tout : une couche
|
||||
d'isolation complète pour les processus. Mais lors des développements suivants,
|
||||
il s'est avéré pratique de pouvoir choisir finement de quels aspects on
|
||||
souhaitait se dissocier.
|
||||
|
|
@ -108,7 +108,7 @@ semblable à :
|
|||
#define STACKSIZE (1024 * 1024)
|
||||
static char child_stack[STACKSIZE];
|
||||
|
||||
int clone_flags = CLONE_CGROUP | CLONE_NEWNET | SIGCHLD;
|
||||
int clone_flags = CLONE_NEWCGROUP | CLONE_NEWNET | SIGCHLD;
|
||||
|
||||
pid_t pid = clone(do_execvp, // First function executed by child
|
||||
child_stack + STACKSIZE, // Assume stack grows downward
|
||||
|
|
@ -121,13 +121,41 @@ 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` ? {-}
|
||||
\
|
||||
|
||||
Lorsque l'on crée un nouveau processus, on ajoute l'option `SIGCHLD` afin
|
||||
d'être notifié par signal lorsque notre processus fil a terminé son
|
||||
d'être notifié par signal lorsque notre processus fils a terminé son
|
||||
exécution. Cela permet d'être réveillé de notre `wait(2)`.
|
||||
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ processus est rendu orphelin dans ce *namespace*, il devient un fils de ce
|
|||
processus, et non un fils de l'`init` de l'arbre global.
|
||||
|
||||
|
||||
#### L'espace de nom `network` {.unnumbered}
|
||||
#### L'espace de noms `network` {.unnumbered}
|
||||
|
||||
Depuis Linux 2.6.29.
|
||||
|
||||
|
|
@ -149,9 +149,9 @@ Lorsque l'on souhaite mesurer un écoulement de temps, la méthode naïve consis
|
|||
soustraction avec l'heure de fin. Cette technique fonctionne bien, à partir du
|
||||
moment où l'on est sûr que l'horloge ne remontera pas dans le temps, parce
|
||||
qu'elle se synchronise ou que le changement d'heure été/hiver
|
||||
intervient, ... Pour palier ces situations imprévisibles, le noyau expose une
|
||||
intervient, ... Pour pallier ces situations imprévisibles, le noyau expose une
|
||||
horloge dite monotone (`CLOCK_MONOTONIC`) : cette horloge démarre à un entier
|
||||
abstrait et s'incrèmente chaque seconde qui passe, sans jamais sauter de
|
||||
abstrait et s'incrémente chaque seconde qui passe, sans jamais sauter de
|
||||
secondes, ni revenir en arrière. C'est une horloge fiable pour calculer des
|
||||
intervalles de temps.
|
||||
|
||||
|
|
|
|||
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
|
||||
|
|
@ -8,5 +8,5 @@ abstract: |
|
|||
Le but de cette seconde partie sur les mécanismes internes du noyau
|
||||
va nous permettre d'utiliser les commandes et les appels système
|
||||
relatifs aux espaces de noms du noyau Linux ainsi que d'appréhender
|
||||
la complexité des sytèmes de fichiers.
|
||||
la complexité des systèmes de fichiers.
|
||||
...
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Cette commande nous dévoile déjà de nombreuses choses :
|
|||
contenerisé » (dans un *namespace*). Le processus initial de la machine se
|
||||
retrouve donc dans des espaces de nom, tout comme les processus d'un conteneur.
|
||||
|
||||
- On aperçoit un genre de hiérarchie dans certain cas.
|
||||
- On aperçoit un genre de hiérarchie dans certains cas.
|
||||
|
||||
- La première colonne nous renseigne sur l'identifiant du *namespace*.
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ esclave ne propagera pas ses nouveaux points de montage à son *maître*.
|
|||
<div lang="en-US">
|
||||
```bash
|
||||
# Suite de l'exemple précédent
|
||||
cd /mnt/test-slave
|
||||
mkdir /mnt/test-slave
|
||||
|
||||
# Duplication de l'accroche, sans s'occuper des éventuels sous-accroches
|
||||
mount --bind /mnt/test-shared /mnt/test-slave
|
||||
|
|
|
|||
|
|
@ -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 /
|
||||
```
|
||||
|
|
|
|||
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.
|
||||
|
|
@ -30,7 +30,7 @@ l'autre.
|
|||
:::::
|
||||
|
||||
Afin d'amener du réseau à notre nouvel espace de nom, il va falloir lui
|
||||
attribuer des interface. En fait, nous allons pouvoir déplacer nos interfaces
|
||||
attribuer des interfaces. En fait, nous allons pouvoir déplacer nos interfaces
|
||||
réseaux, dans le *namespace* vers lequel elle doit être accessible. Une
|
||||
interface donnée ne peut se trouver que dans un seul *namespace* à la fois.
|
||||
|
||||
|
|
@ -82,13 +82,13 @@ 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>
|
||||
|
||||
Les fichiers utilisés par `ip netns` ne sont donc rien de plus que des
|
||||
*bind-mount*. Ce qui explique qu'ils soient persistant même sans processus
|
||||
*bind-mount*. Ce qui explique qu'ils soient persistants même sans processus
|
||||
s'exécutant à l'intérieur.
|
||||
|
||||
:::::
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ donc été alloué à un processus d'initialisation de `bash`, qui s'est termin
|
|||
depuis.\
|
||||
|
||||
Le comportement du noyau, lorsque le PID 1 se termine, est de lancer un *kernel
|
||||
panic* (car c'est un processus indispensable, notamment de part son rôle de
|
||||
panic* (car c'est un processus indispensable, notamment de par son rôle de
|
||||
parent pour tous les processus orphelin). Au sein d'un *namespace* `PID` qui
|
||||
n'est pas le *namespace* racine, le noyau appelle la fonction
|
||||
`disable_pid_allocation` qui retire le *flag* `PIDNS_HASH_ADDING` de l'espace
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Vous allez continuer aujourd'hui le projet qui s'étendra depuis le TP précéde
|
|||
et qui consistera à réaliser la partie d'isolation de la moulinette des ACUs !
|
||||
|
||||
Cette semaine, il faudra faire en sorte de restreindre un groupe de processus
|
||||
pour qu'il s'exécute indépendemment de votre système.
|
||||
pour qu'il s'exécute indépendamment de votre système.
|
||||
|
||||
Il n'y a pas de restriction sur le langage utilisé, vous pouvez tout aussi bien
|
||||
utiliser du C, du C++, du Python, du shell, etc.
|
||||
|
|
|
|||
|
|
@ -62,4 +62,4 @@ sur `rendu4`, ... ce qui vous permet d'avoir une arborescence
|
|||
correspondant à ce qui est demandé, sans pour autant perdre votre
|
||||
travail (ou le rendre plus difficile d'accès).
|
||||
|
||||
::::
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ main(int argc, char *argv[])
|
|||
Ce programme prend au minimum deux arguments :
|
||||
- le chemin d'un fichier d'espace de nom que l'on souhaite rejoindre (le chemin
|
||||
vers le lien symbolique donc) ;
|
||||
- le programme (et ses arguments) que l'on souhaite souhaite exécuter une fois
|
||||
- le programme (et ses arguments) que l'on souhaite exécuter une fois
|
||||
que l'on a rejoint l'espace de noms ciblé.
|
||||
|
||||
Dans un premier temps, on ouvre le fichier passé en paramètre afin d'obtenir un
|
||||
|
|
|
|||
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/>
|
||||
|
|
@ -14,6 +14,6 @@ abstract: |
|
|||
|
||||
Les exercices de ce cours sont à rendre au plus tard le mardi 15
|
||||
novembre 2022 à 23 h 42. Consultez les sections matérialisées par un
|
||||
bandeau jaunes et un engrenage pour plus d'informations sur les
|
||||
bandeau jaune et un engrenage pour plus d'informations sur les
|
||||
éléments à rendre.
|
||||
...
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Il existe différentes typologies d'images :
|
|||
MongoDB, ...
|
||||
- certaines contiennent un programme spécifique : on peut vouloir utiliser
|
||||
`git`, le client `MySQL`, `emacs` ou bien encore le navigateur Chrome ;
|
||||
- d'autes enfin servent pour développer et étendre les possibilités. Ce sont
|
||||
- d'autres enfin servent pour développer et étendre les possibilités. Ce sont
|
||||
souvent des images utilisées comme bases pour les deux types que l'on a vus :
|
||||
Debian, Alpine, Windows, ...
|
||||
|
||||
|
|
@ -62,13 +62,13 @@ Les conteneurs
|
|||
|
||||
On parle d'un conteneur pour désigner une instance en cours d'exécution.
|
||||
|
||||
Lorsqu'on lance un conteneur, une copie de l'image est créée sur le disque,
|
||||
puis le système crée une isolation, pour contenir et restreindre l'exécution
|
||||
aux seules données de l'image.
|
||||
Lorsqu'on lance un conteneur, une couche lecture/écriture est créée à partir
|
||||
de l'image, puis le système crée une isolation, pour contenir et restreindre
|
||||
l'exécution aux seules données de l'image.
|
||||
|
||||
Les conteneurs sont par nature **immuables** : on exécute le contenu d'une
|
||||
image déjà construite, on n'y change pas le code qui est exécuté et on n'y fait
|
||||
pas de changements qui pourrait altérer son fonctionnement. L'idée directrice dans
|
||||
pas de changements qui pourraient altérer son fonctionnement. L'idée directrice dans
|
||||
le design des conteneurs est de toujours être en mesure de pouvoir relancer un
|
||||
conteneur ailleurs et s'attendre exactement au même comportement.
|
||||
|
||||
|
|
@ -90,6 +90,6 @@ avec son historique (la manière dont il a été installé, les mises à jour qu
|
|||
ont été appliquées, les configurations modifiées et les données qui ont été
|
||||
apportées au fil du temps ...). On ne peut pas dans ces circonstances
|
||||
remplacer un conteneur système par un autre, tout comme on ne peut pas
|
||||
remplacer une machine virtuelle que l'on a installée et configuré à la main.
|
||||
remplacer une machine virtuelle que l'on a installée et configurée à la main.
|
||||
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#### Que fait Drone pour « surveiller » un dépôt ? {-}
|
||||
\
|
||||
|
||||
Grâce aux permissions de que Drone a récupéré lors de la connexion OAuth à
|
||||
Grâce aux permissions que Drone a récupéré lors de la connexion OAuth à
|
||||
Gitea, il peut non seulement lire et récupérer le code des différents dépôts
|
||||
auxquels vous avez accès, mais il peut aussi changer certains paramètres.
|
||||
|
||||
|
|
@ -17,5 +17,5 @@ dépôt, sous l'onglet *Déclencheurs Web*.
|
|||
|
||||
À chaque fois qu'un événement va se produire sur le dépôt, Gitea va prévenir
|
||||
Drone qui décidera si l'évènement doit conduire à lancer l'intégration continue
|
||||
ou non, selon les instructions qu'on lui a donné dans la configuration du
|
||||
ou non, selon les instructions qu'on lui a données dans la configuration du
|
||||
dépôt.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ lors que la compilation est terminée, car nous n'en faisons rien.
|
|||
|
||||
::::: {.exercice}
|
||||
|
||||
Ajoutons donc une nouvelle règle à notre `.droneci.yml` pour placer le binaire
|
||||
Ajoutons donc une nouvelle règle à notre `.drone.yml` pour placer le binaire
|
||||
au sein de la liste des fichiers téléchargeables aux côtés des tags.
|
||||
|
||||
Vous aurez sans doute besoin de :
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ en tête.\
|
|||
Ces contraintes : tant liées à la **sécurité** (il faut s'assurer
|
||||
qu'un service n'utilise pas une bibliothèque vulnérable par exemple, donc soit
|
||||
utilisé sur un système à jour, et qu'il ne tourne pas en `root`), qu'à la
|
||||
**disponibilité** (si le service est mal codé est contient beaucoup de fuites
|
||||
**disponibilité** (si le service est mal codé et contient beaucoup de fuites
|
||||
mémoire, il ne faut pas que les autres services présents sur la même machine en
|
||||
pâtissent).
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ Le même principe est aussi valable pour Python, Ruby, ... : les développeurs
|
|||
ont toujours eu tendance à vouloir utiliser les dernières améliorations d'un
|
||||
langage, mais les administrateurs système n'ont alors pas de paquets stables
|
||||
dans la distribution. En effet, les distributions stables telles que Debian,
|
||||
RedHat ou CentOS ont des cycles de vie assez long et se concentrent plus sur la
|
||||
RedHat ou CentOS ont des cycles de vie assez longs et se concentrent plus sur la
|
||||
stabilité.\
|
||||
Cette stabilité est obtenue grâce à l'utilisation de versions éprouvées des
|
||||
langages et des bibliothèques, qui assurent un temps de maintenance et de
|
||||
|
|
@ -197,6 +197,6 @@ conçu par Netflix, qui est un programme qui va casser de manière aléatoire de
|
|||
éléments de l'environnement de production. Le but est de provoquer sciemment
|
||||
des pannes, des latences, ... à n'importe quel niveau du produit, afin d'en
|
||||
tester (brutalement certes) sa résilience. Cela oblige les développeurs, les
|
||||
opérationnels et les architectes à concevoir des services hautement tolérant
|
||||
opérationnels et les architectes à concevoir des services hautement tolérants
|
||||
aux pannes, ce qui fait que le jour où une véritable panne survient, elle n'a
|
||||
aucun impact sur la production (enfin on espère !).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
### Et ensuite ?
|
||||
|
||||
Nous avons vu une manière possible de distribuer notre projet. Essayons-en une
|
||||
autre parmis celles proposées.
|
||||
autre parmi celles proposées.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Vous pouvez néanmoins tester les plugins
|
|||
[`scp`](http://plugins.drone.io/appleboy/drone-scp/) ou
|
||||
[`ansible`](http://plugins.drone.io/drone-plugins/drone-ansible/) si vous avez
|
||||
une machine virtuelle avec une connexion SSH. N'hésitez pas à l'ajouter à votre
|
||||
`.droneci.yml`.
|
||||
`.drone.yml`.
|
||||
|
||||
|
||||
### Profitons !
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ La commande que nous allons utiliser pour lancer Renovatebot est la suivante :
|
|||
<div lang="en-US">
|
||||
```shell
|
||||
docker container run --name renovate --network my_ci_net \
|
||||
-e RENOVATE_ENDPOINT="http://gitea:3000/api/v1/" RENOVATE_PLATFORM=gitea \
|
||||
-e RENOVATE_ENDPOINT="http://gitea:3000/api/v1/" -e RENOVATE_PLATFORM=gitea \
|
||||
-e RENOVATE_TOKEN -e RENOVATE_GIT_AUTHOR="Renovatebot <renovate@sample>" \
|
||||
-e RENOVATE_AUTODISCOVER=true -e RENOVATE_LOG_LEVEL=info -d \
|
||||
renovate/renovate
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Dès que le conteneur sera lancé, nous devrions voir apparaître une ou plusieu
|
|||
*pull-requests* pour le projet `youp0m`. Si votre CI est configurée
|
||||
correctement, des tests automatiques seront lancés.
|
||||
|
||||
Le conteneur s'arrête dès qu'il a terminé d'analysé tous les dépôts. Vous
|
||||
Le conteneur s'arrête dès qu'il a terminé d'analyser tous les dépôts. Vous
|
||||
devrez le relancer si vous attendez une nouvelle action de la part de
|
||||
Renovatebot. Il est courant de le lancer entre chaque heure et 2 ou 4 fois par
|
||||
jour.
|
||||
|
|
@ -18,7 +18,7 @@ consultez la liste des éléments de configuration :
|
|||
|
||||
Ne soyez pas effrayé par la liste interminable d'options. Il est vrai que la
|
||||
première fois, on peut se sentir submergé de possibilités, mais il faut noter
|
||||
que le projet arriver avec des options par défaut plutôt correctes, et que l'on
|
||||
que le projet arrive avec des options par défaut plutôt correctes, et que l'on
|
||||
peut facilement avoir une configuration commune pour tous nos dépôts, à travers
|
||||
les *presets*.
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ Un certain nombre de *presets* sont distribués par défaut, voici la liste
|
|||
(humainement lisible cette fois) :
|
||||
<https://docs.renovatebot.com/presets-default/>
|
||||
|
||||
Voici un exemple de configuration que vous pouvez utilisé comme base de tous
|
||||
Voici un exemple de configuration que vous pouvez utiliser comme base de tous
|
||||
vos projets :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -69,7 +69,7 @@ Voici un exemple de fichier `default.json` que vous pourriez vouloir utiliser :
|
|||
Attention, on ne le répétera jamais assez, mais Renovatebot peut vite devenir
|
||||
infernal, car il va créer de nombreuses *pull-requests*, inlassablement. Il
|
||||
convient de rapidement activer la fusion automatique des mises à jour pour
|
||||
lesquelles vous avez confiances et pour lesquelles vous ne feriez qu'appuyer
|
||||
lesquelles vous avez confiance et pour lesquelles vous ne feriez qu'appuyer
|
||||
sur le bouton de fusion, sans même tester vous-même. La fonctionnalité est
|
||||
décrite en détail dans la documentation[^RENOVATE_AUTOMERGE] et explique les
|
||||
différentes stratégies. Néanmoins, il est nécessaire d'avoir une bonne suite de
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
Autres outils indispensables
|
||||
----------------------------
|
||||
|
||||
### Maintient à jour des dépendances
|
||||
### Maintien à jour des dépendances
|
||||
|
||||
Une opération fastidieuse, souvent oubliée sitôt le projet envoyé en
|
||||
production, c'est la mise à jour des dépendances applicatives. Fastidieux car
|
||||
il faut d'une part être informé qu'une mise à jour est disponible, c'est-à-dire
|
||||
qu'il faut suivre les mails, parfois nombreux, informant des nouvelles
|
||||
*releases*, parfois il s'agir de newslettre, ou encore parfois aucune
|
||||
*releases*, parfois il s'agit de newsletters, ou encore parfois aucune
|
||||
notification ne peut être programmée, il faut se rendre régulièrement sur un
|
||||
site pour savoir si oui ou non une mise à jour est disponible.
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ accès au périphérique correspondant au compteur.
|
|||
|
||||
### Conteneur LXC
|
||||
|
||||
LXC peut s'utiliser de multiple manière différentes, y compris avec des images
|
||||
LXC peut s'utiliser de multiples manières différentes, y compris avec des images
|
||||
OCI. Choisissez la méthode qui vous semble la plus appropriée, il est attendu
|
||||
au moins un script pour lancer notre conteneur, s'il est différent d'un
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Voici à quoi pourrait ressembler le playbook Ansible démarrant notre agent Dro
|
|||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
- name: Launch drone runer
|
||||
- name: Launch drone runner
|
||||
docker_container:
|
||||
name: droneci-runner
|
||||
image: "drone/drone-runner-docker:1"
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ fundation*, notamment les [Compute Module 3 et
|
|||
4](https://www.raspberrypi.com/products/compute-module-4/) et les [Raspberry Pi
|
||||
Zero 2 W](https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/), ainsi
|
||||
que de nombreux PCB fait par l'entreprise, à base de micro-contrôleurs AVR,
|
||||
lorsqu'il est nécessaire de pour s'interfacer avec des équipements
|
||||
lorsqu'il est nécessaire pour s'interfacer avec des équipements
|
||||
propriétaires non prévu pour l'immotique.
|
||||
|
||||
Une grosse partie des travaux est donc réalisé avec un noyau Linux, sur du
|
||||
Une grosse partie des travaux est donc réalisée avec un noyau Linux, sur du
|
||||
matériel très performant, pour de l'embarqué.
|
||||
\
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ entraver la stabilité de la plate-forme en cas de déploiement d'un module
|
|||
défaillant.
|
||||
|
||||
Vous êtes également chargés de jeter les bases du système d'intégration continu
|
||||
des modules. (La partie déploiement continu, sera réalisé plus tard par
|
||||
des modules. (La partie déploiement continu, sera réalisée plus tard par
|
||||
l'équipe développant le nouveau système de base, suivant le meilleur outil que
|
||||
vous retiendrez.)
|
||||
\
|
||||
|
|
@ -90,5 +90,5 @@ quelques tests automatiques. Puis nous publierons automatiquement le binaire
|
|||
`linky2influx` comme fichier associé à un tag au sein de l'interface web du
|
||||
gestionnaire de versions.
|
||||
|
||||
Nous testerons enfin différentes solution pour déployer notre binaire, afin
|
||||
Nous testerons enfin différentes solutions pour déployer notre binaire, afin
|
||||
d'établir quelle est la solution adéquate.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ le projet `youp0m`, que l'on connaît déjà bien.
|
|||
\
|
||||
|
||||
Dans un premier temps, on voudra juste compiler notre projet, pour s'assurer
|
||||
que chaque *commmit* poussé ne contient pas d'erreur de compilation (dans
|
||||
que chaque *commit* poussé ne contient pas d'erreur de compilation (dans
|
||||
l'environnement défini comme étant celui de production, donc avec une version
|
||||
précise des outils de compilation). Ensuite, nous ajouterons quelques tests
|
||||
automatiques, puis nous publierons automatiquement le binaire `youp0m` comme
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ contiendra les paramètres d'exécution.
|
|||
|
||||
<div lang="en-US">
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
influxdb:
|
||||
...
|
||||
|
|
@ -30,11 +29,14 @@ Ce fichier est un condensé des options que nous passons habituellement au
|
|||
|
||||
#### `version`
|
||||
|
||||
Notons toutefois la présence d'une ligne `version` ; il ne s'agit pas de la
|
||||
version de vos conteneurs, mais de la version du format de fichier
|
||||
`docker-compose` qui sera utilisé. Sans indication de version, la version
|
||||
originale sera utilisée, ne vous permettant pas d'utiliser les dernières
|
||||
fonctionnalités de Docker.
|
||||
Le champ `version` était utilisé pour spécifier la version du format de fichier
|
||||
`docker-compose`. Depuis les versions récentes de Docker Compose, ce champ est
|
||||
**déprécié** et n'est plus nécessaire. Docker Compose détecte automatiquement
|
||||
les fonctionnalités utilisées dans le fichier.
|
||||
|
||||
Vous pouvez donc **omettre ce champ** dans vos nouveaux fichiers
|
||||
`docker-compose.yml`. Si vous rencontrez d'anciens fichiers avec `version:
|
||||
"3.9"` ou similaire, sachez qu'il peut être supprimé sans problème.
|
||||
|
||||
|
||||
#### `services`
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ docker container run -p 8086:8086 -d --name mytsdb influxdb:1.8
|
|||
::::: {.warning}
|
||||
|
||||
Remarquez que nous n'utilisons pas la version 2 d'InfluxDB. Sa mise en
|
||||
place est plus contraignantes pour faire de simples tests. Si vous
|
||||
place est plus contraignante pour faire de simples tests. Si vous
|
||||
souhaitez tout de même utiliser la dernière version de la stack TICK,
|
||||
vous pouvez consulter le `README` du conteneur sur le Docker Hub :\
|
||||
<https://hub.docker.com/_/influxdb>
|
||||
|
|
@ -147,6 +147,14 @@ tar xzv -C /tmp
|
|||
```
|
||||
</div>
|
||||
|
||||
|
||||
::::: {.more}
|
||||
|
||||
Vous pouvez vérifier la dernière version disponible sur la
|
||||
[page des releases](https://github.com/influxdata/telegraf/releases)
|
||||
et ajuster la variable `V` en conséquence.
|
||||
|
||||
:::::
|
||||
Puis, lançons *Telegraf* :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -205,7 +213,7 @@ lancé, celui-ci va régulièrement envoyer des métriques de cette machine.
|
|||
### Afficher les données collectées
|
||||
|
||||
À vous de jouer pour lancer le conteneur
|
||||
[*Chronograf*](https://store.docker.com/images/chronograf).
|
||||
[*Chronograf*](https://hub.docker.com/_/chronograf).
|
||||
|
||||
::::: {.question}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ monitoring, d'un simple :
|
|||
|
||||
<div lang="en-US">
|
||||
```
|
||||
42sh$ docker-compose up
|
||||
42sh$ docker compose up
|
||||
```
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ difficulté du rebond.
|
|||
|
||||
Un certain nombre de capabilities Linux sont retirées par Docker au
|
||||
moment de l'exécution du conteneur, on peut utiliser les options
|
||||
`--cap-add` et `--cap-drop` pour respectivement ajouter et retirer une
|
||||
`--cap-add` et `--cap-drop` pour respectivement ajouter et retirer des
|
||||
capabilities.
|
||||
|
||||
Notez que l'option `--privileged` ne retire aucune capabilities à
|
||||
|
|
@ -114,3 +114,34 @@ On peut ensuite l'appliquer à un conteneur Docker :
|
|||
sleep: cannot read realtime clock: Operation not permitted
|
||||
```
|
||||
</div>
|
||||
|
||||
|
||||
::::: {.exercice}
|
||||
|
||||
## Sécuriser la stack TICK
|
||||
|
||||
Maintenant que vous connaissez les mécanismes de limitation de ressources et
|
||||
de sécurisation, appliquez-les à votre stack de monitoring TICK.
|
||||
|
||||
### Limites de ressources
|
||||
|
||||
1. Ajoutez des limites mémoire à vos conteneurs dans le `docker-compose.yml` :
|
||||
- InfluxDB : limiter à 512 Mo de RAM
|
||||
- Chronograf : limiter à 256 Mo de RAM
|
||||
- Telegraf : limiter à 128 Mo de RAM
|
||||
|
||||
2. Configurez les partages CPU pour que :
|
||||
- InfluxDB obtienne 75% des ressources CPU disponibles (768)
|
||||
- Chronograf et Telegraf se partagent les 25% restants (128 chacun)
|
||||
|
||||
|
||||
### Sécurité
|
||||
|
||||
3. Identifiez quelles capabilities sont nécessaires pour Telegraf afin de
|
||||
collecter les métriques système.
|
||||
|
||||
4. Testez le lancement de vos conteneurs avec l'option `--read-only` pour le
|
||||
système de fichiers. Quels volumes devez-vous ajouter pour que les
|
||||
conteneurs fonctionnent correctement en lecture seule ?
|
||||
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ communauté, et parfois même appropriées par Docker.
|
|||
Dans cette partie, nous allons avoir besoin du plugin `docker-compose`.
|
||||
|
||||
L'équipe en charge du projet met à disposition un exécutable que nous pouvons
|
||||
téléchargeant depuis <https://github.com/docker/compose/releases>.
|
||||
télécharger depuis <https://github.com/docker/compose/releases>.
|
||||
|
||||
Ajoutez l'exécutable dans le dossier des plugins : `$HOME/.docker/cli-plugins`
|
||||
(sans oublier de `chmod +x` !).
|
||||
|
|
@ -21,7 +21,7 @@ Ajoutez l'exécutable dans le dossier des plugins : `$HOME/.docker/cli-plugins`
|
|||
|
||||
Autrefois, `docker-compose` était un script tiers que l'on utilisait
|
||||
indépendamment de Docker. Le projet, historiquement écrit en Python, a été
|
||||
entièrement réécrit récemment afin qu'il s'intégre mieux dans l'écosystème.
|
||||
entièrement réécrit récemment afin qu'il s'intègre mieux dans l'écosystème.
|
||||
|
||||
Vous trouverez encore de nombreux articles vous incitant à utiliser
|
||||
`docker-compose`. Dans la plupart des cas, vous pouvez simplement remplacer par
|
||||
|
|
|
|||
|
|
@ -44,5 +44,5 @@ s'agit d'un mécanisme de séries temporelles (*Time Series*) moderne, que l'on
|
|||
peut utiliser pour stocker toute sorte de données liées à un indice temporel.
|
||||
|
||||
La pile logicielle TICK propose de collecter des métriques, en les enregistrant
|
||||
dans une base de données adaptées et permet ensuite de les ressortir sous
|
||||
dans une base de données adaptée et permet ensuite de les ressortir sous
|
||||
forme de graphiques ou de les utiliser pour faire des alertes intelligentes.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Consulter les journaux
|
|||
----------------------
|
||||
|
||||
La première étape consiste bien souvent à regarder ce que le conteneur affiche
|
||||
sur ses sorties standard et d'erreur. Lorsqu'il est lancé en monde *daemon*, il
|
||||
sur ses sorties standard et d'erreur. Lorsqu'il est lancé en mode *daemon*, il
|
||||
convient d'utiliser la commande :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
@ -76,7 +76,7 @@ docker container top cntr_name
|
|||
```
|
||||
</div>
|
||||
|
||||
Cela liste tous les processus rattaché au conteneur nommé : à la fois les
|
||||
Cela liste tous les processus rattachés au conteneur nommé : à la fois les
|
||||
processus démarrés par le `run`, mais également les éventuels processus
|
||||
rattachés par `exec`.
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ ici, puisque c'est ce volume qui assure la persistance des images.
|
|||
|
||||
:::::
|
||||
|
||||
Nos images sont bien persistantes d'une instance à l'autre de notre contenu.
|
||||
Nos images sont bien persistantes d'une instance à l'autre de notre conteneur.
|
||||
|
||||
Nous voici prêt à déployer en production notre service, sans crainte de perdre
|
||||
Nous voici prêts à déployer en production notre service, sans crainte de perdre
|
||||
les jolies contributions. Mais... est-ce que ce sera suffisant pour répondre aux
|
||||
milliers de visiteurs attendus ?
|
||||
|
||||
|
|
|
|||
|
|
@ -42,16 +42,16 @@ docker container run --publish 8080:8080 registry.nemunai.re/youp0m
|
|||
</div>
|
||||
|
||||
Cet argument va faire effectuer à Docker une étape supplémentaire lorsqu'il
|
||||
démarerra le conteneur : il va devoir mettre en place une redirection du port
|
||||
démarrera le conteneur : il va devoir mettre en place une redirection du port
|
||||
de notre système local (8080) vers le port 8080 du conteneur.
|
||||
|
||||
{ width=70% }
|
||||
|
||||
::::: {.question}
|
||||
|
||||
#### Peut-on démarrer plusieurs conteurs utilisant le même port de notre système local ? {-}
|
||||
#### Peut-on démarrer plusieurs conteneurs utilisant le même port de notre système local ? {-}
|
||||
|
||||
Si l'on essai de lancer deux fois la commande de notre dernier `run`, nous
|
||||
Si l'on essaie de lancer deux fois la commande de notre dernier `run`, nous
|
||||
obtenons l'erreur suivante :
|
||||
|
||||
<div lang="en-US">
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Le daemon assemble ensuite l'image et met en place les différents éléments
|
|||
d'isolation pour préparer l'exécution de notre conteneur.
|
||||
|
||||
Enfin, il lance la commande qui sera le premier processus du conteneur. Cette
|
||||
commande fait parti des métadonnées de l'image. Le processus ainsi lancé est un
|
||||
commande fait partie des métadonnées de l'image. Le processus ainsi lancé est un
|
||||
peu particulier : il obtient les mêmes caractéristiques que le PID 1 de notre
|
||||
système ('init').
|
||||
|
||||
|
|
@ -57,11 +57,12 @@ afficher beaucoup de contenu pour faire son choix. Aussi, il est souvent plus
|
|||
pratique d'aller explorer les registres en passant directement par leur interface
|
||||
web.
|
||||
|
||||
Vous trouverez forcément votre bonheur parmi les images proposées par les deux
|
||||
Vous trouverez forcément votre bonheur parmi les images proposées par les trois
|
||||
principaux registres :
|
||||
|
||||
- <https://hub.docker.com/>
|
||||
- <https://quay.io/>
|
||||
- <https://ghcr.io/> (GitHub Container Registry)
|
||||
|
||||
::::: {.warning}
|
||||
|
||||
|
|
@ -155,7 +156,7 @@ registre tiers :
|
|||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
docker container un registry.nemunai.ie/hello-world
|
||||
docker container run registry.nemunai.re/hello-world
|
||||
```
|
||||
</div>
|
||||
|
||||
|
|
@ -164,9 +165,9 @@ nécessaire de récupérer l'image. Elle est ensuite exécutée. Vous devriez do
|
|||
obtenir un résultat en deux parties, similaire à la sortie suivante :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
```
|
||||
Unable to find image 'registry.nemunai.re/hello-world:latest' locally
|
||||
latest: Pulling from library/hello-world
|
||||
latest: Pulling from hello-world
|
||||
2db29710123e: Already exists
|
||||
Digest: sha256:7d246653d0511db2a6b2e0436cfd0e52ac8c066000264b3ce63331ac66dca625
|
||||
Status: Downloaded newer image for registry.nemunai.re/hello-world:latest
|
||||
|
|
@ -357,7 +358,7 @@ c'est toujours le daemon qui exécute directement les commandes et gère les
|
|||
entrées et sorties standards et d'erreur. Avec l'option `--interactive`, on
|
||||
s'assure que l'entrée standard ne sera pas fermée (`close(2)`). Nous demandons
|
||||
également l'allocation d'un TTY, sans quoi `bash` ne se lancera pas en mode
|
||||
interractif[^bashnointer].
|
||||
interactif[^bashnointer].
|
||||
|
||||
[^bashnointer]: Mais il sera possible de l'utiliser sans allouer de TTY, comme
|
||||
dans cet exemple :
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ distribution :
|
|||
|
||||
### Sous Windows et macOS
|
||||
|
||||
Bien que les fonctionnalités de contenerisation de Docker que nous utiliserons
|
||||
Bien que les fonctionnalités de conteneurisation de Docker que nous utiliserons
|
||||
ne soient disponibles que sous Linux, il est possible d'utiliser Docker de
|
||||
manière déportée : le daemon Docker tournera dans une machine virtuelle Linux,
|
||||
mais vous pourrez interagir avec lui via votre ligne de commande habituelle.
|
||||
|
|
@ -80,6 +80,12 @@ car la licence est gratuite pour un usage éducatif ou personnel.
|
|||
Notez que cela ne concerne pas le projet ou le binaire Docker : ceux-ci restent
|
||||
libres. Seules les applications Docker Desktop sont concernées.
|
||||
|
||||
Si vous souhaitez des alternatives libres, vous pouvez considérer :
|
||||
|
||||
- **Rancher Desktop** : <https://rancherdesktop.io/>
|
||||
- **Podman Desktop** : <https://podman-desktop.io/>
|
||||
- **Colima** (macOS uniquement) : <https://github.com/abiosoft/colima>
|
||||
|
||||
:::::
|
||||
|
||||
[^DockerSubscription]: <https://www.docker.com/blog/updating-product-subscriptions/>
|
||||
|
|
@ -175,4 +181,12 @@ Cette action n'est pas anodine d'un point de vue de la sécurité :
|
|||
|
||||
<https://docs.docker.com/engine/security/#docker-daemon-attack-surface>
|
||||
|
||||
Les membres du groupe `docker` peuvent obtenir les privilèges root sur la
|
||||
machine. Pour un environnement plus sécurisé, considérez :
|
||||
|
||||
- **Docker en mode rootless** : permet d'exécuter le daemon Docker sans
|
||||
privilèges root
|
||||
- **Podman** : alternative à Docker fonctionnant sans daemon et sans privilèges
|
||||
root par défaut
|
||||
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ indiquent que le service cherche à se connecter à une base de données. Il va
|
|||
donc falloir lier notre interface d'administration à [un conteneur
|
||||
MariaDB](https://hub.docker.com/_/mariadb).
|
||||
|
||||
Ne vous embêtez pas avec les mots de passes des services, initialisez la base
|
||||
Ne vous embêtez pas avec les mots de passe des services, initialisez la base
|
||||
de données avec le nom d'utilisateur et le mot de passe par défaut. Vous les
|
||||
obtiendrez en lisant la documentation de l'image fic-admin :
|
||||
<https://hub.docker.com/r/nemunaire/fic-admin/>
|
||||
|
|
|
|||
|
|
@ -88,3 +88,21 @@ docker network connect NETWORK CONTAINER
|
|||
Lorsque plusieurs conteneurs ont rejoint un réseau utilisateur, ils peuvent
|
||||
mutuellement se découvrir grâce à un système de résolution de nom basé sur leur
|
||||
nom de conteneur.
|
||||
|
||||
::::: {.question}
|
||||
|
||||
#### Et l'option `--link` ? {-}
|
||||
|
||||
Vous trouverez peut-être dans d'anciens tutoriels l'utilisation de l'option
|
||||
`--link` pour connecter des conteneurs. Cette option est **dépréciée** depuis
|
||||
Docker 1.13 et ne devrait plus être utilisée.
|
||||
|
||||
Les réseaux utilisateurs (`user-defined bridge networks`) sont la méthode
|
||||
recommandée pour faire communiquer des conteneurs, car ils offrent :
|
||||
|
||||
- Une meilleure isolation réseau
|
||||
- La découverte automatique des noms (DNS intégré)
|
||||
- La possibilité de connecter/déconnecter dynamiquement des conteneurs
|
||||
- Plus de flexibilité et de sécurité
|
||||
|
||||
:::::
|
||||
|
|
|
|||
|
|
@ -3,4 +3,113 @@
|
|||
Garder des secrets
|
||||
==================
|
||||
|
||||
TODO
|
||||
Lorsque nous déployons des applications conteneurisées, celles-ci ont souvent
|
||||
besoin d'informations sensibles : mots de passe de base de données, clefs API,
|
||||
certificats, tokens d'authentification, etc. La gestion de ces secrets est
|
||||
cruciale pour la sécurité de nos applications.
|
||||
|
||||
|
||||
## Ce qu'il ne faut pas faire
|
||||
|
||||
Avant de voir les bonnes pratiques, commençons par les erreurs courantes à
|
||||
éviter absolument :
|
||||
|
||||
::::: {.warning}
|
||||
|
||||
**Ne jamais inclure de secrets dans une image Docker !**
|
||||
|
||||
Lorsque vous construisez une image avec un `Dockerfile`, tout ce qui est copié
|
||||
dans l'image y reste, même si vous le supprimez dans une couche ultérieure. Les
|
||||
images peuvent être inspectées, et les secrets peuvent être extraits de
|
||||
l'historique des couches.
|
||||
|
||||
:::::
|
||||
|
||||
::::: {.warning}
|
||||
|
||||
**Ne jamais passer de secrets via la ligne de commande !**
|
||||
|
||||
Les arguments de la ligne de commande sont visibles via `ps`, `docker inspect`,
|
||||
et sont souvent enregistrés dans l'historique du shell.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
# MAUVAIS EXEMPLE - À NE PAS FAIRE
|
||||
docker container run -e DB_PASSWORD=monmotdepasse myapp
|
||||
```
|
||||
</div>
|
||||
|
||||
:::::
|
||||
|
||||
|
||||
## Variables d'environnement avec fichiers
|
||||
|
||||
Pour éviter que les secrets n'apparaissent dans l'historique de commandes ou
|
||||
dans les processus, Docker permet de charger les variables d'environnement
|
||||
depuis un fichier :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
docker container run --env-file secrets.env myapp
|
||||
```
|
||||
</div>
|
||||
|
||||
Le fichier `secrets.env` contiendrait :
|
||||
|
||||
<div lang="en-US">
|
||||
```
|
||||
DB_PASSWORD=monmotdepasse
|
||||
API_KEY=ma_clef_secrete
|
||||
```
|
||||
</div>
|
||||
|
||||
::::: {.warning}
|
||||
|
||||
Pensez à ajouter ce fichier dans votre `.gitignore` ou `.dockerignore` pour éviter qu'il se retrouve accidentellement dans la nature !
|
||||
|
||||
:::::
|
||||
|
||||
|
||||
## Montage de fichiers secrets
|
||||
|
||||
Une approche plus sécurisée consiste à monter les secrets comme fichiers en
|
||||
lecture seule dans le conteneur :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
docker container run \
|
||||
--mount type=bind,source=$HOME/.secrets/db_password,target=/run/secrets/db_password,readonly \
|
||||
myapp
|
||||
```
|
||||
</div>
|
||||
|
||||
L'application peut ensuite lire le secret depuis `/run/secrets/db_password`.
|
||||
Cette approche présente plusieurs avantages :
|
||||
|
||||
- Les secrets ne sont pas visibles via `docker inspect`
|
||||
- Les secrets peuvent avoir des permissions restrictives sur l'hôte
|
||||
- Les applications peuvent être conçues pour lire les secrets depuis des
|
||||
fichiers, ce qui est compatible avec les systèmes d'orchestration
|
||||
|
||||
|
||||
## Docker Secrets (Docker Swarm)
|
||||
|
||||
Docker propose un système de gestion de secrets natif, mais celui-ci n'est
|
||||
disponible qu'en mode Swarm.
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
# Création d'un secret (nécessite Swarm mode)
|
||||
echo "monmotdepasse" | docker secret create db_password -
|
||||
|
||||
# Utilisation dans un service
|
||||
docker service create --secret db_password myapp
|
||||
```
|
||||
</div>
|
||||
|
||||
Dans ce mode, les secrets sont :
|
||||
|
||||
- Chiffrés pendant le transit et au repos
|
||||
- Montés dans `/run/secrets/` sous forme de fichiers temporaires en RAM
|
||||
- Distribués uniquement aux conteneurs qui en ont besoin
|
||||
- Gérés de manière centralisée
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
Stockage de données applicatives
|
||||
================================
|
||||
|
||||
Il est généralement toujours possible d'écrire dans le système de fichier de
|
||||
Il est généralement toujours possible d'écrire dans le système de fichiers de
|
||||
notre conteneur. (Cela n'affecte pas l'image, chaque conteneur est démarré à
|
||||
partir de l'image originale.) Cependant, il n'est pas recommandé de chercher à
|
||||
stocker des données ainsi. En effet, il n'est pas aisé de récupérer ces données
|
||||
une fois l'exécution du conteneur terminée ; les données peuvent même être
|
||||
détruite si on a lancé le conteneur avec l'option `--rm`.
|
||||
détruites si on a lancé le conteneur avec l'option `--rm`.
|
||||
|
||||
Docker met donc à notre disposition plusieurs mécanismes pour que les données
|
||||
de nos applications persistent et soient prêtes à être migrées d'un conteneur à
|
||||
|
|
@ -41,6 +41,31 @@ exemple : <http://10.42.12.23/dQw4w9WgXcQ.mp4>
|
|||
|
||||
:::::
|
||||
|
||||
::::: {.question}
|
||||
|
||||
Pour activer le listing de répertoires avec nginx, il faudrait créer un fichier
|
||||
de configuration personnalisé avec la directive `autoindex on;` et le monter
|
||||
dans le conteneur via un volume supplémentaire.
|
||||
|
||||
:::::
|
||||
|
||||
::::: {.question}
|
||||
|
||||
#### Syntaxes `-v` et `--mount` {-}
|
||||
|
||||
Vous remarquerez dans cette partie l'utilisation de deux syntaxes différentes
|
||||
pour monter des volumes :
|
||||
|
||||
- **`-v` ou `--volume`** : syntaxe courte et concise
|
||||
(ex: `-v ~/Downloads:/usr/share/nginx/html:ro`)
|
||||
- **`--mount`** : syntaxe explicite avec des paires clé-valeur
|
||||
(ex: `--mount type=bind,source=...,target=...,readonly`)
|
||||
|
||||
La syntaxe `--mount` est recommandée pour sa clarté, mais `-v` reste très
|
||||
utilisée car plus courte. Les deux sont équivalentes en fonctionnalité.
|
||||
|
||||
:::::
|
||||
|
||||
## Les volumes
|
||||
|
||||
Les volumes sont des espaces créés via Docker (il s'agit d'objets Docker). Ils
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ Composition de Docker
|
|||
|
||||
Docker est une suite d'outils de haut niveau, permettant d'utiliser des
|
||||
*conteneurs*. Le projet en lui-même utilise de nombreuses dépendances,
|
||||
originellement développées par l'entreprise Docker Inc., puis laissé dans le
|
||||
originellement développées par l'entreprise Docker Inc., puis laissées dans le
|
||||
domaine public lors des efforts de standardisation en 2015.
|
||||
|
||||
Commençons par planter le décor, en détaillant les principes de base de Docker.
|
||||
|
||||
### Séparation des compétences
|
||||
|
||||
Le projet s'article autour d'un daemon lancé au démarrage de la machine, avec
|
||||
Le projet s'articule autour d'un daemon lancé au démarrage de la machine, avec
|
||||
lequel on interagit via un client (le programme `docker`). La communication
|
||||
entre le daemon et le client s'effectuant sur une API REST généralement au
|
||||
travers d'une socket.
|
||||
|
|
@ -67,13 +67,13 @@ défaut.
|
|||
### Les plugins Docker
|
||||
|
||||
L'architecture de Docker est devenue très modulable. Le projet est parti dans
|
||||
de nombreuses directions, chacun voulant tirer la couverture vers soit, et
|
||||
de nombreuses directions, chacun voulant tirer la couverture vers soi, et
|
||||
l'équipe maintenant le projet a parfois eu du mal à arbitrer les bonnes choses
|
||||
à ajouter ou non au projet.
|
||||
|
||||
Afin de palier aux besoins complémentaires, parfois accessoires, parfois
|
||||
Afin de pallier les besoins complémentaires, parfois accessoires, parfois
|
||||
salvateurs, un système de plugins a été intégré. Il permet d'appeler d'autres
|
||||
programmes comme s'il s'agissait de composant de Docker.
|
||||
programmes comme s'il s'agissait de composants de Docker.
|
||||
|
||||
Certains plugins ajoutent des options à la ligne de commande (`docker-compose`,
|
||||
`docker-scan`, `docker-buildx` ...). D'autres ajoutent des typologies de
|
||||
|
|
@ -85,7 +85,7 @@ utilisateurs de notre machine ou dans `$HOME/.docker/` si l'on veut l'installer
|
|||
seulement pour nous.
|
||||
|
||||
Par exemple, les plugins ajoutant des commandes iront dans
|
||||
`$HOME/.docker/cli-plugins`. Par exemple, si l'on souhaite pouvoir disposer de
|
||||
`$HOME/.docker/cli-plugins`. Si l'on souhaite pouvoir disposer de
|
||||
la commande `docker compose`, on téléchargera le plugin vers l'emplacement :
|
||||
`$HOME/.docker/cli-plugins/docker-compose`.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Un outil complet pour indexer et chercher des vulnérabilités est
|
||||
[`Clair`](https://github.com/coreos/clair/), du projet CoreOS. À partir des
|
||||
informations mises à disposition par les équipes de sécurités des principales
|
||||
informations mises à disposition par les équipes de sécurité des principales
|
||||
distributions, cela alimente en continu une base de données qui sera accédé au
|
||||
moment de l'analyse.
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 16)
|
|||
|
||||
Parmi les volumes partagés avec `clair`, il y a un dossier
|
||||
`./clair_config`. Notez le `./` au début, qui indique que le dossier sera
|
||||
recherché relativement par rapport à l'emplacement du `docker-compsose.yml`.
|
||||
recherché relativement par rapport à l'emplacement du `docker-compose.yml`.
|
||||
|
||||
Dans ce dossier, vous devez placer un exemplaire du fichier de configuration
|
||||
dont un [exemple se trouve dans le dépôt du
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Open Container Initiative
|
|||
=========================
|
||||
|
||||
Formée en juin 2015, l'Open Container Initiative (OCI) a pour but d'établir le
|
||||
standard commun aux programmes de contenerisation, afin d'éviter une
|
||||
standard commun aux programmes de conteneurisation, afin d'éviter une
|
||||
fragmentation de l'écosystème.
|
||||
|
||||
## Spécifications
|
||||
|
|
@ -101,7 +101,7 @@ est utilisé pour sélectionner le bon manifest correspondant au système.
|
|||
|
||||
Le format des [couches de système de
|
||||
fichiers](https://github.com/opencontainers/image-spec/blob/master/layer.md)
|
||||
sont spécifiées : il est nécessaire de passer par des formats standards (comme
|
||||
est spécifié : il est nécessaire de passer par des formats standards (comme
|
||||
les tarballs), contenant éventuellement des fichiers et dossiers spéciaux
|
||||
représentant les modifications ou les suppressions éventuelles de la couche.
|
||||
|
||||
|
|
@ -125,5 +125,5 @@ Cela permet de récupérer des images, mais aussi d'en envoyer, en gérant
|
|||
|
||||
\
|
||||
|
||||
Nous allons voir plus en détails, dans les chapitres suivantes, ce que l'on
|
||||
Nous allons voir plus en détails, dans les chapitres suivants, ce que l'on
|
||||
peut tirer de ces spécifications, en décortiquant des usages précis.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ de nouveaux services.
|
|||
Dans les sections à venir, nous allons essayer de récupérer la configuration et
|
||||
les couches de quelques images courantes (Debian, Ubuntu, `hello-world`, ...) :
|
||||
dans un premier temps en nous préoccupant simplement de la couche la plus basse
|
||||
(le système de baase). Puis nous verrons dans le chapitre suivant comment gérer
|
||||
(le système de base). Puis nous verrons dans le chapitre suivant comment gérer
|
||||
les autres couches.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -135,9 +135,12 @@ et le dossier courant par défaut (`cwd`). Pensez également à faire un volume
|
|||
entre un dossier de votre home (ou temporaire, peu importe), afin de pouvoir
|
||||
stocker les photos (dossier `/srv/images`)[^chmod].
|
||||
|
||||
[^chmod]: faites attention aux droits du dossier que vous partagez. Le plus
|
||||
[^chmod]: **Attention aux droits du dossier que vous partagez.** Le plus
|
||||
simple pour l'instant serait d'attribuer les permissions `0777` à la
|
||||
source, temporairement.
|
||||
source, temporairement. **AVERTISSEMENT :** les permissions `0777` donnent
|
||||
un accès complet (lecture, écriture, exécution) à tous les utilisateurs du
|
||||
système. **N'utilisez JAMAIS ces permissions en production** et pensez à les
|
||||
révoquer après l'exercice (`chmod 755` ou `chmod 700` selon vos besoins).
|
||||
|
||||
Pour cette étape, considérez que vous avez réussi si vous voyez s'afficher :
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,41 @@
|
|||
Mise en place
|
||||
=============
|
||||
|
||||
* `docker-compose`
|
||||
* `venv` (Python3)
|
||||
* `jq`
|
||||
* `runc`
|
||||
* `containerd`
|
||||
* `ctr`
|
||||
* `linuxkit`
|
||||
Les exercices suivants nécessiteront l'installation de plusieurs outils.
|
||||
|
||||
- `docker-compose`
|
||||
- `venv`
|
||||
- `jq` : un outil en ligne de commande pour manipuler du JSON.
|
||||
- `runc` : *runtime* de bas niveau pour les conteneurs OCI.
|
||||
- `containerd` : daemon de gestion de conteneurs.
|
||||
- `ctr` : client en ligne de commande pour `containerd` (généralement installé avec `containerd`).
|
||||
|
||||
## LinuxKit {-}
|
||||
|
||||
Un outil pour construire des systèmes Linux minimaux et sécurisés.
|
||||
|
||||
**Installation :**
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
wget https://github.com/linuxkit/linuxkit/releases/latest/download/linuxkit-linux-amd64
|
||||
sudo install -m 755 linuxkit-linux-amd64 /usr/local/bin/linuxkit
|
||||
```
|
||||
</div>
|
||||
|
||||
### QEMU {-}
|
||||
|
||||
Nécessaire pour tester les images LinuxKit.
|
||||
|
||||
**Debian/Ubuntu :**
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
sudo apt-get install qemu-system-x86
|
||||
```
|
||||
</div>
|
||||
|
||||
**Arch Linux :**
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
sudo pacman -S qemu-full
|
||||
```
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ exécution. Puis, lorsqu'il aura terminé, celui-ci sera passé dans un statut
|
|||
|
||||
Parmi ses attributions, `init`, le PID 1 de notre système, est le processus qui
|
||||
récupère les processus orphelins du système. Lorsque le parent direct d'un
|
||||
processus meurt, ses fils sont reparenté sous le processus `init` et ils
|
||||
processus meurt, ses fils sont reparentés sous le processus `init` et ils
|
||||
obtiennent alors comme `ppid` 1. Ils ne conservent pas le PID de leur défunt
|
||||
parent.
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ l'argument `PR_SET_CHILD_SUBREAPER`.
|
|||
:::::
|
||||
|
||||
Docker procure une isolation, notamment au travers du *namespace* PID : les
|
||||
processus faisant parti du même *namespace* ne voient seulement qu'une partie
|
||||
processus faisant partie du même *namespace* ne voient seulement qu'une partie
|
||||
de l'arbre de processus de l'hôte, et notamment, un PID 1 est recréé, il s'agit
|
||||
du premier processus à s'exécuter dans le *namespace*.
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ shell.
|
|||
|
||||
Voici donc une raison supplémentaire de préférer `tini` à `bash` (ou à rien du
|
||||
tout). D'autant plus qu'à moins d'avoir préparé la fin d'exécution, `bash` ne
|
||||
retournera pas le code d'erreur de la commande que l'on a lancé, mais plutôt 0.
|
||||
retournera pas le code d'erreur de la commande que l'on a lancée, mais plutôt 0.
|
||||
|
||||
|
||||
## Intégration dans les `Dockerfile`
|
||||
|
|
@ -121,7 +121,7 @@ semble particulièrement indiqué.
|
|||
L'utilisation par le paramètre `--init` du `run` n'est pas recommandée et
|
||||
devrait se limiter aux cas où l'image a été construite par quelqu'un qui
|
||||
n'avait pas en tête ces contraintes. Lorsque l'on sait que des zombies ne vont
|
||||
pas être géré par leurs parents, le mainteneur se doit d'ajouter `tini` dans
|
||||
pas être gérés par leurs parents, le mainteneur se doit d'ajouter `tini` dans
|
||||
son `Dockerfile`. La méthode recommandée est de l'installer par les paquets de
|
||||
la distribution (`apt-get install tini`, `apk add tini`, ...). Néanmoins, dans
|
||||
le cas d'une distribution qui ne possèderait pas le paquet, il convient
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ the selected base image
|
|||
</div>
|
||||
|
||||
Ce dernier exemple est sans appel : `mysql` est une image officielle, et sa
|
||||
dernière version à l'écriture de ses lignes contient pas moins de 24
|
||||
dernière version à l'écriture de ces lignes contient pas moins de 24
|
||||
vulnérabilités dont 9 *high* (pourtant corrigées dans des versions suivantes).
|
||||
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
|||
```
|
||||
</div>
|
||||
|
||||
Nous pouvons remarque que Trivy, en plus de faire l'analyse statique des
|
||||
Nous pouvons remarquer que Trivy, en plus de faire l'analyse statique des
|
||||
vulnérabilités de l'image, a aussi fait une analyse des dépendances du binaire
|
||||
`/srv/youp0m`.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pour un projet.
|
|||
|
||||
Ainsi, il est possible de provisionner et gérer des machines hôtes sur les
|
||||
plates-formes de cloud habituelles. C'est également ce projet qui est à la base
|
||||
de *Docker Dektop*, en permettant de lancer via, respectivement, VirtualBox ou
|
||||
de *Docker Desktop*, en permettant de lancer via, respectivement, VirtualBox ou
|
||||
Hyper-V, un environnement Linux prêt à être utilisé avec Docker.
|
||||
|
||||
### Par la distribution binaire
|
||||
|
|
@ -33,7 +33,7 @@ chmod +x /usr/bin/docker-machine
|
|||
|
||||
### Support de KVM
|
||||
|
||||
Le programme support de base de nombreux environnement, dont VirtualBox et
|
||||
Le programme supporte de base de nombreux environnements, dont VirtualBox et
|
||||
Hyper-V. Bien d'autres environnements peuvent être supportés, au moyen de
|
||||
plug-ins.
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ iyue3rgd0ohs myWebS replicated 1/1 nginx:latest
|
|||
</div>
|
||||
|
||||
Vous pouvez constater que sur l'un des nœuds, sur lequel votre serveur aura été
|
||||
déployé, le tâche apparaît dans la liste des conteneurs !
|
||||
déployé, la tâche apparaît dans la liste des conteneurs !
|
||||
|
||||
|
||||
Rien de très excitant pour le moment, car nous ne pouvons pas vraiment accéder
|
||||
|
|
@ -86,8 +86,8 @@ particulièrement.
|
|||
Essayons de nous connecter aux ports 80 des deux IP correspondant à nos deux
|
||||
nœuds. Vous devriez voir la même page.
|
||||
|
||||
Lorsque plusieurs tâches s'exécutent pour ce service, le nœud d'entrée choisi
|
||||
selon un round-robin à quelle tâche il va diriger la requête. C'est grâce à ce
|
||||
Lorsque plusieurs tâches s'exécutent pour ce service, le nœud d'entrée choisit
|
||||
selon un round-robin vers quelle tâche il va diriger la requête. C'est grâce à ce
|
||||
mécanisme qu'il est possible de faire de la répartition de charge très
|
||||
simplement.
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ consulter la documentation à ce sujet :\
|
|||
|
||||
On parle depuis tout à l'heure de lancer plusieurs tâches pour le même
|
||||
service. La mise à l'échelle, c'est ça : exécuter plusieurs conteneurs pour la
|
||||
même tâche afin de mieux répartir la charge, idéalement sur des machines
|
||||
même service afin de mieux répartir la charge, idéalement sur des machines
|
||||
physiques différentes.
|
||||
|
||||
Ce qui se fait souvent avec beaucoup de douleur hors de Docker, se résume ici
|
||||
|
|
@ -156,22 +156,21 @@ version: '3'
|
|||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
|
||||
deploy:
|
||||
replicas: 6
|
||||
update_config:
|
||||
parallelism: 2
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
resources:
|
||||
memory: 50M
|
||||
deploy:
|
||||
replicas: 6
|
||||
update_config:
|
||||
parallelism: 2
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
resources:
|
||||
memory: 50M
|
||||
```
|
||||
</div>
|
||||
|
||||
Certaines informations comme les ressources, permettent à l'orchestrateur de
|
||||
mieux choisir le *workers* de destination, en fonction de certaines de ses
|
||||
mieux choisir le *worker* de destination, en fonction de certaines de ses
|
||||
caractéristiques.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Regroupés au sein d'un cluster (on parle de *swarm* pour Docker), chaque
|
|||
*Docker engine* représente un nœud (*node*) dans lequel on va déployer des
|
||||
*services*.
|
||||
|
||||
Certain nœuds ont un rôle de *manager*, parmi ceux-ci, un seul est élu leader et
|
||||
Certains nœuds ont un rôle de *manager*, parmi ceux-ci, un seul est élu leader et
|
||||
prendra les décisions d'orchestration. Les autres sont là pour prendre le
|
||||
relais en cas de dysfonctionnement sur le manager élu.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,11 +21,22 @@ peut-être déjà le plugin installé. Si vous n'avez pas d'erreur en exécutant
|
|||
vous pouvez l'installer comme ceci :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
# Pour Ubuntu/Debian
|
||||
sudo apt-get install docker-buildx-plugin
|
||||
|
||||
# Pour Fedora/RHEL
|
||||
sudo dnf install docker-buildx-plugin
|
||||
```
|
||||
V="v0.9.1"
|
||||
|
||||
Alternativement, vous pouvez l'installer manuellement depuis GitHub :
|
||||
|
||||
```bash
|
||||
# Récupérer la dernière version depuis GitHub
|
||||
BUILDX_VERSION=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -L -s -S -o ~/.docker/cli-plugins/docker-buildx \
|
||||
https://github.com/docker/buildx/releases/download/$V/buildx-$V.linux-amd64
|
||||
https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64
|
||||
chmod +x ~/.docker/cli-plugins/docker-buildx
|
||||
```
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ des instructions similaires à cela :
|
|||
|
||||
<div lang="en-US">
|
||||
```dockerfile
|
||||
FROM golang:1.18
|
||||
FROM golang:1
|
||||
COPY . /go/src/git.nemunai.re/youp0m
|
||||
WORKDIR /go/src/git.nemunai.re/youp0m
|
||||
RUN go build -tags dev -v
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ COPY docker-entrypoint.d /docker-entrypoint.d
|
|||
```
|
||||
</div>
|
||||
|
||||
Le dossier sera créé s'il n'existe pas, et le contenu du dossier source ser
|
||||
Le dossier sera créé s'il n'existe pas, et le contenu du dossier source sera
|
||||
recopié.
|
||||
|
||||
:::::
|
||||
|
|
@ -292,7 +292,7 @@ Par exemple, prenons le `Dockerfile` suivait :
|
|||
|
||||
<div lang="en-US">
|
||||
```Dockerfile
|
||||
FROM python:3.10
|
||||
FROM python:3
|
||||
COPY build /usr/lib/python/grapher
|
||||
EXPOSE 8080
|
||||
RUN pip install pillow pygal
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Les `type`s que vous pouvez découvrir sont ceux que l'on a vu à la
|
|||
section précédente : `node`, `pod`, ...
|
||||
|
||||
La commande `describe` permet d'afficher l'état tel qu'il est attendu
|
||||
et tel qu'il est actuellement (cela permet de se rendre lorsque les
|
||||
et tel qu'il est actuellement (cela permet de se rendre compte lorsque les
|
||||
deux divergent).
|
||||
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ kindnet-2gwlp 1/1 Running 0 13m
|
|||
```
|
||||
|
||||
Eh oui ! De nombreux services de base de Kubernetes tournent dans des
|
||||
conteneurs... qu'il gèren lui-même ! Notamment :
|
||||
conteneurs... qu'il gère lui-même ! Notamment :
|
||||
|
||||
- `etcd` : notre base de données clef/valeur,
|
||||
- `kube-apiserver` : l'API REST avec qui communique `kubectl`,
|
||||
|
|
@ -166,7 +166,7 @@ kubectl logs -f pingpong
|
|||
Notez ici l'option -f qui permet de suivre les logs en direct.
|
||||
|
||||
|
||||
Notre premier test ayant réussi, nous pouvons arrêter de DDos Cloudflare :
|
||||
Notre premier test ayant réussi, nous pouvons arrêter de DDoS Cloudflare :
|
||||
|
||||
```bash
|
||||
kubectl delete pods pingpong
|
||||
|
|
@ -211,19 +211,19 @@ haut niveau et sont là pour s'assurer que les migrations se font en douceur :
|
|||
elles vont permettre de basculer progressivement les *pod*s d'une version X à une
|
||||
version Y (par exemple si l'on change notre ping d'alpine 3.16 vers alpine
|
||||
edge), mais éventuellement de revenir sur la version X si besoin, en cours de
|
||||
migration. Elles délèguent ensuite aux *replicatsets* la gestion des *pod*s.
|
||||
migration. Elles délèguent ensuite aux *replicasets* la gestion des *pod*s.
|
||||
|
||||
Le *replicatset* est là pour indiquer le nombre de *pod*s que l'on désire et
|
||||
Le *replicaset* est là pour indiquer le nombre de *pod*s que l'on désire et
|
||||
s'assurer que le nombre de *pod*s actuellement lancé est bien en adéquation avec
|
||||
le nombre de *pod*s attendu.
|
||||
\
|
||||
|
||||
Pour résumer : `kubectl` a créé une tâche de déploiement
|
||||
`deploy/pingpong`. Cette tâche de déploiement a créé elle-même un *replicatset*
|
||||
`rs/pingpong-xxxx`. Ce *replicatset* a créé un *pod* `po/pingpong-yyyy`.
|
||||
`deploy/pingpong`. Cette tâche de déploiement a créé elle-même un *replicaset*
|
||||
`rs/pingpong-xxxx`. Ce *replicaset* a créé un *pod* `po/pingpong-yyyy`.
|
||||
|
||||
|
||||
#### Pasage à l'échelle : facile ?
|
||||
#### Passage à l'échelle : facile ?
|
||||
|
||||
Pour lancer 3 `ping`s en parallèle, modifions la tâche de déploiement comme suit :
|
||||
|
||||
|
|
@ -232,8 +232,8 @@ kubectl scale deploy/pingpong --replicas 3
|
|||
```
|
||||
|
||||
À ce stade, comme nous ne modifions que le nombre de replicats, Kubernetes va
|
||||
tout simplement propager ce nombre au *replicatset* existant. Puis, le
|
||||
*replicatset* voyant un décalage entre le nombre de *pod*s attendus et le nombre
|
||||
tout simplement propager ce nombre au *replicaset* existant. Puis, le
|
||||
*replicaset* voyant un décalage entre le nombre de *pod*s attendus et le nombre
|
||||
de *pod*s en cours d'exécution, il va en lancer de nouveaux, afin de répondre à
|
||||
la demande.
|
||||
\
|
||||
|
|
@ -245,11 +245,11 @@ kubectl delete pod pingpong-yyyy-zzz
|
|||
```
|
||||
|
||||
Cela supprime bien un *pod*, mais un autre est relancé instantanément car le
|
||||
*replicatset* constate une différence dans le nombre attendu.
|
||||
*replicaset* constate une différence dans le nombre attendu.
|
||||
|
||||
Si nous voulons arrêter de DDoS Google/Cloudflare, il ne s'agit pas de tuer
|
||||
chacun des *pod*s un par un, car de nouveaux seraient créés par le
|
||||
*replicatset*. Si l'on supprime le *replicatset*, la tâche de déploiement en
|
||||
*replicaset*. Si l'on supprime le *replicaset*, la tâche de déploiement en
|
||||
recréera un similaire (avec de nouveaux *pod*s).
|
||||
|
||||
Pour arrêter nos conteneurs, il convient donc de supprimer la tâche de
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Vue d'ensemble de Kubernetes
|
|||
*Kubernetes* (prononcé Ku-ber-né-tice[^prononciation-k8s] en grec) est un
|
||||
système *open source* d'orchestration et de gestion de conteneurs. C'est-à-dire
|
||||
qu'il se charge de faire coller constamment la liste des conteneurs qu'il voit
|
||||
vivant aux spécifications qu'on lui aura demandées.
|
||||
vivants aux spécifications qu'on lui aura demandées.
|
||||
|
||||
[^prononciation-k8s]: <https://github.com/kubernetes/kubernetes/issues/44308>
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ d’une série de nœuds *workers*.
|
|||
|
||||
Le(s) *control-plane(s)* sont en charge de prendre des décisions globales sur
|
||||
le déroulement des opérations du cluster : cela va de la détection d'anomalies
|
||||
sur le cluster ou encore le traiter d'événements et l'organisation de leur
|
||||
sur le cluster ou encore le traitement d'événements et l'organisation de leur
|
||||
réponse, ...
|
||||
|
||||
On retrouve sur ces nœuds centraux les composants suivants :
|
||||
|
|
@ -52,7 +52,7 @@ L'ordonnanceur
|
|||
disponibles.
|
||||
|
||||
Les contrôleurs
|
||||
: Ils vont contrôler l'état des différents composants déployées au sein du
|
||||
: Ils vont contrôler l'état des différents composants déployés au sein du
|
||||
cluster, pour s'assurer d'être dans l'état désiré. Il y a en fait plusieurs
|
||||
contrôleurs ayant chacun la responsabilité de veiller sur une partie des
|
||||
objets, ainsi que le `cloud-controller-manager` lorsque le cluster se trouve
|
||||
|
|
@ -65,11 +65,11 @@ Les contrôleurs
|
|||
\
|
||||
|
||||
|
||||
Chaque nœud (généralement, le nœud *master* est également *worker*) est utilisé
|
||||
Chaque nœud (généralement, le nœud *control-plane* est également *worker*) est utilisé
|
||||
via deux composants :
|
||||
|
||||
`kubelet`
|
||||
: C'est l'agent qui va se charger de créer les conteneurs et les manager, afin
|
||||
: C'est l'agent qui va se charger de créer les conteneurs et les gérer, afin
|
||||
de répondre aux spécifications demandées par les *control-planes*.
|
||||
|
||||
`kube-proxy`
|
||||
|
|
@ -109,14 +109,14 @@ supprimer ou de vérifier qu'une interface va bien.
|
|||
|
||||
Ainsi, à la création d'un conteneur, Kubernetes va laisser aux plugins CNI le
|
||||
loisir d'ajouter les interfaces réseaux adéquates, d'allouer l'adresse IP, de
|
||||
configurer les routes, les règles de pare-feu, ... quelque soit
|
||||
configurer les routes, les règles de pare-feu, ... quelle que soit
|
||||
l'infrastructure et la complexité du réseau utilisé derrière.
|
||||
\
|
||||
|
||||
Terminons en ajoutant qu'un serveur DNS faisant autorité est nécessaire pour
|
||||
que, de la même manière que Docker, il soit possible d'accéder aux autres
|
||||
conteneurs via leur nom (sans qu'il ne soit nécessaire de le déclarer sur un
|
||||
serveur de noms public). Il n'y a pas de projet de porté par Kubernetes pour
|
||||
serveur de noms public). Il n'y a pas de projet porté par Kubernetes pour
|
||||
cela, mais cette tâche est généralement assurée par
|
||||
[CoreDNS](https://coredns.io/).
|
||||
|
||||
|
|
|
|||
|
|
@ -39,41 +39,41 @@ metadata:
|
|||
name: fluentd-elasticsearch
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: fluentd-logging
|
||||
k8s-app: fluentd-logging
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
name: fluentd-elasticsearch
|
||||
matchLabels:
|
||||
name: fluentd-elasticsearch
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: fluentd-elasticsearch
|
||||
spec:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: fluentd-elasticsearch
|
||||
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
- name: varlibdockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: varlibdockercontainers
|
||||
hostPath:
|
||||
metadata:
|
||||
labels:
|
||||
name: fluentd-elasticsearch
|
||||
spec:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: fluentd-elasticsearch
|
||||
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
- name: varlibdockercontainers
|
||||
mountPath: /var/lib/docker/containers
|
||||
readOnly: true
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
- name: varlibdockercontainers
|
||||
hostPath:
|
||||
path: /var/lib/docker/containers
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ Après avoir appliqué la nouvelle spec, on constate qu'il y a beaucoup de *pod*
|
|||
toujours là.
|
||||
|
||||
|
||||
##### Botleneck résolu ? {-}
|
||||
##### Bottleneck résolu ? {-}
|
||||
|
||||
Admirez maintenant dans Chronograf si vous avez réussi à augmenter votre nombre
|
||||
de pépites !
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ leurs infrastructures, choisissent de passer par un prestataire. L'entreprise
|
|||
délègue donc la gestion de son/ses cluster(s) à une autre entreprise, dont
|
||||
c'est le cœur de métier. La plupart du temps, il va s'agir d'Amazon (via
|
||||
[Elastic Kubernetes Service](https://aws.amazon.com/fr/eks/)), d'Azure
|
||||
[Kubernetes
|
||||
([Kubernetes
|
||||
Service](https://azure.microsoft.com/fr-fr/services/kubernetes-service/)) ou
|
||||
Google ([Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)), mais
|
||||
d'autres acteurs plus petits existent aussi
|
||||
|
|
@ -38,7 +38,7 @@ fonctionne.\
|
|||
Chaque distribution de *Kubernetes* aura donc pris soin de mettre à disposition
|
||||
des composants pour un usage plus ou moins spécifique. En dehors des composants
|
||||
strictement nécessaires qui ne changent pas, on verra d'une distribution à
|
||||
l'autre des choix quant au moteur d'exécution de conteneurs (tantot CRI-O,
|
||||
l'autre des choix quant au moteur d'exécution de conteneurs (tantôt CRI-O,
|
||||
Containerd, KataContainers, ...), le réseau sera géré par une brique spécifique
|
||||
(Flannel, Calico, Canal, Wave, ...), le stockage peut également faire l'objet
|
||||
de choix.\
|
||||
|
|
@ -147,8 +147,9 @@ Server Version: Version.Info{Major:"1", Minor:"21", GitVersion:"v1.25.3", [...]
|
|||
```
|
||||
</div>
|
||||
|
||||
Par défaut, `kubectl` va tenter de contacter le port local 2375, `kind` aura
|
||||
pris soin de l'exposer pour vous au moment de la création du cluster.
|
||||
Par défaut, `kubectl` va utiliser le fichier `~/.kube/config` pour contacter
|
||||
l'API du cluster, `kind` aura pris soin de le configurer pour vous au moment
|
||||
de la création du cluster.
|
||||
|
||||
::::: {.warning}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ Avec le paquet `lxc` que nous avons précédemment installé, nous avons égalem
|
|||
récupéré un certain nombre de *modèles* de système (souvent installés dans le
|
||||
dossier `/usr/share/lxc/templates/`) : il s'agit d'une suite de commandes
|
||||
(principalement des `wget`, `chroot` ou `debootstrap`) permettant d'obtenir un
|
||||
système basic fonctionnel, en suivant les étapes d'installation habituelle de
|
||||
la distribution.
|
||||
système de base fonctionnel, en suivant les étapes d'installation habituelles
|
||||
de la distribution.
|
||||
|
||||
La méthode la plus simple pour lancer un conteneur `lxc` est d'utiliser l'un de
|
||||
ces modèles pour obtenir un nouveau système. On utilise pour cela la commande
|
||||
|
|
@ -45,11 +45,11 @@ s'attendre à trouver dans n'importe quelle machine virtuelle (et même physique
|
|||
plus classique (la seule différence réside donc dans le fait que le noyau est
|
||||
partagé avec l'hôte).
|
||||
|
||||
Généralement on lance `lxc-start` avec l'option `--daemon`, car on n'a pas
|
||||
vraiment envie d'avoir un conteneur bloquant un terminal. En mode daemon, on va
|
||||
utiliser la commande `lxc-console` pour nous attacher aux conteneurs. À tout
|
||||
moment, nous pouvons nous détacher de la console (sans que cela n'affecte
|
||||
l'état du conteneur) en pressant les touches : `^A q`.
|
||||
Généralement on lance `lxc-start` avec l'option `--daemon`, car sans cette
|
||||
option le conteneur monopolise le terminal. En mode daemon, on utilise la
|
||||
commande `lxc-console` pour s'attacher aux conteneurs. À tout moment, nous
|
||||
pouvons nous détacher de la console (sans que cela n'affecte l'état du
|
||||
conteneur) en pressant les touches : `^A q`.
|
||||
|
||||
Connectons-nous, lançons quelques commandes puis éteignons la machine en
|
||||
lançant la commande `poweroff` dans le conteneur. Il est également possible de
|
||||
|
|
@ -122,8 +122,9 @@ veth, avec un côté placé dans la machine hôte et l'autre côté placé dans
|
|||
conteneur. `lxc` configure l'interface dans le conteneur, il nous appartient
|
||||
ensuite de configurer la machine hôte.
|
||||
|
||||
Commençons par attribuer une IP à cette nouvelle interface, en adaptant à votre
|
||||
identifiant d'interface :
|
||||
Commençons par attribuer une IP à cette nouvelle interface. L'identifiant de
|
||||
l'interface veth créée côté hôte peut être trouvé avec la commande `ip link`.
|
||||
Adaptez l'exemple suivant avec votre identifiant d'interface :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
|
@ -136,10 +137,10 @@ machine hôte : `ping 172.23.42.2`.
|
|||
|
||||
Notre conteneur ne peut cependant pas encore accéder à Internet. Pour cela, la
|
||||
machine hôte doit faire office de routeur et donc router les paquets d'un
|
||||
réseau à l'autre : en l'occurence, du réseau 172.23.42.1 vers Internet
|
||||
réseau à l'autre : en l'occurrence, du réseau 172.23.42.0/24 vers Internet
|
||||
via 10.0.0.0/8, le réseau de l'école.
|
||||
|
||||
Pour que notre machine hôte route les paquets, exécuter la commande :
|
||||
Pour que notre machine hôte route les paquets, exécutez la commande :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
|
@ -151,9 +152,9 @@ Cette variable, que nous retrouvons dans `/proc/sys/net/ipv4/ip_forward`,
|
|||
indique au noyau qu'il peut faire passer les paquets réseau d'une interface à
|
||||
l'autre. Sans plus de directives, les paquets vont conserver leur adresse
|
||||
source (172.23.42.2 pour les paquets en provenance du conteneur). Cette adresse
|
||||
est une adresse privée, non routable sur Internet, ni même par le bocal. Il
|
||||
faut donc ajouter une couche de NAT/PAT pour réécrire les adresses sources
|
||||
avant d'envoyer les paquets sur internet :
|
||||
est une adresse privée, non routable sur Internet. Il faut donc ajouter une
|
||||
couche de NAT/PAT pour réécrire les adresses sources avant d'envoyer les
|
||||
paquets sur Internet (en adaptant le nom de l'interface veth) :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
|
@ -162,7 +163,7 @@ iptables -t nat -A POSTROUTING ! -o vethYJWD6R -s 172.23.42.0/24 -j MASQUERADE
|
|||
</div>
|
||||
|
||||
Dernière étape, dans notre conteneur, nous devons indiquer la route à utiliser
|
||||
pour accéder à internet :
|
||||
pour accéder à Internet :
|
||||
|
||||
<div lang="en-US">
|
||||
```bash
|
||||
|
|
@ -170,7 +171,7 @@ ip route add default via 172.23.42.1
|
|||
```
|
||||
</div>
|
||||
|
||||
Nous avons maintenant internet dans notre conteneur !
|
||||
Nous avons maintenant Internet dans notre conteneur !
|
||||
|
||||
|
||||
## Utilisation du conteneur
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue