company_banner

Крохотные образы Docker, которые верили в себя*

Автор оригинала: Thomas Kluiters
  • Перевод

[отсылка к американской детской сказке "Маленький паровозик, который верил в себя " ("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 других пакетных менеджеров, так чтобы надобность в многоэтапных сборках отпала.

Southbridge
567,99
Обеспечиваем стабильную работу серверов
Поделиться публикацией

Похожие публикации

Комментарии 15

    +5
    Извините, не удержусь - названием статьи навеяло

      0

      Особенно удобным в этом случае выглядит инвентаризация установленных зависимостей — сторонних библиотек, в которых так же могут быть уязвимости и от которых [вынужденно] зависит "основной" пакет...

        +3
        Обсуждая размеры докер-образов все рекомендуют использовать Alpine Linux. А как в этих образах обстоят дела с производительностью? Читал в интернете отзывы, и сам проверял, что, например Python, там работает заметно медленнее, чем в образе на базе Ubuntu. В качестве объяснения этого приводились такие варианты:
        — все пакеты в Alpine Linux компилируются с оптимизацией по памяти и размеру, а не по скорости;
        — в библиотеке musl методы для работы с памятью (выделение и освобождение) работают медленнее чем в glibc.

        Ну и в контексте питона, кроме скорости, есть ещё одна заморочка — нет возможности устанавливать бинарные сборки питонячих пакетов из PyPi, т.к. они все собраны под glibc. Приходится их собирать самому из исходников.
        +5
        насколько можно уменьшить образ Docker, так чтобы при этом приложение работало?

        Эмм… Минимальный образ состоит из статически слинкованного бинаря приложения и более ничего. Я чего-то недопонимаю?

        • НЛО прилетело и опубликовало эту надпись здесь
            +1
            Неправда, Go нынче модно, а он в статику все собирает.
          +1

          Зачастую во всяких scratch образах потом тяжело с отладкой, когда надо зайти в shell поковырять там.
          И если несколько приложений используют скажем один общий python образ, то ведь он кешируется, а место занимают только само приложение и его зависимости.
          И всё это стоит за каким-нибудь балансером или гейтвеем, что усложняет использование уязвимостей самого образа.
          Зато проблем со сборкой и компиляцией на том же python alpine based добавляется много.


          Тонкий образ имеет смысл для для приложений на go, там один бинарник и ковырять из shell нечего.

            0
            Java 8 размером около ~60 MB, красиво.
            Спасибо, попробую)
              0
              Для контроля размера Docker образа можно использовать docker-image-size-limit
                0
                А ещё через три года доцкерофилы поймут, что можно выкинуть и сам доцкер и получится не хуже.

                Ура, циклическому «развитию» ИТ.
                  +2

                  Люди фигнёй занимаются. Преждевременной оптимизацией. Внезапно выясняется, что уменьшение размера образа не проходит бесследно. Например, ffmpeg под alpine частично работоспособен. И крашится при определенном наборе аргументов. Есть нюансы со средой выполнения. Ну, там всякие TZDATA, LOCALE. И время разработчика, который делает эту микрооптимизацию существенно дороже, чем выгоды от нее. Конечно, речь не идёт прр кейсы, когда у вас образы по 5ГиБ и вы их удали до 500 мб и сэкономили терабайты трафика.


                  Несомненно — я не спорю с очевидными тезисами. Вроде меньше образ — меньше поверхность атаки и поле для уязвимостей. Я — за здравый и осознанный подход в каждом случае.

                    +1
                    имхо, если уж начинать заниматься безопасностью Docker, то лучше начать с Docker Bench for Security.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое