Menor imagem do Docker - Menos de 1000 bytes

Nota perev. : O autor deste material é um arquiteto do Barclays e do entusiasta britânico de código aberto Ian Miell. O objetivo é criar uma imagem conveniente do Docker (com um binário "adormecido"), que não precisa ser baixado, mas simplesmente copie via copiar e colar. Por tentativa, erro e experimentação com o código Assembler, ele atinge a meta preparando uma imagem com menos de um kilobyte de tamanho.



Aqui está (codificado em 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==

Como cheguei a isso?


Um colega uma vez mostrou uma imagem do Docker que ele usou para testar os clusters do Kubernetes. Ele não fez nada: ele apenas correu e esperou que você o matasse.

“Olha, são necessários apenas 700 kilobytes! Download muito rápido! ”

E fiquei curioso com a imagem mínima do Docker que eu poderia criar. Eu queria ter um que pudesse ser codificado em base64 e enviado literalmente para qualquer lugar com um simples copiar e colar. Como a imagem do Docker é apenas um arquivo tar e o arquivo tar é apenas um arquivo, tudo deve funcionar.

Binário minúsculo


Primeiro de tudo, eu precisava de um binário Linux muito pequeno que não faz nada. É preciso um pouco de mágica - e aqui estão dois artigos maravilhosos, informativos e legíveis sobre a criação de pequenos executáveis:


Eu não precisava do “Hello World”, mas de um programa que apenas dorme e roda em x86_64. Comecei com um exemplo do primeiro artigo:

  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 

Execute:

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

Acontece um binário de 504 bytes .

Ainda assim, não é necessário usar o "Hello World" ... Primeiro, descobri que as seções .text ou .text são desnecessárias e o carregamento de dados não é necessário. Além disso, a metade superior da seção _start lida com a saída de texto. Como resultado, tentei o seguinte código:

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

E compilou já em 352 bytes .

Mas este não é o resultado desejado, porque o programa simplesmente encerra seu trabalho e precisamos que ele durma a . Como resultado de pesquisas adicionais, verificou-se que o comando mov eax preenche o registro do processador com o número de chamada do sistema Linux correspondente e int 0x80 faz a própria chamada. Isso é descrito em mais detalhes aqui .

E aqui eu encontrei a lista desejada. O Syscall 1 é a exit e o que precisamos é syscall 29:pause . O seguinte programa acabou:

 global _start _start: mov eax, 29 int 0x80 

Economizamos mais 8 bytes: a compilação produziu um resultado de 344 bytes , e agora é um binário adequado para nós, que não faz nada e espera um sinal.

Cavando em hexágonos


É hora de pegar uma serra elétrica e lidar com o binário ... Para isso, usei o hexer , que é essencialmente o vim para arquivos binários com a capacidade de editar diretamente hexágonos. Após longas experiências, obtive disso:



... aqui está:



Esse código faz o mesmo, mas observe quantas linhas e espaços restam. No processo de meu trabalho, fui guiado por esse documento , mas, em geral, foi um caminho de tentativa e erro.

Portanto, o tamanho diminuiu para 136 bytes .

Menos de 100 bytes?


Eu queria saber se eu poderia ir mais longe. Depois de ler isso , presumi que seria possível atingir 45 bytes, mas infelizmente! não. Os truques descritos lá são projetados apenas para binários de 32 bits, mas para os de 64 bits eles não foram aprovados.

O melhor que consegui foi pegar esta versão de 64 bits do programa e integrá-la na minha chamada de 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 $ - $$ 

A imagem resultante é de 127 bytes . Por isso, parei de tentar reduzir o tamanho, mas aceito as propostas.

Docker minúsculo


Agora que existe um binário que implementa uma espera sem fim, resta colocá-lo em uma imagem do Docker.

Para salvar todos os bytes possíveis, criei um binário com um nome de arquivo de um byte - t - e coloquei no Dockerfile , criando uma imagem quase vazia:

 FROM scratch ADD t /t 

Observe que não há CMD no Dockerfile , pois isso aumentaria o tamanho da imagem. Para começar, você precisa passar o comando pelos argumentos para docker run a docker run de docker run .

Em seguida, o docker save criou um arquivo tar e, em seguida, foi compactado com a compressão gzip máxima. O resultado é um arquivo de imagem portátil do Docker com um tamanho menor que 1000 bytes:

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

Também tentei reduzir o tamanho do arquivo tar experimentando o arquivo de manifesto do Docker, mas em vão: devido às especificidades do formato tar e do algoritmo de compactação gzip, essas alterações apenas levaram ao crescimento do gzip final. Tentei outros algoritmos de compactação, mas o gzip acabou sendo o melhor para esse arquivo pequeno.

PS do tradutor


Leia também em nosso blog:

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


All Articles