[Перевод] Как я уменьшил размер образа docker на 40%
Ежедневно по работе я сталкиваюсь с Dockerfile, написал несколько из них самостоятельно, создавал контейнеры и всё такое. Но я никогда не публиковал их в реестре docker hub. Мне хотелось создать ugit — инструмент для отмены команд git (написанный в виде шелл-скрипта), который бы могли использовать люди, не любящие устанавливать случайные шелл-скрипты из Интернета.
Да-да, знаю. НАДО ПЕРЕПИСАТЬ ЕГО НА GO/RUST/ЕЩЁ КАКОМ-ТО ВОЛШЕБНОМ ЯЗЫКЕ. Сейчас скрипт состоит из пятисот с лишним строк Bash, поэтому я перепишу его на другом языке только под страхом смертной казни. Кроме того, в ugit уже есть практически все функции (осталось реализовать отмену лишь небольшого количества команд, используемых не так часто).
В этой статье я расскажу о том, как написал официальный Dockerfile для ugit (шелл-скрипта) и снизил размер образа почти на 40% (с 31,4 МБ до 17,6 МБ), выполняя пошаговые попытки по инструкции. Надеюсь, это замотивирует других любителей шелла тоже публиковать свои скрипты в виде образов docker!
P.S.: Я не DevOps и не специалист по Docker, поэтому если вы найдёте ошибки или то, что можно сделать лучше, напишите мне. Готовый образ docker выложен на docker hub
Самая первая попытка создания Dockerfile
# Используем в качестве родительского образа официальную среду исполнения Alpine
FROM alpine:3.18
# Устанавливаем в качестве рабочей папки контейнера /app
WORKDIR /app
# Копируем содержимое текущей папки в /app контейнера
COPY . /app
# Устанавливаем зависимости
RUN apk add --no-cache
bash
gawk
findutils
coreutils
git
ncurses
fzf
# Задаём разрешения и перемещаем скрипт по пути
RUN chmod +x ugit && mv ugit /usr/local/bin/
# Запускаем ugit после запуска контейнера
CMD ["ugit"]
Выглядит довольно просто, правда? Так и есть. Вы можете скопипастить этот Dockerfile и собрать образ самостоятельно, если предварительно клонировали ugit в текущую папку,
docker build -t ugit .
docker run --rm -it -v $(pwd):/app ugit
ugit должен запуститься внутри контейнера.
ugit
требует двоичных файлов наподобие bash
(версии 4.0 и выше), awk
, xargs
, git
, fzf
, tput
, cut
, tr
, nl
.
Нам нужно установить findutils, потому что она поставляется вместе с
xargs
.Также нужно установить coreutils, потому что она поставляется с
tr
,cut
иnl
.ncurses
требуется дляtput
(которая используется для получения информации терминала).
Вот и всё, что нужно для запуска ugit на машине с UNIX-подобной операционной системой или в контейнере. На этом этапе размер образа составляет 31,4 МБ
. Неплохо для первой попытки. Давайте посмотрим, сможем ли мы ещё сильнее его уменьшить.
Путь к оптимизации — снижение размера образа на 40%
Во время наших (отчаянных) попыток микрооптимизаций ниже мы будем стремиться выполнить следующие высокоуровневые цели:
Использовать многоэтапные сборки для снижения размера образа.
Избавиться от двоичных файлов наподобие
sleep
,watch
,du
и так далее. Всего этого не требуется для запускаugit
.Избавиться от ненужных зависимостей, вносимых этими двоичными файлами.
Получить минимальную версию всех зависимостей, которые требуются для запуска
ugit
.Загружать в готовый образ только необходимые двоичные файлы и их зависимости.
Вторая попытка — alpine на alpine
Далее я решил создать многоэтапную сборку. Второй этап будет использоваться для того, чтобы копировать только необходимые двоичные файлы и их зависимости. Я снова выбрал в качестве базового образа для этого этапа alpine
.
# Первый этап: устанавливаем пакеты
FROM alpine:3.18 as builder
RUN apk add --no-cache
bash
gawk
findutils
coreutils
git
ncurses
fzf
# Копируем в /app контейнера только скрипт ugit
WORKDIR /app
COPY ugit .
# Задаём разрешения и перемещаем скрипт по пути
RUN chmod +x ugit && mv ugit /usr/local/bin/
# Второй этап: копируем только необходимые двоичные файлы и их зависимости
FROM alpine
COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/gawk /usr/bin/
COPY --from=builder /usr/bin/xargs /usr/bin
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/
WORKDIR /app
# Запускаем ugit при запуске контейнера
CMD ["ugit"]
Одной только простой многоэтапной сборкой мы смогли снизить размер образа до впечатляющих 20,6
МБ. Образ успешно собирается, но ugit пока не запускает.
Error loading shared library libreadline.so.8: No such file or directory (needed by /bin/bash)
Оказалось, что нам не хватает транзитивных зависимостей. Подробнее об этом мы расскажем в третьей попытке.
Похоже, xargs и awk мы получаем просто так?
Оказывается, и xargs
, и awk
по умолчанию есть в образе Alpine. В этом можно убедиться, выполнив следующие команды:
docker run -it alpine /bin/sh -c "awk --help"
docker run -it alpine /bin/sh -c "xargs --help"
Детская ошибка. Давайте уберём gawk
и findutils
из нашего Dockerfile
.
# Первый этап: установка пакетов
FROM alpine:3.18 as builder
RUN apk add --no-cache
bash
coreutils
git
ncurses
fzf
# Копируем в /app контейнера только скрипт ugit
WORKDIR /app
COPY ugit .
# Задаём разрешения и перемещаем скрипт по пути
RUN chmod +x ugit && mv ugit /usr/local/bin/
# Второй этап: копируем только необходимые двоичные файлы и их зависимости
FROM alpine:3.18
COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/
WORKDIR /app
# Запускаем ugit при запуске контейнера
CMD ["ugit"]
Размер образа снизился до 20 МБ
. Мы уже близко, но ugit по-прежнему не запускается.
Третья попытка — используем на втором этапе scratch
Этого большинство людей не пробует. Это довольно пугающе и требует большой отваги. Я тоже был немного испуган, пойдя по этому пути.
Образ docker SCRATCH — это просто пустая файловая система. В ней вообще ничего нет. Чтобы испытать образ SCRATCH, можно прочитать его README на docker hub.
Единственное, что вам нужно знать, так это то, что нам всё придётся складывать вручную. Скрестим пальцы и заменим alpine
на scratch
.
# Первый этап: установка пакетов
FROM alpine:3.18 as builder
RUN apk add --no-cache
bash
coreutils
git
ncurses
fzf
# Копируем в /app контейнера только скрипт ugit
COPY ugit .
# Задаём разрешения и перемещаем скрипт по пути
RUN chmod +x ugit && mv ugit /usr/local/bin/
# Второй этап: копируем только необходимые двоичные файлы и из зависимости
FROM scratch
COPY --from=builder /usr/local/bin/ugit /usr/bin/
COPY --from=builder /usr/bin/git /usr/bin/
COPY --from=builder /usr/bin/fzf /usr/bin/
COPY --from=builder /usr/bin/tput /usr/bin/
COPY --from=builder /usr/bin/cut /usr/bin/
COPY --from=builder /usr/bin/tr /usr/bin/
COPY --from=builder /usr/bin/nl /usr/bin/
COPY --from=builder /usr/bin/env /bin/
COPY --from=builder /bin/bash /bin/
WORKDIR /app
# Запускаем ugit после запуска контейнера
CMD ["ugit"]
При этом размер нашего образа снижается до 12,4 МБ
. Уменьшение на 60%? Мы что, обманули сами себя? Давайте попробуем запустить ugit.
$ docker run --rm -it -v $(pwd):/app ugit-a3
exec /usr/bin/ugit: no such file or directory
$ docker run --rm -it --entrypoint /bin/bash ugit-a4
exec /bin/bash: no such file or directory
Оказалось, мы сломали двоичный файл bash, не передав его зависимости. Давайте посмотрим, что можно с этим сделать.
Выявление транзитивных зависимостей
Настало время поговорить о транзитивных зависимостях. В нашем скрипте используются такие двоичные файлы, как git
, tput
, bash
; у некоторых из этих утилит есть свои зависимости.
Мы называемы эти зависимости общими библиотеками. Общие библиотеки — это файлы .so
(.dll
в Windows, .dylib
OS X). ldd — отличный инструмент для выявления таких зависимостей. Он составляет список всех библиотек, необходимых для исполнения двоичного файла. Например, если мы выполним ldd /bin/bash
в только что созданном контейнере Alpine, то получим следующий результат:
/ # ldd /bin/ls
/lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0xffff8c9c0000)
/ # ldd /bin/bash
/lib/ld-musl-aarch64.so.1 (0xffffb905a000)
libreadline.so.8 => /usr/lib/libreadline.so.8 (0xffffb8f06000)
libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0xffffb905a000)
libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0xffffb8e95000)
Знакомство с общими библиотеками
У каждой библиотеки есть soname, то есть имя файла библиотеки без номера версии. Они начинаются с префикса
lib
, а заканчиваются на.so
. Например,libpcre2-8.so.0
имеет sonamelibpcre2-8.so
.Полностью отвечающая требованиям библиотека включает папку, в которой она расположена. Например,
/usr/lib/libpcre2-8.so.0
.У каждой библиотеки есть реальное имя, то есть имя файла библиотеки с номером версии. Например, реальным именем может быть
libpcre2-8.so.0.3.6
.Шестнадцатеричные данные — это базовый адрес памяти, по которому эта библиотека загружается в память. Для нас эта информация бесполезна. Нам важны только имена библиотек. Давайте проведём это исследование для всех библиотек, которые нужны для запуска ugit.
Путь справа от символа
=>
обозначает реальный путь к общей библиотеке.Имя библиотеки, начинающееся с
libc
— это библиотека C для этой архитектуры. Помните ошибку «exec /bin/bash: no such file or directory»? Мы получаем её именно поэтому: отсутствует библиотека C для нашей архитектуры.
Ниже приведена цитата из статьи Дугласа Кригера о версиях общих библиотек, в которой описываются общие библиотеки. Если вы хотите знать о них больше, то рекомендую прочитать статью полностью.
При использовании общей библиотеки она компилируется один раз, а затем устанавливается в общее местоположение в файловой системе (в Linux-системах это обычно
/usr/lib
). Любой проект, который зависит от этой общей библиотеки, может использовать эту общую уже скомпилированную реализацию без изменений.
Большинство дистрибутивов Linux снижают время компиляции, распространяя двоичные пакеты популярных библиотек; система упаковки дистрибутива заранее компилирует код. При установке пакета вы скачиваете копию скомпилированной библиотеки (желательно подписанную) и помещаете её в общее местоположение; при этом ни на одном из этапов не требуется запуск компилятора (и любой другой части цепочки сборки, создавшей библиотеку).
Мы используем схожий подход для выявления всех уникальных зависимостей всех двоичных файлов, которые нам необходимы для запуска ugit.
Мы сделали это, выполнив команду вида
for cmd in /bin/; do echo $cmd; ldd $cmd; done
# копируем файлы библиотек
COPY --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/
COPY --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/
COPY --from=ugit-ops /usr/lib/libpcre /usr/lib/
COPY --from=ugit-ops /usr/lib/libreadline* /usr/lib/
COPY --from=ugit-ops /lib/libacl.so.1 /lib/
COPY --from=ugit-ops /lib/libattr.so.1 /lib/
COPY --from=ugit-ops /lib/libc.musl-* /lib/
COPY --from=ugit-ops /lib/ld-musl-* /lib/
COPY --from=ugit-ops /lib/libutmps.so.0.1 /lib/
COPY --from=ugit-ops /lib/libskarnet.so.2.13 /lib/
COPY --from=ugit-ops /lib/libz.so.1 /lib/
Обратите внимание, что мы копируем из образа сборщика libc.musl-
и ld-musl-
, поэтому сборка для этих библиотек зависит от архитектуры машины-хоста.
Шебанги #! бесполезны
Если взглянуть на самую первую строку ugit, то можно увидеть шебанг #!/usr/bin/env bash
. Использование env
считается хорошей практикой при написании скриптов, это позволяет сообщить операционной системе, какой интерпретатор шелла следует использовать для запуска скрипта; это идеально в случае стандартной машины для разработки. Linux (и старые версии macOS) поставляются с sh
, bash
, а пользователи также устанавливают zsh
и так далее.
Но поскольку использование шебангов необязательно и мы уже скопировали двоичный файл bash
, нам нужно просто вызвать наш скрипт при помощи него. Это ещё и сэкономит нам пару байтов в размера образа. Если точнее, почти 1,9 МБ.
# Запускаем ugit при запуске контейнера
CMD ["/bin/bash", "/bin/ugit"]
# or
# ENTRYPOINT ["/bin/bash", "/bin/ugit"]
Изучим базу данных terminfo
В ugit благодаря tput
есть цвета. Мы загружаем «голый» образ Alpine с bash
и переходим в папку /etc/terminfo
. В этой папке содержится база данных информации терминала.
37a1a77f70ed:/app# cd /etc/terminfo/
37a1a77f70ed:/etc/terminfo# ls
a d g k l p r s t v x
Каждая из этих буквенных «папок» представляет отдельный тип $TERM
. Например, xterm
— это тип терминала. Если запустить на вашей локальной машине tput -T xterm colors
, то вы получите количество поддерживаемых терминалом цветов. Для xterm
оно должно равняться 8
, а в случае xterm-256color
— 256
.
У нас есть шанс обеспечить поддержку только одного типа терминала из более чем сорока, представленных в базе данных terminfo
. От остальных типов терминалов можно избавиться. Это сэкономит нам ещё 97 КБ — мелочь, но необходимая для разборки беспорядка.
# Копируем базу данных terminfo только для xterm-256color
COPY --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/
# Используем все цвета
ENV TERM=xterm-256color
Всё, что необходимо для запуска ugit
Окончательный образ Docker имеет размер 17,6 МБ и не содержит уязвимостей безопасности (в соответствии с отчётом docker scout на момент написания статьи). Мы успешно уменьшили размер образа на 40%.
Вот готовый Dockerfile:
FROM alpine:3.18 as ugit-ops
RUN apk add --no-cache
bash
coreutils
git
ncurses
curl
# Скачиваем двоичный файл fzf с GitHub, придерживаемся версии 0.46.0, ugit требует минимум 0.21.0
RUN curl -L -o fzf.tar.gz https://github.com/junegunn/fzf/releases/download/0.46.0/fzf-0.46.0-linux_amd64.tar.gz &&
tar -xzf fzf.tar.gz &&
mv fzf /usr/bin/
# Копируем в /app контейнера только скрипт ugit
COPY ugit .
# Задаём разрешения и перемещаем скрипт по пути
RUN chmod +x ugit && mv ugit /usr/bin/
# Второй этап: копируем только необходимые двоичные файлы и их зависимости
FROM scratch
COPY --from=ugit-ops /usr/bin/ugit /bin/
COPY --from=ugit-ops /usr/bin/git /usr/bin/
COPY --from=ugit-ops /usr/bin/fzf /usr/bin/
COPY --from=ugit-ops /usr/bin/tput /usr/bin/
COPY --from=ugit-ops /usr/bin/nl /usr/bin/
COPY --from=ugit-ops /usr/bin/awk /usr/bin/
COPY --from=ugit-ops /usr/bin/xargs /usr/bin/
COPY --from=ugit-ops /usr/bin/cut /usr/bin/cut
COPY --from=ugit-ops /usr/bin/tr /usr/bin/tr
COPY --from=ugit-ops /bin/bash /bin/
COPY --from=ugit-ops /bin/sh /bin/
# Копируем файлы библиотек
COPY --from=ugit-ops /usr/lib/libncursesw.so.6 /usr/lib/
COPY --from=ugit-ops /usr/lib/libncursesw.so.6.4 /usr/lib/
COPY --from=ugit-ops /usr/lib/libpcre* /usr/lib/
COPY --from=ugit-ops /usr/lib/libreadline* /usr/lib/
COPY --from=ugit-ops /lib/libacl.so.1 /lib/
COPY --from=ugit-ops /lib/libattr.so.1 /lib/
COPY --from=ugit-ops /lib/libc.musl-* /lib/
COPY --from=ugit-ops /lib/ld-musl-* /lib/
COPY --from=ugit-ops /lib/libutmps.so.0.1 /lib/
COPY --from=ugit-ops /lib/libskarnet.so.2.13 /lib/
COPY --from=ugit-ops /lib/libz.so.1 /lib/
# Копируем базу данных terminfo
COPY --from=ugit-ops /etc/terminfo/x/xterm-256color /usr/share/terminfo/x/
# Используем все цвета
ENV TERM=xterm-256color
WORKDIR /app
# Запускаем ugit при запуске контейнера
CMD ["/bin/bash", "/bin/ugit"]
Я решил зафиксироваться на версии fzf
0.46.0
(последней на момент написания этой статьи), потому что для запуска ugit требуется минимум 0.21.0
, и я подумал: какого чёрта, давай привяжемся к последней версии.
Контейнер ugit запускается командой
docker run --rm -it -v $(pwd):/app ugit
. Убедитесь, что ваша текущая папка — это репозиторий git.
Вот, как должно выглядеть окончательное дерево файловой системы:
Permission UID:GID Size Filetree
drwxr-xr-x 0:0 0 B ├── app
drwxr-xr-x 0:0 886 kB ├── bin
-rwxr-xr-x 0:0 866 kB │ ├── bash
-rwxr-xr-x 0:0 21 kB │ └── ugit
drwxr-xr-x 0:0 1.9 MB ├── lib
-rwxr-xr-x 0:0 658 kB │ ├── ld-musl-aarch64.so.1
-rwxr-xr-x 0:0 67 kB │ ├── libacl.so.1
-rwxr-xr-x 0:0 67 kB │ ├── libattr.so.1
-rwxr-xr-x 0:0 658 kB │ ├── libc.musl-aarch64.so.1
-rwxr-xr-x 0:0 265 kB │ ├── libskarnet.so.2.13
-rwxr-xr-x 0:0 67 kB │ ├── libutmps.so.0.1
-rwxr-xr-x 0:0 133 kB │ └── libz.so.1
drwxr-xr-x 0:0 15 MB └── usr
drwxr-xr-x 0:0 12 MB ├── bin
-rwxr-xr-x 0:0 919 kB │ ├── awk
-rwxr-xr-x 0:0 1.2 MB │ ├── cut
-rwxr-xr-x 502:20 3.6 MB │ ├── fzf
-rwxr-xr-x 0:0 3.0 MB │ ├── git
-rwxr-xr-x 0:0 1.2 MB │ ├── nl
-rwxr-xr-x 0:0 67 kB │ ├── tput
-rwxr-xr-x 0:0 1.2 MB │ ├── tr
-rwxr-xr-x 0:0 919 kB │ └── xargs
drwxr-xr-x 0:0 2.8 MB ├── lib
-rwxr-xr-x 0:0 396 kB │ ├── libncursesw.so.6
-rwxr-xr-x 0:0 396 kB │ ├── libncursesw.so.6.4
-rwxr-xr-x 0:0 592 kB │ ├── libpcre2-8.so.0
-rwxr-xr-x 0:0 592 kB │ ├── libpcre2-8.so.0.11.2
-rwxr-xr-x 0:0 67 kB │ ├── libpcre2-posix.so.3
-rwxr-xr-x 0:0 67 kB │ ├── libpcre2-posix.so.3.0.4
-rwxr-xr-x 0:0 351 kB │ ├── libreadline.so.8
-rwxr-xr-x 0:0 351 kB │ └── libreadline.so.8.2
drwxr-xr-x 0:0 4.0 kB └── share
drwxr-xr-x 0:0 4.0 kB └── terminfo
drwxr-xr-x 0:0 4.0 kB └── x
-rw-r--r-- 0:0 4.0 kB └── xterm-256color
И это всё, что нужно для работы нашего приложения шелла.
P.S.: Окончательный размер образа docker снизился до 16,2 МБ. Обновлённый Dockerfile можно найти здесь. Чтобы не редактировать статью, я оставил размер образа равным 17,6 МБ.
Можно ли ещё уменьшить размер?
Да, но есть две причины, по которым мы не стали двигаться дальше:
Причина 1: фиксация минимальной версии fzf?
На момент написания ugit этот скрипт использовал fzf
с минимальной версией 0.20.0
. Очевидно, что последняя версия будет больше, чем минимально требуемая. Так что, наверно, стоит придерживаться старой версии? Нет, потому что тогда появляются уязвимости безопасности из-за зависимостей fzf, например, Golang. Как сообщает docker scout quickview
, старая версия Golang имеет 66 проблем с безопасностью. Может, они повлияют на образ, может, и нет. Но я не хочу так рисковать и стремлюсь сделать образ максимально защищённым.
Проблемы с безопасностью в Golang
В экосистеме Alpine в общем случае не рекомендуется привязываться к минимальным версиям пакетов.
Причина 2: использование новых функций bash?
На момент написания ugit я использовал bash
версии 4.0
. Если бы я перешёл на более новую версию bash, например, 5.0, то и tr, и cut можно было бы убрать. Но это, друзья мои, изменение, нарушающее обратную совместимость. Избавившись от них, я бы сэкономил пару байтов, но я не хотел бы, чтобы скрипт перестал работать у пользователей, по-прежнему работающих с bash 4.0. Пусть даже я только один такой, на моей машине всё ещё есть bash.
Почему ты не попробовал docker-slim?
Я пробовал и уменьшил образ, но это поломало скрипт отсутствующими зависимостями. Slim — отличная вещь, вам стоит его изучить. К сожалению, мне не удалось заставить работать его с ugit за приемлемое время.
Кроме того, я хотел научиться делать это собственными руками, а не полагаться на инструмент.
Почему ты не попробовал docker-squash?
Я пробовал, но оптимизация размера оказалась почти незаметной. Вот лог процесса squash, который я запускал на своей машине с Linux (AMD) (не обращайте внимания на размер образа; так как мы на другой архитектуре, размер отличается):
2024-01-26 19:53:30,292 root INFO docker-squash version 1.1.0, Docker 20.10.22, API 1.41...
2024-01-26 19:53:30,293 root INFO Using v2 image format
2024-01-26 19:53:30,310 root INFO Old image has 30 layers
2024-01-26 19:53:30,310 root INFO Checking if squashing is necessary...
2024-01-26 19:53:30,310 root INFO Attempting to squash last 30 layers...
2024-01-26 19:53:30,310 root INFO Saving image sha256:1b9107b09dec50bd8134a935ad58ef07deb7dc2a639804abc2cb3d47c1e74206 to /tmp/docker-squash-20kl1kbx/old directory...
2024-01-26 19:53:30,536 root INFO Image saved!
2024-01-26 19:53:30,537 root INFO Squashing image 'ugit:latest'...
2024-01-26 19:53:30,538 root INFO Starting squashing...
2024-01-26 19:53:30,538 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/3b03ba6aad8ab3b95e5ae7c173b5fe8980a4bb2b57e6e3c78d4c851e62befacd/layer.tar'...
2024-01-26 19:53:30,539 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/af8614dc670d37bc2b4ee235fba1fba32f79cc665e1685d17bc09103206add88/layer.tar'...
2024-01-26 19:53:30,541 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/fdfcf6e294b868ac5c9bb79f479da7365f074a24932d35cf88e6ee87313be39f/layer.tar'...
2024-01-26 19:53:30,542 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/fea7bea22fc0e61f8b3bb33795946844efca8e6e2ece2d4cfb5d9d5dc50fa29c/layer.tar'...
2024-01-26 19:53:30,543 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/c6b22f335e663d959cf9ecb676d297de3b818c608c10811f857fbd0950d39126/layer.tar'...
2024-01-26 19:53:30,543 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/482d23995758cbffbedc338a3be0325106fc6172a7c54d23a22e8a1d585d2b9e/layer.tar'...
2024-01-26 19:53:30,545 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/3b9a86d49326da225c5b1c85e01764d210c15bec579c5af34f0fc171127f5b5f/layer.tar'...
2024-01-26 19:53:30,546 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/0ca82f27b278a4c9bb0bfe1ff3f38716051fb4849374eec8925f48efe436009b/layer.tar'...
2024-01-26 19:53:30,546 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/3cca8b35bff33c3f212c956b4c71dd419bd86c146ec38afdcdd5a0288d1659b6/layer.tar'...
2024-01-26 19:53:30,547 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/81dfb23402548ba22dff2162d2c4291d16e3553a3fc47b761d21013316571a82/layer.tar'...
2024-01-26 19:53:30,548 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/273d7b9c894ca049315a77ce845423f819ee684544f171eaf6b8689c77f6ad05/layer.tar'...
2024-01-26 19:53:30,550 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/27be6a1ed5a8507dc43bd30cb56ddddc9c0c9d52b6806eec8037f1fa09395552/layer.tar'...
2024-01-26 19:53:30,551 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/d5f577b8ef6ec1875b1d986ca79da0339c1d228cc31bce4d42988556135c5909/layer.tar'...
2024-01-26 19:53:30,551 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/f292c627dcee37256b008f33c3bc91c65463e7b8a6758306c130f53d80b045e5/layer.tar'...
2024-01-26 19:53:30,553 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/41a4f2afaf2abd9098d62d9b4d5c6ee88c2d9478f48be9c8f407a81c7b207d6e/layer.tar'...
2024-01-26 19:53:30,555 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/5dc1b8c25faa63c58a7905850043c14c59dced4746d7d34453edad7b86956849/layer.tar'...
2024-01-26 19:53:30,557 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/f91a2a9df95e299850a3c512d292167e12f7df86dfc6a598279d35db4d804b7d/layer.tar'...
2024-01-26 19:53:30,560 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/72447920860e9e88f3680623d2ac1d10f78111f16c3563b43009852d704d7515/layer.tar'...
2024-01-26 19:53:30,563 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/cc9b36b470edfc560a35e67fa7d3adbcf3fe91acfaf82cd484080418a1168c7f/layer.tar'...
2024-01-26 19:53:30,566 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/05a8e77bfb6dd759bacb2ba9c1505bc66979885591d623d83ce58cbe0047a539/layer.tar'...
2024-01-26 19:53:30,568 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/a3452a5b414ce5be1c02a8d88374419f8de637552486ca47d6d810adc351386e/layer.tar'...
2024-01-26 19:53:30,570 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/38af27767b34ce2676958ed305d78508fe745597b548d65b4a5532481ffd3296/layer.tar'...
2024-01-26 19:53:30,570 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/c2e188390cb6b51470e879b62e2686add024114bcb76eb163053382cb424928a/layer.tar'...
2024-01-26 19:53:30,574 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/d03cc62007f0fa8d1c09d0f34f5e74e0e6f65f48c9a53177b189c57f47be8074/layer.tar'...
2024-01-26 19:53:30,578 root INFO Squashing file '/tmp/docker-squash-20kl1kbx/old/fa90033ac9d3c9085ba57fc406aed549a0dc14823c8f63f6358c9557b8f0a9ea/layer.tar'...
2024-01-26 19:53:30,578 root INFO Squashing finished!
2024-01-26 19:53:30,608 root INFO Original image size: 17.34 MB
2024-01-26 19:53:30,608 root INFO Squashed image size: 17.29 MB
2024-01-26 19:53:30,608 root INFO Image size decreased by 0.32 %
2024-01-26 19:53:30,608 root INFO New squashed image ID is 2c697df9cb2d9fd5f4534e6358d68f1e1a04bb5b6d27087f894dbf492964b791
2024-01-26 19:53:30,741 root INFO Image registered in Docker daemon as ugit-squashed:squashed
2024-01-26 19:53:30,746 root INFO Done
Выводы
Linux — потрясающая операционная система. Всё в нём, каждое архитектурное решение, каждый инструмент наследует это великолепие.
Никогда не бойтесь залезать в кроличьи норы микрооптимизаций, вы многому научитесь. Всегда есть что-то новое, чему можно научиться.
Благодарности
Огромное спасибо авторам
ldd
и всем участникам сообществdocker
иalpine
.Благодарю ребят из дискорда developersIndia за помощь советами и рекомендациями.
Инструменту dive за помощь в визуализации слоёв образа.