Это короткая история про то, как я захотел упаковать небольшое приложение на Python в бинарный файл, а затем сделать из этого такой же небольшой образ Docker.


А когда говорят "небольшой образ" обычно подразумевают, что первой строкой в Dockerfile будет "FROM alpine". Но вот загвоздка - Alpine Linux использует musl, а PyInstaller использует glibc и, если просто взять и собрать бинарный пакет, то вы получите ожидаемый при таких условиях результат. И что же делать? Вот именно это я и хочу рассказать.

TL;DR

Классный парень Mike Thornton aka six8 все сделал за нас и собрал образ, с помощью которого можно довольно просто получить из Python-файла файл исполняемый. Так что, если вам нужно один раз собрать и забыть, то можете воспользоваться его образом и дальше не читать.

Но я хочу свой Dockerfile

Поэтому я нашел магию, которую Майк использовал для создания Alpine-совместимого бинарного файла. Нам интересен вот этот отрывок из его Dockerfile:

RUN apk add --update \
    binutils python3 python3-dev git gcc musl-dev libc-dev libffi-dev zlib-dev && \
    # Build bootloader for alpine
    git clone https://github.com/pyinstaller/pyinstaller.git /tmp/pyinstaller \
    && cd /tmp/pyinstaller/bootloader \
    && CFLAGS="-Wno-stringop-overflow" python3 ./waf configure --no-lsb all \
    && pip3 install .. \
    && rm -Rf /tmp/pyinstaller

Здесь устанавливается все, чтобы собрать свой PyInstaller с загрузчиком для Alpine Linux, собирается и ставится пакет, удаляется временная директория.
После этого мы можем, используя модифицированный PyInstaller, собрать наш бинарный файл. Как-то так:

RUN pip3 install -r requirements.txt && \
    pyinstaller \
    --noconfirm \
    --onefile \
    --log-level DEBUG \
    --clean \
    my_app.py

Собранный файл мы сможем найти в поддиректории dist.

Зачем?

Docker прочно ассоциируется в моем сознании со словосочетанием "Microservice Architecture" и 100Gb "микросервис" рвет в клочья все мои шаблоны. Я стараюсь делать свои образы как можно меньше. Может кому-то проще будет вникнуть на живом примере. Вот наглядный пример, где я запаковал Prometheus Jenkins Exporter с помощью техники, описанной в этой заметке. В итоге я сократил размер конечного образа более, чем в шесть раз, по сравнению с оригинальным.