diff --git a/tutorial/4/filesystem-ex.md b/tutorial/4/filesystem-ex.md new file mode 100644 index 0000000..90b40a3 --- /dev/null +++ b/tutorial/4/filesystem-ex.md @@ -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. + +
+```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. +>>> _ +``` +
+ +::::: diff --git a/tutorial/4/filesystem-more.md b/tutorial/4/filesystem-more.md new file mode 100644 index 0000000..e6474ce --- /dev/null +++ b/tutorial/4/filesystem-more.md @@ -0,0 +1,10 @@ +## Pour aller plus loin + +* [Unioning file systems: Architecture, features, and design choices](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 : + diff --git a/tutorial/4/filesystem.md b/tutorial/4/filesystem.md new file mode 100644 index 0000000..8b840b0 --- /dev/null +++ b/tutorial/4/filesystem.md @@ -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.` +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 : , +. + +::::: + +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 : + +
+``` +mount -t overlay -olowerdir=/lower,upperdir=/upper,workdir=/work ignored /merged +``` +
+ +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` : + +
+``` +42sh$ docker info | grep "Storage Driver" + Storage Driver: overlay2 +``` +
+ +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 : + +
+``` +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) +``` +
+ +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 : + +
+``` +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" +} +``` +
+ +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 : + +
+``` +incntr$ echo "newfile" > /root/foobar +``` +
+ +
+``` +42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff +/var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff +└── root + └── foobar +``` +
+ +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é : + +
+``` +incntr$ echo "Bienvenue dans le conteneur" >> /etc/issue +``` +
+ +
+``` +42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff +/var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff +└── etc + └── issue +``` +
+ +
+``` +42sh$ cat /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/etc/issue +Debian GNU/Linux 11 \n \l +Bienvenue dans le conteneur +``` +
+ + +### 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. + +
+``` +incntr$ rm /etc/adduser.conf +``` +
+ +
+``` +42sh$ tree /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff +/var/lib/docker/overlay2/1531651afa872006a4b2b9b913d5d8ee317cf12be7883517ba77f3d094f871b4/diff +└── etc + └── adduser.conf +``` +
+ +
+``` +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 +``` +
+ +Notons ici `Device type: 0,0`. + +Pour créer nous-mêmes un fichier similaire, il faudrait utiliser : + +
+``` +42sh$ mkdir /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/bin +42sh$ mknod /var/lib/docker/overlay2/2a353708e5...91b230f9d700/diff/bin/sh c 0 0 +``` +
+ +::::: {.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.`, où `` 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 + diff --git a/tutorial/docker-internals/Makefile b/tutorial/docker-internals/Makefile index bfd45d2..83b74dc 100644 --- a/tutorial/docker-internals/Makefile +++ b/tutorial/docker-internals/Makefile @@ -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 diff --git a/tutorial/docker-internals/linuxkit-content.md b/tutorial/docker-internals/linuxkit-content.md index 2a37d0f..49e830c 100644 --- a/tutorial/docker-internals/linuxkit-content.md +++ b/tutorial/docker-internals/linuxkit-content.md @@ -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 :\ . 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 : -## Hello? +### Hello? L'image la plus simple que l'on puisse réaliser pourrait être :
```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 ```
-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 à +::::: + ## `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` :
```yaml - name: sshd - image: linuxkit/sshd:add8c094a9a253870b0a596796628fd4ec220b70 + image: linuxkit/sshd:4696ba61c3ec091328e1c14857d77e675802342f + binds.add: + - /root/.ssh:/root/.ssh ```
+::::: {.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 :\ + + +::::: + 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` ``` -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. ## Interface réseau virtuelle diff --git a/tutorial/docker-internals/oci.md b/tutorial/docker-internals/oci.md index 0a50526..a71ffde 100644 --- a/tutorial/docker-internals/oci.md +++ b/tutorial/docker-internals/oci.md @@ -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 diff --git a/tutorial/docker-internals/registry.md b/tutorial/docker-internals/registry.md index c5f038c..a0d84e0 100644 --- a/tutorial/docker-internals/registry.md +++ b/tutorial/docker-internals/registry.md @@ -49,7 +49,7 @@ registre. Avec `jq`, on peut l'extraire grâce à :
-```bash +``` | jq -r .token ```
@@ -149,7 +149,7 @@ tar xzf ${DL_LAYER} -C rootfs Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
-```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 :
-```bash +``` 42sh$ cd $(mktemp -d) 42sh$ ./registry_play library/hello-world:latest diff --git a/tutorial/docker-internals/runc.md b/tutorial/docker-internals/runc.md index a40f326..91c3847 100644 --- a/tutorial/docker-internals/runc.md +++ b/tutorial/docker-internals/runc.md @@ -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 : . -## 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
-## 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
-## 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 :