[отсылка к американской детской сказке "Маленький паровозик, который верил в себя " ("The Little Engine That Could") — прим. пер.]*
Как автомагически создавать крохотные docker-образы для своих нужд
Необычная одержимость
Последние пару месяцев я был одержим навязчивой идеей: насколько можно уменьшить образ Docker, так чтобы при этом приложение работало?
Понимаю, идея странная.
Прежде чем углубиться в детали и технические дебри, я бы хотел пояснить, чем эта проблема так меня зацепила, и как она касается вас.
Почему размер имеет значение
Сокращая содержимое образа Docker, мы тем самым сокращаем список уязвимостей. Дополнительно мы делаем образы чище, ведь они содержат только то, что нужно для запуска приложений.
Есть еще одно небольшое преимущество — образы скачиваются чуточку быстрее, но, как по мне, это не столь важно.
Обратите внимание: Если вас заботит размер, образы Alpine сами по себе малы и наверняка подойдут вам.
Distroless-образы
Проект Distroless предлагает подборку базовых "distroless"-образов, они не содержат менеджеров пакетов, оболочек и прочих утилит, которые вы привыкли видеть в командной строке. В результате использовать менеджеры пакетов вроде pip
и apt
не получится:
FROM gcr.io/distroless/python3
RUN pip3 install numpy
Dockerfile, использующий distroless-образ Python 3
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM gcr.io/distroless/python3
---> 556d570d5c53
Step 2/2 : RUN pip3 install numpy
---> Running in dbfe5623f125
/bin/sh: 1: pip3: not found
Pip в образе нет
Обычно такая проблема решается путем многоэтапной сборки:
FROM python:3 as builder
RUN pip3 install numpy
FROM gcr.io/distroless/python3
COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.5/
Многоэтапная сборка
В результате получается образ размером 130МВ. Не так уж и плохо! Для сравнения: образ Python по умолчанию весит 929МВ, а "похудевший" (3,7-slim
) — 179МВ, образ alpine (3,7-alpine
) — 98,6MB, тогда как базовый distroless-образ, использованный в примере, — 50,9МВ.
Можно справедливо указать на то, что в предыдущем примере мы копируем целый каталог /usr/local/lib/python3.7/site-packages
, в котором могут лежать ненужные нам зависимости. Хотя ясно, что разница в размерах всех существующих базовых образов Python колеблется.
На момент написания этих строк Google distroless поддерживает не так много образов: Java и Python — еще на стадии эксперимента, а Python существует только для 2,7 и 3,5.
Крохотные образы
Вернемся к моему помешательству на создании небольших образов.
Вообще я хотел посмотреть, как устроены distroless-образы. Проект distroless использует Google'овский инструмент сборки bazel
. Однако, чтобы устновить Bazel и написать собственные образы, пришлось попотеть (а если быть до конца честным, то заново изобретать колесо — это весело и познавательно). Хотелось упростить создание уменьшенных образов: акт создания образа должен быть предельно простым, банальным. Чтобы никаких тебе файлов конфигурации, только одна строка в консоли: просто собрать образ для <приложение>
.
Итак, если хотите создавать собственные образы, то знайте: есть такой уникальный образ docker, scratch
. Scratch — это "пустой" образ, в нем нет файлов, хотя он и весит по умолчанию — ого! — 77 байт.
FROM scratch
Образ scratch
Идея образа scratch в том, что можно скопировать в него любые зависимости с машины-хоста и либо использовать их внутри Dockerfile (это как скопировать их в apt
и установить с нуля), либо позднее, когда образ Docker материализован. Это позволяет полностью контролировать содержимое контейнера Docker, и, таким образом, полностью же контролировать размер образа.
А теперь нам нужно как-то собрать эти зависимости. Существующие инструменты вроде apt
позволяют скачивать пакеты, но они привязаны к текущей машине и, в конце концов, не поддерживают Windows или MacOS.
И вот я взялся собрать собственный инструмент, который автоматически собирал бы базовый образ наименьшего возможного размера и чтобы тот еще запускал любое приложение. Использовал я пакеты Ubuntu/Debian, делал выборку (получая пакеты прямиком из репозиториев) и рекурсивно находил их зависимости. Программа должна была автоматически скачивать новейшую устойчивую версию пакета, максимально снижая риски для безопасности.
Интрумент я назвал fetchy
, потому что он… находит и приносит… что нужно [от англ. "fetch", "приносить" — прим. пер.]. Инструмент работает через интерфейс командной строки, но в то же время предлагает и API.
Для того, чтобы собрать образ при помощи fetchy
(возьмем на этот раз образ Python), вам надо лишь использовать CLI вот так: fetchy dockerize python
. У вас могут запросить целевую операционную систему и кодовое имя, поскольку fetchy
пока использует только пакеты на базе Debian и Ubuntu.
Теперь можно выбирать, какие зависимости совсем не нужны (в нашем контексте) и исключить их. Например, Python зависит от perl, хотя прекрасно работает без установленного Perl.
Результаты
Образ Python, созданный с помощью команды fetchy dockerize python3.5
весит всего 35МВ (я более чем уверен, что в будущем его можно будет облегчить еще больше). Выходит, с distroless-образа удалось "сбрить" еще 15МВ.
Все собранные на данный момент образы посмотреть можно здесь.
Проект — тут.
Если вам не хватает функций, просто создайте заявку — буду рад помочь :) Даже больше, я в данный момент работаю над интеграцией в fetchy других пакетных менеджеров, так чтобы надобность в многоэтапных сборках отпала.