В этой статье будет использоваться программа Node, чтобы показать, как оптимизировать образ Docker (идея оптимизации является общей, независимо от программы), в основном для решения проблемы чрезмерного размера образа и скорости создания образов CI/CD. как шаг за шагом оптимизировать Dockerfile.Сначала лайкните, а потом смотрите, после прочтения можно снять лайк 😅. Оптимизированные результаты следующие:
- Размер от 1,06 Гб до 73,4 Мб
- Скорость сборки с 29,6 секунды до 1,3 секунды(по сравнению со скоростью второго билда)
Узел проекта
Я просто написал один для собственного использованияwechat-bot, а затем используйте этот проект, чтобы продемонстрировать, как оптимизировать образ Docker.
Вот Dockerfile, который я только начал писать, не заглядывая в Docker
FROM node:14.17.3
# 设置环境变量
ENV NODE_ENV=production
ENV APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 把当前目录下的所有文件拷贝到镜像的工作目录下 .dockerignore 指定的文件不会拷贝
COPY . $APP_PATH
# 安装依赖
RUN yarn
# 暴露端口
EXPOSE 4300
CMD yarn start
После сборки, как показано ниже, мой простой образ программы Node имеет1GБольше, тогда мы будем постепенно оптимизировать, чтобы уменьшить этот размер
Предисловие к оптимизации
Прежде чем приступать к оптимизации, мы должны кое-что понять. Первый шаг в решении проблемы — выяснить, что вызывает проблему.
-
Файл Dockerfile содержит инструкции одну за другой, каждая инструкция создает слой, поэтому содержимое каждой инструкции должно описывать, как создается слой.
-
Образ Docker — это не просто файл, а набор файлов, наиболее важными из которых являютсяПол(Слои)
-
Когда образ построен, он будет строиться слой за слоем, и предыдущий слой является основой следующего слоя.
После построения каждого слоя он не изменится, и любые изменения на следующем слое будут происходить только с его собственным слоем. Например, операция удаления файла на предыдущем уровне фактически не удаляет файл на предыдущем уровне, а только помечает файл как удаленный на текущем уровне. При запуске финального контейнера, хотя этот файл и не будет виден, на самом деле файл всегда будет следовать за изображением
-
Слой изображения будет закэширован и повторно использован (по этой же причине скорость будет выше, когда изображение строится во второй раз, и принцип оптимизации скорости построения изображения также выполняется по принципу кеширования)
-
Когда инструкции Dockerfile изменены, рабочие файлы изменены или переменные, указанные при построении образа, отличаются, соответствующий кеш слоя изображения будет недействительным.
Механизм кэширования сборки докеров, как докер узнает об изменениях файла?
Стратегия, принятая Docker, заключается в получении содержимого Dockerfile (включая некоторую информацию об индексном узле файла) и вычислении уникального хеш-значения.Если хеш-значение не изменилось, можно считать, что содержимое файла не изменилось. изменены, и можно использовать механизм кэширования, и наоборот.
-
После того, как зеркальный кеш определенного слоя станет недействительным, кеш зеркального слоя после него будет недействительным.
-
Каждый слой образа записывает только изменения файла.При запуске контейнера Docker вычисляет каждый слой образа и, наконец, генерирует файловую систему.
Когда я узнал об этом, я вдруг понял, что используемая нами операционная система, такая как Android, ios, win, mac и т. д., на самом деле является файловой системой, а наше взаимодействие с программным интерфейсом на самом деле представляет собой чтение и запись файлов. , работающий дом, это чтение и запись локальных файлов или чтение и запись данных в памяти. Я не знаю, верны ли какие-то мои личные мнения. Я фронтенд-кодировщик, который не является профессиональным кодером 😅
Использованная литература:Принцип многоуровневого образа Docker
-
хорошо, мы уже знаем, что образ состоит из многослойных файловых систем, чтобы оптимизировать его размер, нам нужно уменьшить количество слоев, каждый слой должен содержать только то, что нужно слою, все дополнительные вещи должны быть построены в конце. этого слоя. Очистите перед, начните текст ниже
Оптимизировать Dockerfile
Оптимизируйте первый слойFROM node:14.17.3
Вариант 1. Используйте версию узла Alpine.
Это также метод оптимизации зеркалирования, известный большинству людей. Alpine — это небольшой дистрибутив Linux. Пока вы выбираете версию Node для Alpine, будут большие улучшения. Мы превращаем это предложение в инструкцию и меняем его наFROM node:14.17.4-alpine
(ты можешь пойти вdockerhubПроверьте, какие теги версии есть у узла), размер изображения после сборки, как показано на рисунке ниже, мгновенноПадение с 1,06G до 238M, можно сказать, что эффект значительный
Вы также можете использовать другие основные маленькие зеркала, такие какmhart/alpine-node, это может быть меньше, измените его наFROM mhart/alpine-node:14.17.3
Попробуйте еще раз, вы можете видеть, что он меньше5M 😂 хоть и не много, но придерживаясь "принципа босса" можно немного выжимать, много складывать, а крайности выжимать
Вариант 2. Установите Node вручную, используя чистый образ Alpine.
Поскольку Alpine — самый маленький Linux, давайте попробуем использовать чистый образ Alpine и попробуем установить Node самостоятельно.
FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0
# ... 后面的步骤不变
После сборки посмотрите на картинку ниже, только174MДа намного меньше
Вывод состоит в том, что не так уж сложно стремиться к конечному и использовать второй план.Уменьшено с 1,06G до 174M
Уменьшите количество слоев и обратитесь к слоям, которые не меняются часто.
-
ENV
Инструкция может одновременно задавать несколько переменных среды. Если инструкция может быть выполнена за один раз, ее не нужно выполнять дважды. Еще одна инструкция означает еще один слой. -
EXPOSE
Команда выставить порт.На самом деле вам не нужно писать эту команду.Вы можете сами сопоставить порт при запуске контейнера.Если вы напишете эту команду,потому что порт не часто меняется,вы можете продвинуться эту команду и напишите эту команду.Есть два преимущества:- Помогите пользователям зеркала понять порт демона этой службы зеркала, чтобы упростить сопоставление конфигурации.
- При использовании случайного сопоставления портов во время выполнения, т.е.
docker run -P
, он будет автоматически случайным образом отображатьEXPOSE
порт
Что касается того, писать или не писать, это зависит от человека, я обычно не пишу, потому что я буду указывать порт проекта в команде запуска проекта и отображать его при запуске контейнера, поэтому я должен поддерживать место, если в Dockerfile тоже написано, Если изменился порт проекта, его нужно изменить здесь, что увеличит стоимость обслуживания.Конечно, есть способ сделать переменные портов на обеих сторонах из файла конфигурации, до тех пор, пока файл конфигурации не будет изменен.
Ниже приведен переписанный Dockerfile
FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0
# 暴露端口
EXPOSE 4300
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 把当前目录下的所有文件拷贝到镜像的工作目录下 .dockerignore 指定的文件不会拷贝
COPY . $APP_PATH
# 安装依赖
RUN yarn
# 启动命令
CMD yarn start
На этом шаге оптимизации не видно никакой очевидной разницы с точки зрения размера изображения или скорости построения изображения, потому что измененное содержимое слоя небольшое (не отражено), но видно, что слой изображения уменьшен, вы можете попробовать. самостоятельно Посмотреть зеркальные слои попробовать
Уменьшение количества слоев изображения — традиционная и хорошая привычка «хороших начальников», а «сотрудникам» не позволено тратить ресурсы впустую.
package.json ускоряет компиляцию раньше времени
Как видно из рисунка ниже, самая трудоемкая часть каждой сборки — это выполнение.yarn
Когда команда устанавливает зависимости, большую часть времени мы просто меняем код, а зависимости остаются неизменными.В это время, если этот шаг можно кэшировать, когда зависимости не меняются, нет необходимости переустанавливать зависимости, что может значительно улучшить скорость компиляции.
Как мы уже упоминали ранее, построение образа происходит послойно, а предыдущий слой является основой следующего слоя.Поскольку это так, мы заранее скопируем файл package.json в образ отдельно, а затем установим зависимости и выполните команду на следующем шаге.Предыдущий уровень уровня зависимости установки — скопировать файл package.json, потому что команда зависимости установки не изменится, поэтому, пока файл package.json не изменится, он повторно выполняться не будетyarn
Установите зависимости, он будет переиспользовать ранее установленные зависимости, принцип ясен, посмотрим эффект ниже
Измененный файл Docker
FROM alpine:latest
# 使用 apk 命令安装 nodejs 和 yarn,如果使用 npm 启动,就不需要装 yarn
RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0
# 暴露端口
EXPOSE 4300
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 拷贝 package.json 到工作跟目录下
COPY package.json .
# 安装依赖
RUN yarn
# 把当前目录下的所有文件拷贝到镜像的工作目录下 .dockerignore 指定的文件不会拷贝
COPY . .
# 启动命令
CMD yarn start
build Посмотрите на рисунок ниже.Время компиляции от 29,6с до 1,3с.Перед слоем, который использует кеш, будет слово CACHED.Если внимательно посмотреть на рисунок ниже, то можно увидеть
Использование всех преимуществ функции кеша докеров — мощный инструмент для оптимизации скорости сборки.
Снова сожмите размер изображения с помощью многоэтапных сборок
Про многоступенчатое строительство тут особо и говорить нечего, если не понятно, можно поискать сначала нужную информацию.
Потому что, когда мы запускаем программу node, нам нужны только производственные зависимости, а конечный узел может запускать файлы, то есть нам нужны только зависимости в зависимостях в файле package.js для запуска проекта и devDependencies зависимости используются только на этапе компиляции, такие как eslint и другие инструменты в нем не используются при запуске проекта. Например, наш проект написан на машинописном языке. Node не может напрямую запускать файл ts. Файл ts должен быть скомпилирован в js файл.Для запуска проекта нам нужны только скомпилированные файлы и зависимости в зависимостях.Его можно запускать,а значит в финальном образе нужно только то что нам нужно,а все остальное можно удалить.Давайте перепишем файл Dockerfile в несколько этапов.
# 构建基础镜像
FROM alpine:3.14 AS base
# 设置环境变量
ENV NODE_ENV=production \
APP_PATH=/node/app
# 设置工作目录
WORKDIR $APP_PATH
# 安装 nodejs 和 yarn
RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0
# 使用基础镜像 装依赖阶段
FROM base AS install
# 拷贝 package.json 到工作跟目录下
COPY package.json ./
# 安装依赖
RUN yarn
# 最终阶段,也就是输出的镜像是这个阶段构建的,前面的阶段都是为这个阶段做铺垫
FROM base
# 拷贝 装依赖阶段 生成的 node_modules 文件夹到工作目录下
COPY --from=install $APP_PATH/node_modules ./node_modules
# 将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入镜像的工作目录下
COPY . .
# 启动
CMD yarn start
Внимательные друзья обнаружат, что я указал здесь альпийскую версию, а выше используется последняя версия, потому что как раз, когда я нашел яму, на которую нужно обратить внимание, когда мы выбираем альпийскую версию, лучше не выбирать последняя версия, потому что более поздняя версия программного обеспечения, которое будет установлено, может не иметь номера версии соответствующего программного обеспечения в последней версии alpine, и установка будет неправильной.Нажмите, чтобы просмотреть информацию о пакете для альпийской версии.
После сборки посмотрим на размер образа.В прошлый раз было 174M, сейчас опять упало до 73.4M, что крайне ужимает. Зеркальное отображение: "Отпусти меня, у меня правда больше нет" 😂
объяснять:
Я разделил эту сборку на три этапа:
-
Этап 1. Создание базового образа
На этапах установки зависимостей, компиляции, запуска и т. д. вещи, общие для всех этапов, запечатываются в базовый образ на первом этапе для использования на других этапах, таких как установка переменных среды, установка рабочего каталога, установка nodejs, пряжа , так далее.
-
Второй этап: этап установки зависимостей
На этом этапе установите зависимости.Если проект нужно скомпилировать, то вы можете установить зависимости и скомпилировать их на этом этапе.
Здесь мы говорим о небольших деталях загрузки зависимостей, то есть выполнение
yarn --production
Добавьте производственный параметр или переменную средыNODE_ENV
дляproduction
, yarn не будет устанавливать пакеты, перечисленные в devDependencies ,Нажмите на меня, чтобы просмотреть официальную документацию, потому что я установил переменную среды, поэтому я не добавлял этот параметр -
Этап 3: Окончательное использование зеркала
Скопируйте папку хороших зависимостей, установленную на втором этапе, затем скопируйте файл кода в рабочую директорию, выполните команду запуска, нам не нужен лишний мусор из установки второго этапа, мы копируем только то, что используем, значительно Уменьшаем Размер изображения
Если проект нужно скомпилировать, скопируйте скомпилированную папку, не копируя предварительно скомпилированный код, вы можете запустить проект с скомпилированным кодом и зависимостями
При многоэтапном построении результирующее изображение может быть только результатом последнего этапа, однако возможно копирование файлов с предыдущего этапа на более поздний этап, что является самым большим значением многоэтапного построения.
Окончательные результаты оптимизации:
- Размер от 1,06 Гб до 73,4 Мб
- Скорость сборки с 29,6 секунды до 1,3 секунды(по сравнению со скоростью второго билда)
На этом средства выдавливания зеркального отражения закончились.Если у боссов еще остались средства выдавливания, вы можете ими поделиться.
Внутренний монолог Зеркального образа: "Ты вежлив? Вернись"
Действия github создают проблему с зеркалом
Действия, предоставляемые github, каждый раз являются чистым экземпляром. Что вы имеете в виду, каждое выполнение — это чистая машина. Это вызовет проблему и приведет к тому, что докер не сможет использовать кеш. Есть ли решение? Пришли два решения разум:
-
Решение для кэширования действий, официально предоставленное докером
Я использую решение для кеша Github
-
Самостоятельные действия запускают машину
Как и бегун Gitlab, вы можете предоставить бегун, и вы не будете предоставлять чистую машину каждый раз.Подробности смотрите в официальной документации действий
-
Сначала создайте образ с установленным пакетом зависимостей, а затем повторите сборку на основе этого образа, что эквивалентно многоэтапной сборке, отправьте продукты образа, созданные на первых двух этапах, на зеркальный склад, а затем соберите последующие части. на основе этого изображения. Используйте зеркальный склад для хранения основного изображения для достижения эффекта кэширования (это решение исходит от большого парня в комментарии)
# 以这个镜像为基础去构建,这个镜像是已经装好项目依赖的镜像并推送到镜像仓库里,这里从镜像仓库拉下来 FROM project-base-image:latest COPY . . CMD yarn start
Использованная литература:
наконец
Адрес склада проектаwechat-bot
Если в статье есть ошибки, пожалуйста, поправьте меня, чтобы дети не поняли меня неправильно.