Imagen de Docker más pequeña: menos de 1000 bytes

Nota perev. : El autor de este material es arquitecto en Barclays y el entusiasta británico de código abierto Ian Miell. Su objetivo es hacer una imagen conveniente de Docker (con un binario "dormido"), que no es necesario descargar, sino simplemente copiar mediante copiar y pegar. Mediante prueba, error y experimentación con el código Assembler, alcanza el objetivo preparando una imagen de menos de un kilobyte de tamaño.



Aquí está (codificado 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==

¿Cómo llegué a esto?


Un colega mostró una vez una imagen de Docker que utilizó para probar los grupos de Kubernetes. No hizo nada: solo corrió y esperó a que lo mataras.

“¡Mira, solo se necesitan 700 kilobytes! ¡Descarga realmente rápida! ”

Y luego sentí curiosidad por la mínima imagen de Docker que podía crear. Quería obtener uno que pudiera codificarse en base64 y enviarse literalmente a cualquier lugar con simple copiar y pegar. Dado que la imagen de Docker es solo un archivo tar y el archivo tar es solo un archivo, todo debería funcionar.

Pequeño binario


En primer lugar, necesitaba un binario Linux muy pequeño que no hace nada. Se necesita un poco de magia, y aquí hay dos artículos maravillosos, informativos y legibles sobre la creación de pequeños ejecutables:


No necesitaba "Hello World", sino un programa que simplemente duerme y se ejecuta en x86_64. Comencé con un ejemplo del primer artículo:

  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 

Ejecutar:

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

Resulta un binario de 504 bytes .

Pero aún así, no es necesario usar "Hello World" ... Primero, descubrí que las secciones .data o .text son innecesarias y no se requiere la carga de datos. Además, la mitad superior de la sección _start se ocupa de la salida de texto. Como resultado, probé el siguiente código:

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

Y se compiló ya en 352 bytes .

Pero este no es el resultado deseado, porque el programa simplemente termina su trabajo y lo necesitamos para dormir a . Como resultado de una investigación adicional, resultó que el comando mov eax llena el registro del procesador con el número de llamada del sistema Linux correspondiente, e int 0x80 realiza la llamada en sí. Esto se describe con más detalle aquí .

Y aquí encontré la lista deseada. Syscall 1 es exit , y lo que necesitamos es syscall 29:pause . El siguiente programa resultó:

 global _start _start: mov eax, 29 int 0x80 

Guardamos otros 8 bytes: la compilación produjo un resultado de 344 bytes , y ahora es un binario adecuado para nosotros, que no hace nada y espera una señal.

Excavando en hexes


Es hora de obtener una motosierra y tratar con el binario ... Para esto, utilicé hexer , que es esencialmente vim para archivos binarios con la capacidad de editar directamente hexes. Después de largos experimentos, obtuve de esto:



... aquí está:



Este código hace lo mismo, pero tenga en cuenta cuántas líneas y espacios quedan. En el proceso de mi trabajo, fui guiado por dicho documento , pero en general fue un camino de prueba y error.

Entonces, el tamaño ha disminuido a 136 bytes .

¿Menos de 100 bytes?


Quería saber si podía ir más lejos. Después de leer esto , supuse que sería posible alcanzar los 45 bytes, ¡pero por desgracia! no Los trucos descritos allí están diseñados solo para binarios de 32 bits, pero para los de 64 bits no se aprobaron.

Lo mejor que logré fue tomar esta versión de 64 bits del programa e incorporarla a mi llamada al sistema:

 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 $ - $$ 

La imagen resultante es de 127 bytes . Sobre esto, dejé de intentar reducir el tamaño, pero acepto las propuestas.

Tiny Docker


Ahora que hay un binario que implementa una espera interminable, queda por ponerlo en una imagen Docker.

Para guardar cada byte posible, creé un binario con un nombre de archivo de un byte - t - y lo puse en el Dockerfile , creando una imagen casi vacía:

 FROM scratch ADD t /t 

Tenga en cuenta que no hay CMD en el Dockerfile , ya que esto aumentaría el tamaño de la imagen. Para comenzar, debe pasar el comando a través de los argumentos para docker run .

Luego, el docker save creó un archivo tar y luego se comprimió con la máxima compresión gzip. El resultado es un archivo de imagen portátil de Docker con un tamaño de menos de 1000 bytes:

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

También intenté reducir el tamaño del archivo tar experimentando con el archivo de manifiesto Docker, pero en vano: debido a la especificidad del formato tar y el algoritmo de compresión gzip, tales cambios solo llevaron al crecimiento del gzip final. Probé otros algoritmos de compresión, pero gzip resultó ser el mejor para este pequeño archivo.

PD del traductor


Lea también en nuestro blog:

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


All Articles