docker-internals: oci and registry parts done

This commit is contained in:
nemunaire 2018-11-14 10:11:43 +01:00
parent f89b5e88e6
commit 5291bd365e
3 changed files with 226 additions and 3 deletions

View File

@ -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.

View File

@ -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*).
<div lang="en-US">
```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"
}
```
</div>
C'est le `token` qu'il faudra fournir lors de nos prochaines requêtes au
registre.
Avec `jq`, on peut l'extraire grâce à :
<div lang="en-US">
```shell
| jq -r .token
```
</div>
## Lecture de l'index d'images
Une fois en possession de notre jeton, nous pouvons maintenant demander l'index
d'images à notre registre :
<div lang="en-US">
```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 .
```
</div>
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 :
<div lang="en-US">
```shell
curl -s \
-H "Authorization: Bearer ${TOKEN}" \
-H "Accept: ${MEDIATYPE}" \
"https://registry-1.docker.io/v2/library/hello-world/manifests/${MANIFEST_DIGEST}" | jq .
```
</div>
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 :
<div lang="en-US">
```shell
curl -s --location \
-H "Authorization: Bearer ${TOKEN}" \
"https://registry-1.docker.io/v2/library/hello-world/blobs/${CONFIG_DIGEST}" | jq .
```
</div>
Enfin, armé du `digest` de notre couche, il ne nous reste plus qu'à la demander gentiment :
<div lang="en-US">
```shell
wget --header "Authorization: Bearer ${TOKEN}" "https://registry-1.docker.io/v2/library/hello-world/blobs/${LAYER_DIGEST}"
```
</div>
## 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 :
<div lang="en-US">
```shell
mkdir rootfs
tar xzf ${DL_LAYER} -C rootfs
```
</div>
Et voilà, nous avons extrait notre première image, nous devrions pouvoir :
<div lang="en-US">
```shell
42sh# chroot rootfs /hello
Hello from Docker!
[...]
```
</div>
## Exercice {.unnumbered}
Réalisez un script pour automatiser l'ensemble de ces étapes :
<div lang="en-US">
```shell
42sh$ cd $(mktemp)
42sh$ ~/workspace/registry_play.sh library/hello
42sh$ find
.
./rootfs
./rootfs/hello
42sh# chroot rootfs /hello
Hello from Docker!
[...]
```
</div>

View File

@ -31,6 +31,9 @@ cela dépendra de votre avancée dans le projet) :
<div lang="en-US">
```
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