docker-internals: oci and registry parts done
This commit is contained in:
parent
f89b5e88e6
commit
5291bd365e
3 changed files with 226 additions and 3 deletions
|
@ -9,6 +9,67 @@ fragmentation de l'écosystème.
|
||||||
|
|
||||||
Trois spécifications ont été écrites :
|
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é ;
|
- [`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éfini la construction, le transport et la préparation des images ;
|
- [`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éfini la manière dont sont partagées et récupérées les 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.
|
||||||
|
|
159
tutorial/docker-internals/registry.md
Normal file
159
tutorial/docker-internals/registry.md
Normal 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>
|
|
@ -31,6 +31,9 @@ cela dépendra de votre avancée dans le projet) :
|
||||||
<div lang="en-US">
|
<div lang="en-US">
|
||||||
```
|
```
|
||||||
login_x-TP5/
|
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/mydocker-export.sh
|
||||||
login_x-TP5/config.json
|
login_x-TP5/config.json
|
||||||
login_x-TP5/unboundkit.yml
|
login_x-TP5/unboundkit.yml
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue