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: