From 5291bd365e39baf9da3dbc287df44d94a1f7ad25 Mon Sep 17 00:00:00 2001 From: nemunaire Date: Wed, 14 Nov 2018 10:11:43 +0100 Subject: [PATCH] docker-internals: oci and registry parts done --- tutorial/docker-internals/oci.md | 67 ++++++++++- tutorial/docker-internals/registry.md | 159 ++++++++++++++++++++++++++ tutorial/docker-internals/rendu.md | 3 + 3 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 tutorial/docker-internals/registry.md diff --git a/tutorial/docker-internals/oci.md b/tutorial/docker-internals/oci.md index 3448d76..c8730ae 100644 --- a/tutorial/docker-internals/oci.md +++ b/tutorial/docker-internals/oci.md @@ -9,6 +9,67 @@ fragmentation de l'écosystème. Trois spécifications ont été écrites : -- [`runtime-spec`](https://github.com/opencontainers/runtime-spec/blob/master/spec.md#platforms): défini la manière dont un conteneur est lancé ; -- [`image-spec`](https://github.com/opencontainers/image-spec/blob/master/spec.md): défini la construction, le transport et la préparation des images ; -- [`distribution-spec`](https://github.com/opencontainers/distribution-spec/blob/master/spec.md): défini la manière dont sont partagées et récupérées les images. +- [`runtime-spec`](https://github.com/opencontainers/runtime-spec/blob/master/spec.md#platforms): définit les paramètres du démarrage d'un conteneur ; +- [`image-spec`](https://github.com/opencontainers/image-spec/blob/master/spec.md): définit la construction, le transport et la préparation des images ; +- [`distribution-spec`](https://github.com/opencontainers/distribution-spec/blob/master/spec.md): définit la manière dont sont partagées et récupérées les images. + + +## `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. + +Pour démarrer un conteneur, la spécification indique qu'il est nécessaire +d'avoir un fichier de configuration `config.json` ainsi qu'un dossier où l'on +peut trouver la racine de notre conteneur. + +Le plus gros de la spécification concerne le format de ce fichier de +configuration : il contient en effet l'ensemble des paramètres avec lesquels il +faudra créer le conteneur : tant d'un point de vue isolement (on peut par +exemple choisir de quel *namespace* on souhaite se dissocier, ou le(s)quel(s) +rejoindre), quelles *capabilities* resteront disponibles, quels nouveaux points +de montages, ... Voir [la +suite](https://github.com/opencontainers/runtime-spec/blob/master/config.md). + +Aujourd'hui, les dernières versions de `docker` utilisent `runc` pour l'étape +de lancement du conteneur, après avoir téléchargé l'image puis mis en place +l'empilement de couches dans un répertoire prédéterminé. `docker` ne lance donc +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` + +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. + +Le +[manifest](https://github.com/opencontainers/image-spec/blob/master/manifest.md) +est toujours le point d'entrée d'une image : il référence l'emplacement où +trouver les différents éléments : configuration et couches. Lorsqu'une même +image a des variation en fonction de l'architecture du processeur, du système +d'exploitation, ... dans ce cas [l'index +d'image](https://github.com/opencontainers/image-spec/blob/master/image-index.md) +est utilisé pour sélectionner le bon manifest. + +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 +les tarballs), contenant éventuellement des fichiers et dossiers spéciaux +contenant les modifications, suppressions, ... éventuelles de la couche +représentée. + +La +[configuration](https://github.com/opencontainers/image-spec/blob/master/config.md) +contient l'ensemble des métadonnées qui serviront par exemple à construire le +`config.json` attendu par `runc` lorsqu'il faudra lancer l'image (c'est +là-dedans que finissent toutes les métadonnées que l'on inscrit dans nos +`Dockerfile` : `CMD`, `VOLUME`, `PORT`, ...). On y retrouve également l'ordre +des couches du système de fichiers, ainsi que l'historique de l'image. + + +## `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. diff --git a/tutorial/docker-internals/registry.md b/tutorial/docker-internals/registry.md new file mode 100644 index 0000000..dab6c8b --- /dev/null +++ b/tutorial/docker-internals/registry.md @@ -0,0 +1,159 @@ +Registres +========= + +**Outils nécessaires :** `curl`, `gunzip`, `jq`, `tar`. + +Dans cette partie, nous allons appréhender le fonctionnement d'un registre OCI, +et préparer le *rootfs* d'une image de base (Debian, Ubuntu, hello, ...) : en +nous préoccupant simplement de la couche la plus basse (qui ne contient pas de +modification ou de suppression : chaque fichier est normal). + + +## Authentification + +L'authentification est facultative et est laissée à l'appréciation du +fournisseur de service. Étant donné que nous allons utiliser le [Docker +Hub](https://hub.docker.com/), le registre par défaut de `docker`, nous allons +devoir nous plier à leur mécanisme d'authentification : chaque requête au +registre doivent être effectuées avec un jeton, que l'on obtient en +s'authentifiant auprès d'un service dédié. Ce service peut délivrer un jeton +sans authentifier l'interlocuteur, en restant anonyme ; dans ce cas, on ne +pourra accéder qu'aux images publiques. Ça tombe bien, c'est ce qui nous +intéresse aujourd'hui ! + +Il n'en reste pas moins que le jeton est forgé pour un service donné (dans +notre cas `registry.docker.io`) et avec un objectif bien cerné (pour nous, on +souhaite récupérer le contenu du dépôt[^quiddepot] `hello-world` : +`repository:hello-world:pull`). Ce qui nous donne : + +[^quiddepot]: Dans un registre, les fichiers qui composent l'image forment un + dépôt (*repository*). + +
+```shell + 42sh$ curl "https://auth.docker.io/token"\ + > "?service=registry.docker.io&scope=repository:library/hello-world:pull" | jq . + { + "token": "lUWXBCZzg2TGNUdmMy...daVZxGTj0eh", + "access_token": "eyJhbGciOiJSUzI1NiIsI...N5q469M3ZkL_HA", + "expires_in": 300, + "issued_at": "2012-12-12T12:12:12.123456789Z" + } +``` +
+ +C'est le `token` qu'il faudra fournir lors de nos prochaines requêtes au +registre. + +Avec `jq`, on peut l'extraire grâce à : + +
+```shell + | jq -r .token +``` +
+ + +## Lecture de l'index d'images + +Une fois en possession de notre jeton, nous pouvons maintenant demander l'index +d'images à notre registre : + +
+```shell + curl -s \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \ + "https://registry-1.docker.io/v2/library/hello-world/manifests/latest" | jq . +``` +
+ +Dans la liste des manifests retournés, nous devons récupérer son `digest`. Dans +tout l'écosystème OCI, les `digest` servent à la fois de chemin d'accès et de +somme de contrôle. + + +## Lecture du manifest + +Demandons maintenant le manifest correspondant à notre matériel et à notre +système d'exploitation : + +
+```shell + curl -s \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Accept: ${MEDIATYPE}" \ + "https://registry-1.docker.io/v2/library/hello-world/manifests/${MANIFEST_DIGEST}" | jq . +``` +
+ +Nous voici donc maintenant avec le manifest de notre image. Nous pouvons +constater qu'il n'a bien qu'une seule couche, ouf ! + + +## Récupération de la configuration et de la première couche + +Les deux éléments que l'on cherche à récupérer vont se trouver dans le +répertoire `blobs`, il ne s'agit en effet plus de manifest. Si les manifests sont toujours stockés par le registre lui-même, les blobs peuvent être délégués à un autre service, par exemple dans le cloud, chez Amazon S3, un CDN, etc. + +Pour récupérer la configuration de l'image : + +
+```shell + curl -s --location \ + -H "Authorization: Bearer ${TOKEN}" \ + "https://registry-1.docker.io/v2/library/hello-world/blobs/${CONFIG_DIGEST}" | jq . +``` +
+ + +Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment : + +
+```shell + wget --header "Authorization: Bearer ${TOKEN}" "https://registry-1.docker.io/v2/library/hello-world/blobs/${LAYER_DIGEST}" +``` +
+ + +## Extraction + +Le type indiqué par le manifest pour cette couche était +`application/vnd.docker.image.rootfs.diff.tar.gzip`, il s'agit donc d'une +tarball compressée au format gzip : + +
+```shell + mkdir rootfs + tar xzf ${DL_LAYER} -C rootfs +``` +
+ +Et voilà, nous avons extrait notre première image, nous devrions pouvoir : + +
+```shell + 42sh# chroot rootfs /hello + Hello from Docker! + [...] +``` +
+ + +## Exercice {.unnumbered} + +Réalisez un script pour automatiser l'ensemble de ces étapes : + +
+```shell + 42sh$ cd $(mktemp) + 42sh$ ~/workspace/registry_play.sh library/hello + 42sh$ find + . + ./rootfs + ./rootfs/hello + 42sh# chroot rootfs /hello + Hello from Docker! + [...] +``` +
diff --git a/tutorial/docker-internals/rendu.md b/tutorial/docker-internals/rendu.md index 9ea0b09..3c7a3f9 100644 --- a/tutorial/docker-internals/rendu.md +++ b/tutorial/docker-internals/rendu.md @@ -31,6 +31,9 @@ cela dépendra de votre avancée dans le projet) :
``` login_x-TP5/ + login_x-TP5/clair_config/config.yaml + login_x-TP5/nginx:mainline.html + login_x-TP5/registry_play.sh login_x-TP5/mydocker-export.sh login_x-TP5/config.json login_x-TP5/unboundkit.yml