Главная » Хабрахабр » [Перевод] Самый маленький Docker-образ — меньше 1000 байт

[Перевод] Самый маленький Docker-образ — меньше 1000 байт

Прим. перев.: Автор этого материала — архитектор в Barclays и Open Source-энтузиаст из Великобритании Ian Miell. Он задаётся целью сделать удобный образ Docker (со «спящим» бинарником), который не нужно скачивать, а достаточно просто копировать через copy & paste. Методом проб, ошибок и экспериментов с Assembler-кодом он достигает цели, подготовив образ размером менее килобайта.

Вот он (закодирован в 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==

Как я к этому пришёл?

Однажды коллега показал Docker-образ, который он использовал для тестирования кластеров Kubernetes. Он ничего не делал: просто запускал под и ждал, пока вы его убьёте.

Его по-настоящему быстро скачивать!» «Смотри, он занимает всего 700 килобайт!

И тут мне стало любопытно, какой же минимальный образ Docker я смогу создать. Хотелось получить такой, что можно было бы закодировать в base64 и отправлять буквально куда угодно простым copy & paste. Поскольку Docker-образ — это просто tar-файл, а tar-файл — это всего лишь файл, всё должно получиться.

Крохотный бинарник

В первую очередь мне был нужен очень маленький Linux-бинарник, которые ничего не делает. Потребуется немного волшебства — и вот две замечательные, содержательные и достойные прочтения статьи о создании маленьких исполняемых файлов:
Мне нужен был не «Hello World», а программа, которая просто спит и работает на x86_64. Я начал с примера из первой статьи:

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

Запустим:

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

Получается бинарник в 504 байта.

Вдобавок, верхняя половина секции _start занимается выводом текста. Но всё-таки нужен не «Hello World»… Во-первых, я выяснил, что излишни секции .data или .text и не требуется загрузка данных. В итоге, я попробовал следующий код:

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

И он скомпилировался уже в 352 байта.

В результате дополнительных исследований выяснилось, что команда mov eax заполняет регистр процессора соответствующим номером системного вызова Linux, а int 0x80 производит сам вызов. Но это ещё не искомый результат, потому что программа просто завершает свою работу, а нам нужно, чтобы она спала. Подробнее это описано здесь.

Syscall 1 — это exit, а нужный нам — это syscall 29:pause. А здесь я нашёл нужный список. Получилась такая программа:

global _start
_start: mov eax, 29 int 0x80

Мы сэкономили ещё 8 байтов: компиляция выдала результат в 344 байта, и теперь это подходящий нам бинарник, который ничего не делает и ожидает сигнала.

Копаясь в hex'ах

Настало время достать бензопилу и разобраться с бинарником… Для этого я использовал hexer, который по сути vim для бинарных файлов с возможностью прямого редактирования hex'ов. После продолжительных экспериментов я получил из такого:

… вот это:

В процессе своей работы я руководствовался таким документом, но по большому счёту это был путь проб и ошибок. Данный код делает то же самое, но обратите внимание, сколько строк и пробелов ушло.

Итак, размер уменьшился до 136 байт.

Меньше 100 байт?

Хотелось узнать, можно ли пойти дальше. Прочитав это, я предположил, что получится дойти до 45 байт, однако — увы! — нет. Описанные там фокусы рассчитаны только на 32-битные бинарники, а для 64-битных не проходили.

Лучшее же, что мне удалось, — взять эту 64-битную версию программы и встроить в свой системный вызов:

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

Результирующий образ — 127 байт. На этом я прекратил попытки уменьшать размер, но принимаю предложения.

Крохотный Docker-образ

Теперь, когда есть бинарник, реализующий бесконечное ожидание, остаётся положить его в Docker-образ.

Чтобы сэкономить каждый возможный байт, я создал бинарник с файловым именем из одного байта — t — и поместил его в Dockerfile, создавая практически пустой образ:

FROM scratch
ADD t /t

Обратите внимание, что в Dockerfile нет CMD, поскольку это увеличило бы размер образа. Для запуска понадобится передавать команду через аргументы к docker run.

Получился портируемый файл Docker-образа размером менее 1000 байт: Далее командой docker save был создан tar-файл, а затем — сжат с максимальной компрессией gzip.

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

Ещё я попытался уменьшить размер tar-файла, экспериментируя с manifest-файлом Docker, но тщетно: из-за специфики формата tar и алгоритма сжатия gzip такие изменения приводили только к росту финального gzip'а. Пробовал и другие алгоритмы компрессии, но gzip оказался лучшим для этого маленького файла.

P.S. от переводчика

Читайте также в нашем блоге:


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Разминка для тех, кто умеет в Python

Мы обожаем квизы, посвященные языкам программирования. Привет! Сегодняшний квиз посвящен исключительно Python. В нашем блоге мы разместили уже целых три: первый — по Python, PHP, Golang, DevOps, второй — полностью по Go, третий — только по PHP. (Кстати, кто едет? ...

Финтех-дайджест: финансовым регуляторам нужен ИИ для того, чтобы работать в современных условиях

Сегодня в выпуске: Привет, Хабр! В США считают, что финансовым регуляторам необходимо использовать ИИ; Банки РФ обязали уведомлять ЦБ перед тем, как рассказывать прессе о произошедших хакерских атаках; Биткоин двинулся вперед, есть оптимистичные прогнозы; Несмотря на запрет биткоинов, в Китае ...