Plus petite image Docker - moins de 1 000 octets

Remarque perev. : L'auteur de ce document est un architecte de Barclays et un passionné de l'open source britannique Ian Miell. Il vise à créer une image Docker pratique (avec un binaire "en veille"), qui n'a pas besoin d'être téléchargée, mais simplement copiée via copier-coller. Par essai, erreur et expérimentation du code Assembleur, il atteint l'objectif en préparant une image de moins d'un kilo-octet.



Le voici (encodé en base64)
H4sICIa2A1sCA2IA7Vrrbts2FFYL7M9+7QUGGNyfDYhtkuJFFLAhWZOhBYJmaLMOWBAEFC+xVlkyJLpYEBjdY+0l+k6jfGvqtkEWp2qD8TMg8vAqnsNzDg9lQhhmEjHDhY4zgWJBBUQJ5ZnCGAubMUQMyhJqoRRMJxYbo7Q2CedYxlQO/myqMroeEEHICIngApspxohEKI4h5DHmGEUQQw7jqAejDjBtnKz9q2w7zubi7gkugazVKHdGuWltQArkWDMCdoCqSpufg/QSPK4aV8pxW+nL96uxzMu39G+NqRe5PeekGj13Oi9BamXRmCtl1dS9X2jqel147C7W+aOJKd8dZ04dlcqsSw7KVyA9Ab/uHT/+cTht6mFRKVkMmywv0yv0mnxbMc8sSP8Apzvg0ViDtJwWxQ54Mpbny5W9qIrp2DSrmt+r+mVenu/ny+UelK6+mFR56VYtjsqfp3mxHupQZqZYdp/NGeo850x99r9j7QloyWEz8kvpK//47vuymvzQ29vf79m8MKnIaIa8bUmwRdByw6TKREIoIzE3xBrjrY7MGDUilomQ3GrNrFaIKqSZ4lkvL3tD12sn/IQCrI10xtcC7C1kH9I+xseQpYilRAwoZ5AI9IcfWFfqpRfzK1M3eeUZDRAfQDGAfc/jHTDKG1fVXiInlzcfctnwLPP9Vszs9VXvUzFy5jlZV5WzTbtN3cWkZWkhL/yS2gXm1p7lumkl24wkpv51FbYcU0EZy7SV0ucEZowkiCjvLbAVikCaGUqhyjT0c0Lj/YrElmmSWANOZ7MooHPwRCiLRaJEzBXKFGTCy49lUHNKjEigVdD6H4uTzPj9wzDCSawU0TQT2ujhjVwjgZzSj/n/eX7D/xPm/T8N/v/Ll/+Lg2fPnxw93eL85xFvyB9Rn4TzXwdAAxiMYLD/t9f/7eM/xDja1P+YBf3vKP7L2+PnttsA/IfjcQiE7nkgdH18Ey4O7pjdH7ygmX0p9n8eFA5aG3pb+0/eP/9jzFmw/13AdTBHK3/OPx7/Ic4X8qecQ9K244QG/98JXh8c/vLwwYM1/TD6KWqpv6LdOb37gT67URKterTpVxu1V9PXq3lW1d8skn++9Y83f4cDeEBAQMBnwliWuTWNu8l33G38/3X3fzGk79wFQ4S4Lwr+vwOcXIJHy4ANkLv4L4APcJ6ZSXUsz+efh1xaSOf3VxstHS6+H/nSu4s6wOns9OugxrdG7WXV5K6qc9NEn0n/ESab+s9o0P+O7v9ce1WzVNI7uAiczYI6BgQEBNwD/AvqV/+XACoAAA==

Comment en suis-je arrivé là?


Un collègue a montré une fois une image Docker qu'il a utilisée pour tester les clusters Kubernetes. Il n'a rien fait: il a juste couru sous et a attendu que vous le tuiez.

«Regardez, cela ne prend que 700 kilo-octets! Téléchargement très rapide! ”

Et puis je suis devenu curieux de savoir quelle image Docker minimale je pouvais créer. Je voulais en obtenir un qui pourrait être encodé en base64 et envoyé littéralement n'importe où avec un simple copier-coller. Étant donné que l'image Docker n'est qu'un fichier tar et que le fichier tar n'est qu'un fichier, tout devrait fonctionner.

Petit binaire


Tout d'abord, j'avais besoin d'un très petit binaire Linux qui ne fait rien. Il faut un peu de magie - et voici deux articles merveilleux, informatifs et lisibles sur la création de petits exécutables:


Je n'avais pas besoin de «Hello World», mais d'un programme qui dort et fonctionne sur x86_64. J'ai commencé avec un exemple du premier article:

  SECTION .data msg: db "Hi World",10 len: equ $-msg SECTION .text global _start _start: mov edx,len mov ecx,msg mov ebx,1 mov eax,4 int 0x80 mov ebx,0 mov eax,1 int 0x80 

Exécuter:

 nasm -f elf64 hw.asm -o hw.o ld hw.o -o hw strip -s hw 

Il s'avère un binaire de 504 octets .

Mais tout de même, il n'est pas nécessaire d'utiliser "Hello World" ... Tout d'abord, j'ai découvert que les sections .data ou .text sont pas nécessaires et que le chargement des données n'est pas requis. De plus, la moitié supérieure de la section _start traite de la sortie de texte. En conséquence, j'ai essayé le code suivant:

 global _start _start: mov ebx,0 mov eax,1 int 0x80 

Et il a compilé dès 352 octets .

Mais ce n'est pas le résultat souhaité, car le programme termine simplement son travail, et nous en avons besoin pour dormir a . À la suite de recherches supplémentaires, il s'est avéré que la commande mov eax remplit le registre du processeur avec le numéro d'appel système Linux correspondant, et int 0x80 effectue l'appel lui-même. Ceci est décrit plus en détail ici .

Et là, j'ai trouvé la liste souhaitée. Syscall 1 est exit , et ce dont nous avons besoin est syscall 29:pause . Le programme suivant s'est avéré:

 global _start _start: mov eax, 29 int 0x80 

Nous avons économisé encore 8 octets: la compilation a produit un résultat de 344 octets , et maintenant c'est un binaire approprié pour nous, qui ne fait rien et attend un signal.

Creuser dans des hexs


Il est temps d'obtenir une tronçonneuse et de gérer le binaire ... Pour cela, j'ai utilisé hexer , qui est essentiellement vim pour les fichiers binaires avec la possibilité de modifier directement les hexs. Après de longues expériences, j'en ai tiré:



... le voici:



Ce code fait de même, mais notez combien de lignes et d'espaces il reste. Au cours de mon travail, j'ai été guidé par un tel document , mais dans l'ensemble, c'était une voie d'essais et d'erreurs.

Ainsi, la taille a diminué à 136 octets .

Moins de 100 octets?


Je voulais savoir si je pouvais aller plus loin. Après avoir lu ceci , j'ai supposé qu'il serait possible d'atteindre 45 octets, mais hélas! - non. Les astuces décrites ici sont conçues uniquement pour les binaires 32 bits, mais pour les binaires 64 bits, elles n'ont pas réussi.

Le mieux que j'ai réussi a été de prendre cette version 64 bits du programme et de l'intégrer à mon appel système:

 BITS 64 org 0x400000 ehdr: ; Elf64_Ehdr db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 0x3e ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndx ehdrsize equ $ - ehdr phdr: ; Elf64_Phdr dd 1 ; p_type dd 5 ; p_flags dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr _start: mov eax, 29 int 0x80 filesize equ $ - $$ 

L'image résultante est de 127 octets . Sur ce point, j'ai arrêté d'essayer de réduire la taille, mais j'accepte les propositions.

Tiny Docker


Maintenant qu'il existe un binaire qui implémente une attente sans fin, il reste à le mettre dans une image Docker.

Pour enregistrer chaque octet possible, j'ai créé un binaire avec un nom de fichier à partir d'un octet - t - et l' Dockerfile mis dans le Dockerfile , créant une image presque vide:

 FROM scratch ADD t /t 

Notez qu'il n'y a pas de CMD dans le Dockerfile , car cela augmenterait la taille de l'image. Pour commencer, vous devez passer la commande via les arguments à docker run .

Ensuite, la docker save créé un fichier tar, puis il a été compressé avec une compression gzip maximale. Le résultat est un fichier image Docker portable d'une taille inférieure à 1000 octets:

 $ docker build -tt . $ docker save t | gzip -9 - | wc -c 976 

J'ai également essayé de réduire la taille du fichier tar en expérimentant le fichier manifeste Docker, mais en vain: en raison des spécificités du format tar et de l'algorithme de compression gzip, de tels changements n'ont conduit qu'à la croissance du gzip final. J'ai essayé d'autres algorithmes de compression, mais gzip s'est avéré être le meilleur pour ce petit fichier.

PS du traducteur


Lisez aussi dans notre blog:

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


All Articles