tuto5: add part on filesystems

This commit is contained in:
nemunaire 2022-11-15 22:34:03 +01:00
parent 75c35a36d1
commit d02f573142
8 changed files with 547 additions and 45 deletions

View File

@ -0,0 +1,36 @@
::::: {.exercice}
## Exercice {-}
Continuons notre script `registry_play` de la partie précédente afin d'être en
mesure d'extraire également les images en plusieurs couches.
<div lang="en-US">
```bash
42sh$ cd $(mktemp -d)
42sh$ ./registry_play library/python:latest
42sh$ tree -L 1
.
./lower0
./lower1
./lower2
./lower3
./lower4
./lower5
./lower6
./lower7
./lower8
./upper
./work
./rootfs
42sh# chroot rootfs /usr/bin/python3
Python 3.23.0 (main) on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> _
```
</div>
:::::

View File

@ -0,0 +1,10 @@
## Pour aller plus loin
* [Unioning file systems: Architecture, features, and design choices](https://lwn.net/Articles/324291/) :\
<https://lwn.net/Articles/324291/>
Des expériences sont en cours pour essayer de faire un format
d'archive pour les couches qui permettrait d'utiliser les conteneurs
sans avoir eu besoin préalablement d'avoir télécharger le contenu des
couches :
<https://github.com/containerd/stargz-snapshotter/blob/main/docs/estargz.md>

403
tutorial/4/filesystem.md Normal file
View File

@ -0,0 +1,403 @@
\newpage
Systèmes de fichiers et couches
===============================
Les images de conteneurs sont distribuées en couches, chaque couche contenant
les différences apportée par rapport à la couche précédente : l'ajout d'un
fichier, la suppression d'un dossier, ...
L'intérêt principal est bien entendu d'optimiser l'espace de stockage, en
favorisant la réutilisation des couches d'un conteneur à l'autre. Cela permet
en outre d'accélérer le processus de création des conteneurs, puisqu'il n'y a
pas besoin de commencer par recopier les fichiers de l'image avant de pouvoir
lancer le conteneur.
Pour réaliser ces actions, Docker dispose de plusieurs techniques, implémentées
sous forme de *storage drivers*. Beaucoup de ces *drivers* s'appuient sur des
mécanismes existant dans le noyau, mais la diversité des configurations impose
d'avoir plusieurs solutions pour ce problème. D'ailleurs si aucun mécanisme
n'est disponible, Docker utilise le driver `vfs`. Ce *driver* va alors
recopier, au moment du lancement d'un conteneur, chacune des couches ; la
méthode n'est alors pas très optimale, mais a le mérite d'exister
indépendamment des implémentations.
## Union de systèmes de fichiers
Le principe général de ce système de couches repose sur l'union de système de
fichiers : il s'agit de faire une combinaison logique de deux couches (ou plus,
selon l'implémentation), afin de créer un unique système de fichiers qui est la
combinaison de chaque couche.
### Historique
Les premières implémentations de ce type de systèmes de fichiers est apparu
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
modifier artificiellement le contenu du CD, notamment pour mettre à jour les
paquets, ou ajouter ses propres applications, documents, photos, ...
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 fût le premier OS à
développer cette technique dès 1986 (pour un système de fichier 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*.
### Usages
En dehors de l'exemple des LiveCD que l'on vient de décrire, les unions de
systèmes de fichiers trouvent leur intérêt également dans les systèmes
embarqués : on peut garder le système de base en lecture seule (entre deux
mises à jour du système) et rajouter une couche pour l'utilisateur en
lecture/écriture, ce qui donne la possibilité de faire facilement une
réinitialisation à la demande de l'utilisateur, ou en cas de corruption du
système de fichiers en écriture.
On trouve également usage de cette fonctionnalité pour réaliser des sauvegardes
en place des données.
### Généralités
Les unions de systèmes de fichiers partagent un certain nombre de concepts que
nous allons illustrer au travers du schéma suivant :
![Accès aux fichiers en fonction des couches](overlayfs.png){height=6cm}
On voit un système de fichiers à deux couches, on parle de deux *branches* dans
le jargon. Elles sont notées *Lower* pour la couche la plus basse et *upper* la
couche qui s'insère par dessus la couche *lower* ; et enfin *Merged* le
résultat. Certaines implémentations supportent plus que 2 branches, avec des
politiques d'accès et de modifications parfois complexes.
Lorsque l'on supprime un fichier de l'union, un fichier dit *whiteout file* est
placé dans la couche en écriture pour indiquer que ce fichier ne doit plus être
affiché dans la couche *merged*. Le même concept existe pour les dossiers, mais
on parle alors d'*opaque directory*.
Lorsqu'il s'agit d'accéder à un fichier présent dans la branche *lower* et qui
n'a pas été modifié dans *upper*, on accède directement au fichier de *lower*.
Lorsqu'un fichier est modifié, on recopie son contenu intégralement dans la
branche *upper*, depuis la branche *lower*. Un fichier qui est ajouté, écrasé
ou modifié aura donc son contenu intégralement dans la couche *upper*.
::::: {.question}
#### Pourrait-on se contenter d'un *Copy-on-Write* au niveau des blocs ? {-}
\
C'est en effet une solution qui existe (les *snapshots* LVM par exemple, que
Docker peut utiliser au travers du *driver* `device-mapper`). Dans ce cas,
seuls les blocs modifiés seront réécrits, cela peut sembler être une
alternative performante. Il faut noter cependant qu'outre les blocs liés au
fichier modifié, il faut également mettre à jour les métadonnées (*inodes*,
...).
Dans les scénarios d'écriture intensive, il s'avère que ce type de système perd
beaucoup en performance face à une union de système de fichiers.
Il est généralement admis également que le *Copy-on-Write* tend à occuper
davantage de place au fil du temps et des modifications, que l'union.
:::::
## OverlayFS
OverlayFS est arrivé dans le noyau 3.18, après de plus de 4 années de réécritures
et d'amélioration structurelles, pour atteindre le niveau d'exigence et sans
compromis nécessaire à son intégration dans le noyau officiel.
::::: {.question}
#### Quelles problématiques rendent l'implémentation d'une union de systèmes de fichier compliquée ? {-}
\
L'un des problèmes les plus délicats est de trouver une manière de représenter
les suppressions de fichiers et de dossiers : cela doit être un fichier valide
(avec ou sans métadonnée) car il faut pouvoir stocker l'information
concrètement. Dans de nombreuses implémentations, un fichier `.wh.<filename>`
sert de *whiteout file*, ce qui peut créer des conflits avec des fichiers de
l'utilisateur (ou réduire ses choix de noms de fichiers).
Un problème similaire s'applique aux dossiers : est-ce qu'il faut supprimer
chaque fichier contenu dans le dossier ou la simple présence d'un *opaque
directory* empêche toute découverte ?
L'usage de la mémoire peut vite devenir incontrôlable, surtout si
l'implémentation autorise beaucoup de branches, car si on veut que le système
soit performant il faudra avoir en mémoire les topologies de chaque système de
fichiers.
L'implémentation de `mmap(2)` est nécessairement un cauchemar : lorsqu'un
fichier est modifié par deux processus qui le `mmap(2)`, on s'attend
normalement à voir les modifications dans les deux processus, or le premier à
faire une modification a créer un nouveau fichier dans la branche accessible en
écriture. Il est ardu de réconcilier les pointeurs deux des processus.
D'une manière similaire, il faut penser à la gestion des *hard links* : tous
les pointeurs d'un contenu mis à jour devrait être modifié dans la couche en
écriture, cependant il n'y a pas d'index des pointeurs, il n'est donc pas
facile de retrouver les fichiers à mettre à jour.
Ajoutons aussi que les systèmes de fichiers sous-jacents de chacune des
branches n'ont pas forcément les mêmes contraintes (tailles des noms de
fichiers, attributs étendus, métadonnées, encodage des accents, ...) et qu'il
faut réussir à jongler entre chaque, tout en retournant des erreurs cohérentes
le cas échéant.
Et bien d'autres encore. Notamment `readdir(2)` qui doit être stable malgré
les turbulences qui pourraient arriver entre deux appels, ...
Voir cette série d'articles résumant les différentes implémentations, leurs
choix et différences : <https://lwn.net/Articles/325369/>,
<https://lwn.net/Articles/327738/>.
:::::
Afin de satisfaire les contraintes d'intégration au noyau, le minimum de
fonctionnalités ont été retenues : on ne peut notamment avoir qu'une seule
couche en écriture, qui se positionne nécessairement au sommet, en
superposition des autres. C'est de là que vient le nom du système de fichiers,
puisqu'il s'agit davantage d'une superposition (*overlay*) d'un système de
fichiers sur un autre, plutôt qu'une union de plusieurs systèmes aux politiques
d'écritures potentiellement plus variées.
### Utilisation
L'usage d'OverlayFS est plus complexe que la plupart des autres systèmes de
fichiers. Il faut bien évidemment indiquer le/les systèmes de fichiers à
utiliser comme branches basses, ainsi que l'éventuelle couche en
lecture/écriture, mais il faut aussi disposer d'un dossier de travail, qui
permettra à l'implémentation de préparer certaines actions qui nécessitent
d'être atomiques.
On peut réaliser une opération atomique en déplaçant un fichier préalablement
créé et rempli (plutôt qu'en le créant et en l'écrivant en place). Afin de
pouvoir satisfaire à l'atomicité, le répertoire *upper* et le dossier de
travail doivent être obligatoirement sur le même système de fichiers. Dans le
cas contraire, un appel à `rename(2)` retournerait `EXDEV` et l'opération ne
pourrait alors pas être atomique.
Voici un exemple général de création d'une union simple entre un système de
fichiers en lecture seule et un en lecture/écriture :
<div lang="en-US">
```
mount -t overlay -olowerdir=/lower,upperdir=/upper,workdir=/work ignored /merged
```
</div>
Le type à utiliser est `overlay`, avec les options `lowerdir` qui indique
l'emplacement du/des dossiers à combiner en lecture seule (on les sépare par
des `:` lorsqu'il y en a plusieurs), on indique également le répertoire
contenant le système en lecture/écriture dans l'option `upperdir`, et on il
faut pas oublier l'option `workdir` un chemin sur la même partition que
l'`upperdir`, qui doit être vide.
On termine l'appel par donner le périphérique source, qui est inutile dans
notre cas (`ignored` ou tout autre chaîne fera l'affaire), et enfin le dossier
vers lequel sera monté notre union : `/merged` dans l'exemple.
\
Analysons un conteneur Docker en cours d'exécution pour en apprendre davantage.
D'abord, on vérifie que l'on utilise bien le *storage driver* `overlay2` :
<div lang="en-US">
```
42sh$ docker info | grep "Storage Driver"
Storage Driver: overlay2
```
</div>
C'est le cas (en fonction de la configuration de votre noyau, Docker aura
peut-être choisi un *driver* différent), commençons donc l'analyse :
<div lang="en-US">
```
42sh$ docker container run --rm -it debian
incntr$ mount | grep "on / "
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/B62UNV3UB3X4TBWQMM6XCMM6W5:/var/lib/docker/overlay2/l/V6HGFN3C3PEW6CZ6XWRSHHDKJH,upperdir=/var/lib/docker/overlay2/2a353708e5b16ea7775cf1a33dd23ce31430faaa504bcde5508691b230f9d700/diff,workdir=/var/lib/docker/overlay2/2a353708e5b16ea7775cf1a33dd23ce31430faaa504bcde5508691b230f9d700/work)
```
</div>
On remarque que 2 `lowerdir` sont utilisés. Il s'agit de liens symboliques
pointant vers les dossiers identifiant les couches (les noms des liens sont
aléatoires, il s'agit en fait d'avoir un chemin raccourci par rapport au chemin
complet vers le système de fichiers de la couche, car le nombre de caractères
que l'on peut passer à l'appel système `mount(2)` est limité).
La branche la plus basse (le plus à droite du paramètre `lowerdir`) contient
l'unique couche de notre image `debian`, celle un peu plus à gauche superpose
un certain nombre de fichiers de configuration nécessaire à l'exécution du
conteneur (`/etc/hosts`, `resolv.conf`, ...).
La branche en lecture/écriture est également enregistrée dans le dossier
`/var/lib/docker/overlay2` et l'on peut voir son identifiant. L'`upperdir` se
trouve dans le dossier `diff`, tandis que le `workdir` est dans le dossier
`work`, sous le même identifiant de couche.
On peut également voir les dossiers utilisés en inspectant notre conteneur :
<div lang="en-US">
```
42sh$ docker container inspect youthful_wilbur | jq .[0].GraphDriver.Data
```
```json
{
"LowerDir": "/var/lib/docker/overlay2/22753d0d81...8706f1a31-init/diff:/var/lib/docker/overlay2/2cc3656c06...c0fb91d6/diff",
"MergedDir": "/var/lib/docker/overlay2/22753d0d81...8706f1a31/merged",
"UpperDir": "/var/lib/docker/overlay2/22753d0d81...8706f1a31/diff",
"WorkDir": "/var/lib/docker/overlay2/22753d0d81...8706f1a31/work"
}
```
</div>
Si on teste avec une image avec plus de couches, on obtient davantage de
`lowerdir`, un par couche. N'hésitez pas à faire la même série de commandes
avec l'image `python` par exemple.
### Ajout de fichiers
À ce stade, si nous regardons le contenu de notre dossier `upperdir`, nous
pouvons remarqué que celui-ci est vide. C'est normal puisque nous n'avons apporté
aucune modification.
Dans notre conteneur précédemment lancé, apportons une modification, en
ajoutant un fichier :
<div lang="en-US">
```
incntr$ echo "newfile" > /root/foobar
```
</div>
<div lang="en-US">
```
42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff
/var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff
└── root
└── foobar
```
</div>
Notre nouveau fichier, qui n'est pourtant pas le seul dans l'arborescence que
l'on voit dans le conteneur, a été ajouté comme on pouvait s'y attendre, dans
la branche en lecture/écriture.
### Modification de fichiers
Si nous apportons une modification à un fichier, par exemple en ajoutant une
ligne, ce n'est pas seulement la différence qui est stockée dans la branche en
écriture, mais bien tout le fichier, tel qu'il a été modifié :
<div lang="en-US">
```
incntr$ echo "Bienvenue dans le conteneur" >> /etc/issue
```
</div>
<div lang="en-US">
```
42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff
/var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff
└── etc
└── issue
```
</div>
<div lang="en-US">
```
42sh$ cat /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/etc/issue
Debian GNU/Linux 11 \n \l
Bienvenue dans le conteneur
```
</div>
### Suppression de fichiers
Lorsque l'on souhaite supprimer un fichier que l'on vient d'ajouter, il n'y a
pas grand chose à faire puisque supprimer ce fichier de la branche en écriture
fera bien disparaître le fichier de l'arborescence montée.
Lorsqu'il s'agit de supprimer un fichier présent dans une branche en lecture
seule, il faut réussir à faire en sorte de masquer ce fichier au moyen d'un
marqueur. En fonction du *storage driver*, ce marqueur est différent : dans
`OverlayFS`, une suppression est matérialisée par un fichier spécial de type
caractère du même nom.
<div lang="en-US">
```
incntr$ rm /etc/adduser.conf
```
</div>
<div lang="en-US">
```
42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff
/var/lib/docker/overlay2/1531651afa872006a4b2b9b913d5d8ee317cf12be7883517ba77f3d094f871b4/diff
└── etc
└── adduser.conf
```
</div>
<div lang="en-US">
```
42sh$ cat /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/etc/adduser.conf
cat: No such device or address
42sh$ stat /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/etc/adduser.conf
File: /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/etc/adduser.conf
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: fe0bh/65035d Inode: 515773 Links: 2 Device type: 0,0
```
</div>
Notons ici `Device type: 0,0`.
Pour créer nous-mêmes un fichier similaire, il faudrait utiliser :
<div lang="en-US">
```
42sh$ mkdir /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/bin
42sh$ mknod /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/bin/sh c 0 0
```
</div>
::::: {.warning}
Faire cette commande `mknod` alors que l'union de système de fichiers est
montée par ailleurs ne va pas faire disparaître le fichier `/bin/sh` car les
modifications qui pourraient être apportées aux branches en dehors du système
monté conduisent à des résultats explicitement indéfinis.
:::::
### Suppressions pour `unionfs` et AuFS
Le concept de *whiteout file*, comme on a pu le voir, diffère en fonction du
système de fichiers. Il s'avère que même si l'OverlayFS a été intégré dans le
noyau Linux après maintes péripéties, Docker, lorsqu'à été spécifié le format
des archives utilisées pour distribuer les couches, utilise aujourd'hui le
format d'AuFS pour représenter les suppressions. Il est donc important de le
voir également.
Au lieu d'utiliser un fichier spécial, AuFS crée un fichier standard
`.wh.<filename>`, où `<filename>` est le nom du fichier à masquer.
Afin de s'adapter au *storage driver*, lors de la décompression de l'archive,
Docker s'emploie à convertir[^MOBYWHITEOUT] les *whiteout files* qu'il rencontre dans le
format attendu.
[^MOBYWHITEOUT]: Voir le code
<https://github.com/moby/moby/blob/master/pkg/archive/archive_linux.go#L27>

View File

@ -1,6 +1,12 @@
include ../pandoc-opts.mk
SOURCES = tutorial.md oci.md registry.md runc.md linuxkit.md linuxkit-content.md rendu.md
SOURCES = tutorial.md \
registry.md \
../4/filesystem.md ../4/filesystem-ex.md ../4/filesystem-more.md \
oci.md \
runc.md \
linuxkit.md linuxkit-content.md \
rendu.md
all: tutorial.pdf

View File

@ -1,10 +1,12 @@
## Prérequis
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :\
Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez le télécharger
ici :\
<https://github.com/linuxkit/linuxkit/releases/latest>.
Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire
en plus du binaire[^lollibc] ;-)
en plus du binaire[^lollibc]. Un simple `chmod +x` vous permettra de l'exécuter
depuis n'importe quel dossier
[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.
@ -19,51 +21,77 @@ parties :
- `kernel` : il est attendu ici une image OCI contenant le nécessaire pour
pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
- `init` : l'ensemble des images OCI de cette liste seront fusionnés pour
donner naissance au *rootfs* de la machine. On n'y place normalement qu'un
gestionnaire de conteneur, qui sera chargé de lancer chaque conteneur au bon
donner naissance au *rootfs* de la machine. On y place normalement qu'un
gestionnaire de conteneurs, qui sera chargé de lancer chaque conteneur au bon
moment ;
- `onboot`, `onshutdown` et `services` : il s'agit de conteneurs qui seront
lancés par le système disponible dans l'`init`, au bon moment. Les conteneurs
indiqués dans `onboot` seront lancés **séquentiellement** au démarrage de la
machine, ceux dans `onshutdown` seront lancés lors de l'arrêt de la
machine. Les conteneurs dans `services` seront lancés simultanément une fois
que le dernier conteneur de `onboot` aura rendu la main ;
- `files` : des fichiers supplémentaires à placer dans le rootfs.
lancés par le gestionnaire disponible dans l'`init`, au moment désigné.\ Les
conteneurs indiqués dans `onboot` seront lancés **séquentiellement** au
démarrage de la machine, ceux dans `onshutdown` seront lancés lors de l'arrêt
de la machine. Les conteneurs dans `services` seront lancés simultanément une
fois que le dernier conteneur de `onboot` aura rendu la main ;
- `files` : des fichiers supplémentaires à placer dans le système de fichier à
l'emplacement déterminé.
Le format est documenté ici :
<https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md>
## Hello?
### Hello?
L'image la plus simple que l'on puisse réaliser pourrait être :
<div lang="en-US">
```yaml
kernel:
image: linuxkit/kernel:5.10.76
image: linuxkit/kernel:5.10.104
cmdline: "console=tty0 console=ttyS0"
init:
- linuxkit/init:eb597ef74d808b5320ad1060b1620a6ac31e7ced
- linuxkit/runc:21dbbda709ae138de0af6b0c7e4ae49525db5e88
- linuxkit/containerd:2f0907913dd54ab5186006034eb224a0da12443e
- linuxkit/init:8f1e6a0747acbbb4d7e24dc98f97faa8d1c6cec7
- linuxkit/runc:f01b88c7033180d50ae43562d72707c6881904e4
- linuxkit/containerd:de1b18eed76a266baa3092e5c154c84f595e56da
onboot:
- name: dhcpcd
image: linuxkit/dhcpcd:v0.8
image: linuxkit/dhcpcd:52d2c4df0311b182e99241cdc382ff726755c450
command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
services:
- name: getty
image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101
image: linuxkit/getty:76951a596aa5e0867a38e28f0b94d620e948e3e8
env:
- INSECURE=true
trust:
org:
- linuxkit
```
</div>
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un
shell sur la machine !
On retrouve nos différentes sections : `kernel` indique qu'il faut récupérer
l'image `linuxkit/kernel` depuis le registre Docker : il ne s'agit pas d'une
image qui sera lancé, elle est plutôt utilisée comme une archive de stockage
pour le noyau et ses modules. LinuxKit au moment de la construction de l'image
se chargera de placer les fichiers aux bons endroits.
Ensuite, nous avons une section `init` qui déclare 3 images Docker :
- `linuxkit/init` contient les fichiers de base et un binaire `/sbin/init` qui
servira de système d'initialisation ;
- `linuxkit/runc` nous donnera les outils pour lancer des conteneurs ;
- `linuxkit/containerd` apporte un daemon pour gérer les conteneurs pendant
leur durée de vie.
Ces trois images ne sont pas non plus des images Docker conventionnelles, dans
le sens où on ne peut pas les utiliser pour faire un `docker container
run`. Elles contiennent chacune une partie de l'arborescence du système de
fichiers, uniquement les fichiers nécessaire, en plus, au fonctionnement du
programme qu'elles ajoutent. Les images déclarées dans la section `init` seront
fusionnées ensemble et formeront le système de fichiers de base de notre image
LinuxKit.
Enfin les sections `onboot` et `services` sont plus conventionnelles : il
s'agit bien d'images Docker, les conteneurs seront lancés comme tel, à partir
d'une configuration `runc` qui sera générée au moment de la construction de
l'image LinuxKit.
L'image `getty` est notamment très pratique pour déboguer, car elle permet
d'avoir un shell sur la machine !
::::: {.more}
On notera cependant que, positionné dans `services`, le shell que nous
obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un
@ -74,6 +102,8 @@ la partie `init`, elle sera alors lancée comme un équivalent de
[^infogetty]: Plus d'infos à
<https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>
:::::
## `namespaces`
@ -81,7 +111,7 @@ Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourr
pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une
faille exploitable.
Néanmoins, contrairement à Docker qui va de base nous dissocier du maximum de
Néanmoins, contrairement à Docker qui, de base, va nous dissocier du maximum de
*namespaces*, `linuxkit` ne le fait pas pour les *namespaces* `net`, `ipc` et
`uts`. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les
conteneurs, tout comme les IPC et le nom de la machine.
@ -173,10 +203,29 @@ serveur SSH aux `services` :
<div lang="en-US">
```yaml
- name: sshd
image: linuxkit/sshd:add8c094a9a253870b0a596796628fd4ec220b70
image: linuxkit/sshd:4696ba61c3ec091328e1c14857d77e675802342f
binds.add:
- /root/.ssh:/root/.ssh
```
</div>
::::: {.question}
#### Que fait la ligne `binds.add` ? {-}
\
Avec l'instruction `binds.add`, LinuxKit va créer un *bind mount* selon le même
principe que les volumes des conteneurs Docker. Ici nous allons partager le
dossier `/root/.ssh` de notre image LinuxKit avec celui du conteneur `sshd`.
\
Un certain nombre de *bind mounts* sont effectués par défaut. Ceux-ci sont
déclarés dans les métadonnées des images. Pour avoir la liste, il convient de
regarder le fichier `build.yml` de chaque image :\
<https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5>
:::::
Comme nous n'avons défini aucun mot de passe, il va falloir utiliser une clef
SSH pour se connecter. Voilà un bon début d'utilisation de la section `files` :
@ -188,12 +237,10 @@ SSH pour se connecter. Voilà un bon début d'utilisation de la section `files`
```
</div>
Ceci va aller chercher votre clef RSA publique sur votre machine, pour
la placer directement comme contenu du fichier `authorized_keys`.
Ceci va aller chercher votre clef RSA publique sur votre machine, pour la
placer directement comme contenu du fichier `authorized_keys`. À adapter en
fonction de votre situation.
Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi
recopié, car le *package* `linuxkit/sshd` défini déjà cela :\
cf. <https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5>
## Interface réseau virtuelle

View File

@ -7,6 +7,8 @@ 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
fragmentation de l'écosystème.
## Spécifications
Trois spécifications ont été écrites :
- [`runtime-spec`](https://github.com/opencontainers/runtime-spec/blob/master/spec.md#platforms): définis les paramètres du démarrage d'un conteneur ;
@ -14,7 +16,7 @@ Trois spécifications ont été écrites :
- [`distribution-spec`](https://github.com/opencontainers/distribution-spec/blob/master/spec.md): définis la manière dont sont partagées et récupérées les images.
## `runtime-spec`
### `runtime-spec`
`runc` est l'implémentation de cette spécification ; elle a été extraite de
`docker`, puis donnée par Docker Inc. à l'OCI.
@ -38,7 +40,7 @@ plus de conteneur a proprement parlé, il fait seulement en sorte d'atteindre
l'état voulu par cette spécification, avant de passer la main à `runc`.
## `image-spec`
### `image-spec`
Une image OCI est composée d'un manifest, d'une suite de couches de systèmes de
fichiers, d'une configuration ainsi qu'un index d'image optionnel.
@ -68,14 +70,14 @@ là-dedans que finissent toutes les métadonnées que l'on inscrit dans nos
des couches du système de fichiers, ainsi que l'historique de l'image.
## `distribution-spec`
### `distribution-spec`
Dernière née de l'organisme, cette spécification fédère la notion de
*registre* : une API REST sur HTTP où l'on peut récupérer des images, mais
aussi en envoyer.
## Pour aller plus loin {-}
### Pour aller plus loin {-}
Si maintenant `docker` fait appel à un programme externe pour lancer
effectivement nos conteneurs, c'est que l'on peut changer cette

View File

@ -49,7 +49,7 @@ registre.
Avec `jq`, on peut l'extraire grâce à :
<div lang="en-US">
```bash
```
| jq -r .token
```
</div>
@ -149,7 +149,7 @@ tar xzf ${DL_LAYER} -C rootfs
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
<div lang="en-US">
```bash
```
42sh# chroot rootfs /hello
Hello from Docker!
[...]
@ -164,7 +164,7 @@ Hello from Docker!
Réalisez un script pour automatiser l'ensemble de ces étapes :
<div lang="en-US">
```bash
```
42sh$ cd $(mktemp -d)
42sh$ ./registry_play library/hello-world:latest

View File

@ -1,7 +1,5 @@
\newpage
`runc`
======
------
`runc` est le programme qui est responsable de la création effective du
conteneur : c'est lui qui va mettre en place toute la machinerie, les points de
@ -18,14 +16,14 @@ Pour appréhender l'utilisation de `runc` sans l'aide de Docker, nous allons
essayer de lancer un shell `alpine` avec un volume dans notre home.
## Prérequis
### Prérequis
Vous devriez avoir le binaire `runc` ou `docker-runc`. Si ce n'est pas le cas,
vous pouvez télécharger la dernière version :
<https://github.com/opencontainers/runc/releases>.
## Extraction du rootfs
### Extraction du rootfs
À l'aide du script d'extraction de registre déjà réalisé, extrayons le
*rootfs* d'alpine : `library/alpine` dans le registre Docker.
@ -40,7 +38,7 @@ docker image save alpine | tar xv -C rootfs
</div>
## Modèle de configuration
### Modèle de configuration
L'écriture complète d'un fichier `config.json` pour `runc` est plutôt
fastidieux et répétitif, nous allons donc gagner du temps et utiliser la
@ -57,7 +55,7 @@ Pour savoir à quoi correspondent tous ces éléments, vous pouvez consulter :\
Rassurez-vous, il n'y a que très peu de champs à modifier.
## Test brut
### Test brut
Voici comment nous pouvons tester le fonctionnement de notre *bundle* :
@ -87,7 +85,7 @@ virli1 12345 running /tmp/work/runctest 2012-12-12T12:12:12.123456789Z root
</div>
## Attacher notre `home`
### Attacher notre `home`
Dans le modèle de `config.json`, il y a déjà de nombreux systèmes de fichiers
qui sont montés. Nous pouvons les filtrer avec :