Voyage au centre ... image docker. Ou comment télécharger une image du registre sans docker

3 jours avant la nouvelle année il y avait une tâche pour transférer notre logiciel au client via le manager, sur un lecteur flash. Le logiciel est une plate-forme de microservice avec des dizaines d'images Docker avec de nombreux paramètres et un graphique de barre d'un kilomètre. Ce que nous avions:


  • Manager à Moscou (je ne suis pas de là)
  • Windows
  • Il n'y a pas d'interaction directe (et si c'était le cas, cela n'a pas vraiment aidé)
  • docker non

Pff, je pensais! Je vais prendre Golang, écrire un programme, compiler pour Windows.
... et 5 heures plus tard, j'ai réalisé la hâte de mes conclusions. À ce moment, le rire de Nelson se rappela pour la première fois. HA HA Ce qui m'a hanté tout le temps que j'ai passé à étudier la question.


La plupart des exemples que j'ai trouvés nécessitent dockerd. Deux scripts qui n'utilisent pas dockerd, qui ont été trouvés après une heure de recherche sur Google, un et deux . La première option m'a aidé à comprendre le processus d'obtention de toutes les couches d'images et des fichiers de configuration, mais il est impossible de l'utiliser avec Windows. Et la deuxième option a indiqué que ce n'était pas seulement que divers hachages scintillaient sur l'écran, en particulier ce FIXME . On pourrait bien sûr s'arrêter là, ça marche! Le transfert pour aller n'est pas difficile. Mais comment vérifier que les images du gestionnaire se sont révélées être sous la même forme que dans notre registre? Mais pas question! Par conséquent, je viens de télécharger des images vers le stockage partagé, téléchargées à l'aide de la commande docker save et partagé le lien. Et calmé à ce sujet.


Le quatrième jour des vacances, assez fatigué d'eux, l'idée de télécharger et d'assembler correctement l'image docker m'a de nouveau dépassé, et j'ai plongé dans le code moby pendant quelques heures.


Ce que j'avais cette fois:


  • Comprendre comment obtenir toutes les couches

Prenant Python "en main" et basé sur ce script , j'ai décidé de le corriger. Le deuxième jour, j'ai décidé d'écrire un script à partir de zéro. En me souvenant de mon artisanat pour l'oaut-autorisation, j'ai simplement copié une partie du code à partir de là, ainsi que les développements déjà effectués par moi lors de l'édition du script. Il n'y a eu aucun problème d'autorisation et de téléchargement de ces données, mais des questions se sont posées:


  • quels sont les hachages affichés lors de l'exécution de la commande docker pull?
  • quels sont les hachages utilisés pour nommer les répertoires à l'intérieur de l'archive d'image?
  • Comment construire une archive tar pour que la somme de contrôle corresponde à l'image d'origine?

Pour étudier, j'ai choisi une image ubuntu: 18.04
image sha256sum enregistrée via docker save - 257cab9137419a53359d0ed76f680fe926ed3645238357bdcdb84070a8f26cd0.


 > docker pull ubuntu:18.04 18.04: Pulling from library/ubuntu 2746a4a261c9: Downloading [==============> ] 6.909MB/26.69MB 4c1d20cdee96: Download complete 0d3160e1d0de: Download complete c8e37668deea: Download complete Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928f0de9336eba21aa4 Status: Downloaded newer image for ubuntu:18.04 docker.io/library/ubuntu:18.04 

Contenu de l'archive d'images


 tar tvf ubuntu.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0 root root 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0 root root 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0 root root 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0 root root 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0 root root 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0 root root 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0 root root 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0 root root 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0 root root 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0 root root 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0 root root 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0 root root 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0 root root 432 Jan 1 1970 manifest.json -rw-r--r-- 0 root root 88 Jan 1 1970 repositories 

Configuration d'image


 { ... "rootfs": { "type": "layers", "diff_ids": [ "sha256:2dc9f76fb25b31e0ae9d36adce713364c682ba0d2fa70756486e5cedfaf40012", "sha256:9f3bfcc4a1a8a676da07287a1aa6f2dcc8e869ea6f054c337593481a5bb1345e", "sha256:27dd43ea46a831c39d224e7426794145fba953cd7309feccf4d5ea628072f6a2", "sha256:918efb8f161b4cbfa560e00e8e0efb737d7a8b00bf91bb77976257cd0014b765" ] } ... } 

La première question a été réglée assez rapidement, la documentation https://github.com/opencontainers/image-spec/blob/master/config.md a aidé. Les hachages qui apparaissent lorsque vous exécutez la commande docker pull sont des identificateurs de chaîne calculés à partir de la liste diff_ids du manifeste image-config, où le premier chainID est toujours le premier de la liste diff_ids et les suivants sont des sommes de hachage de la chaîne (chain_id[i-1] + " " + diff_id[i]) . Code pour construire chainID chainID:


 def chain_ids(ids: list) -> list: chain = list() chain.append(ids[0]) if len(ids) < 2: return ids nxt = list() nxt.append("sha256:" + hashlib.sha256(f'{ids[0]} {ids[1]}'.encode()).hexdigest()) nxt.extend(ids[2:]) chain.extend(chain_ids(nxt)) return chain 

L'ajout d'un préfixe avec le nom de l'algorithme, dans ce cas "sha256:", est obligatoire et est inclus dans les exigences de la norme opencontainers, c'est-à-dire la chaîne doit être de la forme "algorithme: hachage".


Il a passé deux soirées sur une question sur la façon de nommer un répertoire. Pendant assez longtemps, j'ai regardé le code source de docker-daemon et Oh! un miracle! J'ai réussi à trouver le code de génération ici et ici . Pour générer le nom du répertoire, vous devez calculer le hachage à partir de la configuration de la couche json. Docker a plusieurs versions de configuration et avant le moteur Docker 1.9, les configurations de la version v1 étaient utilisées. Aussitôt dit, aussitôt fait! Et encore une fois, la silhouette de Nelson se pose. Après un bref débogage, j'ai réalisé que le problème se cachait dans la génération de json. En Python, l'ordre des données dans un dictionnaire peut différer de l'ordre des données dans json généré à partir de ce dictionnaire. L'ordre des données dans json sera différent et, par conséquent, son hachage sera également différent. Je devais aller à OrderedDict, pré-enregistrer la commande de données souhaitée en eux. Cela a augmenté la taille du code d'une fois et demie.


Il semble que tout soit corrigé, je lance le script et ... quelque part au fond, le fameux HA-HA apparaît! Le dernier hachage ne correspond pas. Encore une fois, j'étudie le code et vois , et ceci est une autre v1-config contenant toutes les informations sur l'image, qui peuvent être vues en utilisant la docker inspect . Ajoutez un autre OrderDict spécialement pour lui, complétez le code et ... HA HA!
Il était déjà 5 heures du matin et ma tête ne réfléchissait pas vraiment, donc après le sommeil, je suis retourné voir le code. La lecture répétée du code de génération est tombée sur une ligne . Comme j'étais content de voir ça. Avant de la voir, j'avais pensé à construire mon docker avec Jack noir et enregistrement des données. J'active le débogage, j'exécute la docker save et ... ce n'est pas du tout ridicule, dans docker-desktop pour mac os il y a une limite sur la longueur de la ligne dans le journal de 947 caractères et la configuration générée passe à \" . Après avoir effectué toutes ces étapes sous Linux, j'ai réussi à obtenir la configuration couche de la première version, sur la base de laquelle j'ai écrit le code, et j'ai réussi à obtenir le hachage souhaité de la dernière couche. Les hachages pour tous les fichiers sont les mêmes, les répertoires sont nommés de la même manière que l'image d'origine. Il est temps de collecter l'archive tar ...


La taille du fichier ne correspond pas, lisez https://github.com/opencontainers/image-spec/blob/master/layer.md et le format d' archive tar . La valeur par défaut est 10240 octets, et la taille de l'archive que j'ai collectée est supérieure de 9216 octets. Au début, je pensais qu'il était nécessaire de réduire la taille du bloc à 1024 octets, ce qui s'est avéré incorrect et, par conséquent, la taille du bloc de 512 octets égalisait la taille des archives.


 tarfile.RECORDSIZE = 512 

La première ligne de l'archive nouvellement créée contient le dossier racine "/". Cette option ne convient pas, par conséquent, je complète le code en scannant le contenu du dossier et en l'ajoutant individuellement à l'archive, après l'avoir préalablement trié.


Enfin, nous avons réussi à obtenir la même taille de fichier, un aspect uniforme des répertoires, mais ce n'est pas tout. Les fichiers et répertoires, à l'exception de manifest.json et respoitories, dans l'archive doivent avoir des attributs st_atime, st_mtime égal à st_ctime. Pour les fichiers manifest.json et respoitories, les attributs st_atime, st_mtime et st_ctime doivent être datés du début de l'ère 1970-01-01 00:00 . Toutes les dates doivent être définies en fonction du fuseau horaire, respectivement. Depuis que j'ai effectué tout le travail sous mac os, j'ai remarqué une différence. Lors de l'enregistrement de l'image sous Linux, la liste des fichiers dans l'archive ressemblait à ceci:


 tar tvf ubuntu.tar drwxr-xr-x 0/0 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0/0 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0/0 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0/0 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0/0 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0/0 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0/0 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0/0 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0/0 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0/0 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0/0 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0/0 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0/0 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0/0 432 Jan 1 1970 manifest.json -rw-r--r-- 0/0 88 Jan 1 1970 repositories 

Contrairement à la liste au début de l'article, sous Linux, l'archive est enregistrée avec l'indicateur numérique uniquement. Il y a deux variables dans l'objet tarinfo responsables de cela, tarinfo.uname et tarinfo.gname. Et le deuxième problème avec mac os est l'absence d'un groupe racine, il est corrigé en utilisant la variable tarinfo.gid dans le même objet tarinfo. Eh bien, tout semble créer une archive ...



Pour tous les fichiers, le hachage converge, les noms des répertoires et des fichiers sont les mêmes, les attributs st_atime, st_mtime et st_ctime convergent avec l'original, les droits sur les fichiers sont exactement les mêmes. L'ouverture des deux archives dans un éditeur hexadécimal a vu une légère différence:



La fenêtre supérieure est l'original, l'archive inférieure que j'ai collectée.


Comprendre le format tar. Après le nom du répertoire est la valeur des droits sur le fichier (rectangle orange). La différence est que les informations sur l'objet ajouté à l'archive ne sont pas ajoutées aux droits de répertoire. Une valeur de 40755 indiquée dans les droits indique qu'il s'agit d'un répertoire avec des autorisations de 755 et 100644 est un fichier avec des autorisations de 644. Le rectangle rouge est une chaîne magique et à en juger par le code ustar\000 chaîne magique ustar\000 , il est utilisé uniquement aux formats PAX et USTAR. Le format PAX ne convient pas du tout, il utilise un type spécial d'en-tête. Le rectangle bleu est une somme de contrôle et il diffère en raison de l'utilisation de différents formats pour les droits d'écriture sur un fichier et un en-tête magique.


Je mets le format d'archive sur USTAR, mais on ne sait pas quoi faire avec l'enregistrement des autorisations de fichier. Ici et ici, la magie opère pour moi, je n'ai jamais travaillé avec le système octal et je ne comprends pas pourquoi l'esperluette est nécessaire ici (peut-être que quelqu'un dans les commentaires partagera ses connaissances). J'ai dû ajouter plusieurs impressions pour voir quelles données arrivent avec des arguments et quelles données sont utilisées pour former le bloc d'archives. Prenant un entier de la deuxième position du bloc de données, il était 16877, et le menant au calcul octal, il s'est avéré que cette valeur est 0o40755 , ce qui est 0o40755 ce dont j'ai besoin. En remplaçant simplement les fonctions get_info et _create_header, en supprimant & 0o7777 (rien d'autre ne m'est venu à l'esprit), j'ai réussi à collecter une archive tar avec un hachage sha256 qui coïncidait avec l'original.


PS Lors de la rédaction d'un article, l'image d'ubuntu a été mise à jour: 18.04 sur hub.docker.com. J'ai donc dû télécharger l'image sur Digest. Les sommes de hachage ne correspondent plus à l'original car Digest a été écrit à la place de la balise, sinon ce sont des images identiques.


La deuxième découverte pour moi a été l'absence d'un fichier de référentiels dans l'archive lors de l'enregistrement d'une image avec une balise manquante à l'aide de la docker save .


Code de travail complet ici: https://github.com/myback/docker_pull


L'image de Nelson Manz, ainsi que son rire «HA-HA» est la propriété de FOX :)

Source: https://habr.com/ru/post/fr484374/


All Articles