Как сделать Docker наладить более быстро

Как сделать Docker наладить более быстро

25 июля 2025 г.

Введение

Вы когда-нибудь смотрели на свой терминал, ожидая сборки Docker, и задавались вопросом, почему крошечное изменение кода вызвало 10-минутное перекомпиляцию всего вашего проекта? Или почему ваше последнее изображение сотни мегабайт больше, чем вы думаете, это должно быть? Это не причуды таинственной системы; Это предсказуемые результаты понятной механики. Разница между разочаровывающим медленным рабочим процессом и эффективным быстрым часто сводится к пониманию двигателяdocker buildПолем


Эта статья является руководством по этому инженерному отделению. Мы демистифицируем процесс сборки, освоив три столпа эффективности: система кэширования слоя, искусствоRUNкомандование и роль.dockerignoreФайл в качестве привратника в вашу сборку. К концу вы не будете знатьчтокоманды для запуска, нопочемуОни работают, позволяя вам создавать по -настоящему профессиональные и оптимизированные контейнеры. Этот проект имеет простое приложение для ИИ, которое использует модель BERT для классификации текста, и мы будем использовать его DockerFile из нашегоlayered_imageПроект как тематическое исследование, чтобы проиллюстрировать эти основные принципы.


Фундамент: слои Docker и кеш сборки - неизменная книга

Представьте себе, что изображение Docker не является единственным монолитным файлом, а в виде стопки точно определенных изменений, например, неизменной бухгалтерской книги, где каждая транзакция записывается на новой странице. Это суть слоистой файловой системы Docker. Каждая инструкция в вашемDockerfile-FROMВCOPYВRUNВCMDи т. д. обычно создает новый слой. Этот слой не содержит полной копии файловой системы; Вместо этого он записывает толькоразличиявведено этой конкретной инструкцией по сравнению со слоем под ним. Если аRUN apt-get install curlКоманда добавляетcurlЭтот слой по сути говорит «+ curl и его зависимости». Если последующийCOPY my_script.py /app/Добавляет сценарий, этот новый слой говорит «+ /apps/my_script.py».


Этот многослойный подход гениальен для эффективности. Когда вы тянете изображение, Docker загружает только слои, которых у него еще нет. Когда вы строите изображения, которые разделяют общие базовые слои (например,python:3.10-slim) эти базовые слои хранятся один раз и разделяются.


Опираясь на эту многослойную файловую системуDocker строить кешПолем Это память Docker о прошлых операциях. Когда вы выпускаетеdocker buildкоманда, Docker проходит через вашDockerfileинструкция по инструкции. Для каждой инструкции это проверяет три вещи:

  1. Точная инструкция (например,COPY my_file.txt /dest/)
  2. Содержание любых файлов, связанных с этой инструкцией (например, контрольная суммаmy_file.txt)
  3. Родительский слой изображения, на котором основана эта инструкция.

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


Однако, еслилюбойИз этих условий изменяются для, например, Если инструкция отличается, если содержимое копированного файла изменилось, или если родительский слой отличается (потому что предыдущая инструкция была промахом кэша), то Docker испытаетКэш БюстПолем Когда происходит бюст в кеш, Docker должен выполнить эту инструкцию с нуля, создав новый слой. Критически,Все последующие инструкции вDockerfileтакже будет выполнен с нуля, независимо от того, могли ли они соответствовать кешу самостоятельно. Кэш недействителен с этого момента вниз.


Это приводит к золотому правилу кэширования:Заказ инструкции с наименьшего часто изменяются на наиболее часто изменяемые.Подумайте об этом, как о организации своего стола: вещи, которые вы редко касаетесь, идут в задние ящики; Вещи, которые вы используете, постоянно остаются на вершине.


Интерактивный эксперимент, чтобы почувствовать кеш:

  1. Во -первых, построитьLayered_image(который имеет порядок, удобный в кэше), используя такую команду, какtime docker build -t bert-classifier:layered -f layered_image/Dockerfile layered_image/Полем Для нас эта первоначальная сборка взяла на себя23 секундыПолем
  2. Теперь открытlayered_image/app/predictor.pyи сделать тривиальное изменение, например, добавить комментарий. Восстановите изображение:time docker build -t bert-classifier:layered -f layered_image/Dockerfile layered_image/Полем Сборка должна завершитьМенее чем секундуПолем Почему? Докер видитFROMВWORKDIRВCOPY runtime_requirements.txtне изменяются и повторно используют свои слои. Он видитRUN pip installинструкция такая же, и ее вход (runtime_requirements.txt) не изменил свой контент, поэтому он повторно использует массовый слой, созданныйpip installПолем Только когда он достигаетCOPY layered_image/app/ ./app/Обнаружает ли это изменение (ваш модифицированныйpredictor.py), так что это восстанавливает этот слой и последующие. Если вам нужны доказательства, идите вперед и добавьте—progress=plainФлаг до конца команда сборки. Docker CLI покажет вам кэшированные слои.
  3. Далее, решающий тест для понимания недействительной: отредактируйте свойlayered_image/DockerfileПолем Переместить линиюCOPY layered_image/app/ ./app/кдоаRUN pip install ...линия. Сделать еще одно тривиальное изменениеlayered_image/app/predictor.pyи восстановить. Что происходит? Сборка снова займет 23 секунды! Изменение наapp/predictor.pyразбил кеш в (теперь ранее)COPY ./app/шаг. Потому чтоpip installШаг приходитпослеЭтот бюст в кеш, он также вынужден повторно запустить с нуля, хотяruntime_requirements.txtне изменился.

Этот эксперимент мощно демонстрирует, как каскады бюста кэша и почему порядок ваших инструкций DockerFile имеет первостепенное значение для быстрого цикла развития. Вот структура, благоприятная для кэша, мы защищаем из нашихlayered_imageпроект:


# Cache-Friendly Order (from layered_image/Dockerfile runtime stage)
FROM python:3.10-slim AS runtime
WORKDIR /app

# 1. Copy requirements first (changes less often than app code)
COPY layered_image/runtime_requirements.txt ./runtime_requirements.txt

# 2. Install dependencies (slow step, now cached if requirements.txt doesn't change)
RUN pip install --no-cache-dir -r runtime_requirements.txt # (Full command shown later)

# 3. Copy app code last (changes most often)
COPY layered_image/app/ ./app/
COPY layered_image/sample_data/ ./sample_data/

CMD ["python", "app/predictor.py", "sample_data/sample_text.txt"]


ИскусствоRUNКоманда: цепочка для микроскопических слоев

Стремление к эффективномуDockerfileИмеет параллель в физическом мире: попытка минимизировать объем коллекции предметов. КаждыйRUNКоманда в вашем Dockerfile создает новый слой. Если вы загружаете инструмент, используйте его, а затем удалите его в отдельномRUNКоманды, вы похожи на то, что кто -то помещает элемент в коробку, а затем положил пустую обертку для этого элемента в другой поле сверху. Оригинальный предмет все еще там, в нижней коробке, занимающий место, даже если в верхней коробке говорится: «Это ушло».


В частности, файлы, созданные в одном слое, не могут бытьПоистине удаленоот общего размера изображения по команде в последующем слое. Последующий слой просто записывает, что эти файлы «удалены» или «скрыты», но биты, включающие эти файлы, все еще существуют в исторических слоях изображения. Вот что подобно инструментамdiveчасто сообщают как «потраченное впустую пространство».


Рассмотрим этот анти-паттерн:

# Anti-Pattern: Separate RUN commands leading to bloat
FROM python:3.10-slim
WORKDIR /app
COPY runtime_requirements.txt .
RUN pip install --no-cache-dir -r runtime_requirements.txt  # Step 1: Install
RUN pip cache purge                                         # Step 2: Cleanup attempt 1
RUN rm -rf /tmp/* /var/tmp/*                                # Step 3: Cleanup attempt 2
# ... (further cleanup attempts)

Если бы вы построили изображение и пробегdocker history bert-classifier-layers, вы бы соблюдали вывод для каждогоRUNшаг. ПервыйRUN pip install...Шаг покажет значительный объем написанных данных (около 679 МБ). ПоследующееRUN pip cache purgeиRUN rm -rf /tmp/Шаги показывают очень мало данных, написанных для их слоев, возможно, всего несколько килобитов. Это потому, что они неудаление данных из предыдущего уровня 679 МБ; Они просто добавляют новые маленькие слои сверху, которые отмечают эти файлы как удаленные. Слой 679 МБ остается частью истории изображения.


docker history bert-classifier-layers                                                                
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
f09d44f97ab4   34 minutes ago   CMD ["python" "app/predictor.py" "sample_dat…   0B        buildkit.dockerfile.v0
<missing>      34 minutes ago   COPY layered_image/sample_data/ ./sample_dat…   376B      buildkit.dockerfile.v0
<missing>      34 minutes ago   COPY layered_image/app/ ./app/ # buildkit       5.51kB    buildkit.dockerfile.v0
<missing>      34 minutes ago   RUN /bin/sh -c rm -rf /tmp/* /var/tmp/* &&  …   0B        buildkit.dockerfile.v0
<missing>      34 minutes ago   RUN /bin/sh -c pip cache purge # buildkit       6.21kB    buildkit.dockerfile.v0
<missing>      34 minutes ago   RUN /bin/sh -c pip install --no-cache-dir -r…   679MB     buildkit.dockerfile.v0
<missing>      34 minutes ago   COPY layered_image/runtime_requirements.txt …   141B      buildkit.dockerfile.v0
<missing>      3 hours ago      WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      11 days ago      CMD ["python3"]                                 0B        buildkit.dockerfile.v0
<missing>      11 days ago      RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
<missing>      11 days ago      RUN /bin/sh -c set -eux;   savedAptMark="$(a…   46.4MB    buildkit.dockerfile.v0
<missing>      11 days ago      ENV PYTHON_SHA256=ae665bc678abd9ab6a6e1573d2…   0B        buildkit.dockerfile.v0
<missing>      11 days ago      ENV PYTHON_VERSION=3.10.18                      0B        buildkit.dockerfile.v0
<missing>      11 days ago      ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8…   0B        buildkit.dockerfile.v0
<missing>      11 days ago      RUN /bin/sh -c set -eux;  apt-get update;  a…   9.17MB    buildkit.dockerfile.v0
<missing>      11 days ago      ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      11 days ago      ENV PATH=/usr/local/bin:/usr/local/sbin:/usr…   0B        buildkit.dockerfile.v0
<missing>      11 days ago      # debian.sh --arch 'arm64' out/ 'bookworm' '…   97.2MB    debuerreotype 0.15


Решение состоит в том, чтобы выполнить все связанные операции, особенно создание и очистку временных файлов или инструментов, в рамкаходинокийRUNкомандование, цепляя их с помощью&&Полем Это гарантирует, что существуют любые временные артефактыэфемерноВо время исполнения этого синглаRUNкомандование и прошел до того, как слой будет завершен и совершен.


Давайте посмотрим на агрессивную очисткуRUNкомандование из нашегоlayered_image/Dockerfile.:

RUN pip install --no-cache-dir -r runtime_requirements.txt && \
    pip cache purge && \
    rm -rf /tmp/* /var/tmp/* && \
    find /usr/local/lib/python*/site-packages/ -name "*.pyc" -delete && \
    find /usr/local/lib/python*/site-packages/ -name "__pycache__" -type d -exec rm -rf {} + || true


Эта команда - тщательно хореографический танец:

  1. pip install --no-cache-dir -r runtime_requirements.txt: Устанавливает пакеты Python, не оставляя загруженные колесные файлы в PIP's HTTP Cache.
  2. pip cache purge: Явно очищает любой другой кэш -PIP.
  3. rm -rf /tmp/* /var/tmp/: Удаляет файлы из стандартных временных каталогов.
  4. find ... -name “.pyc" -delete: Удаляет скомпилированные файлы кода Python Byte.
  5. find ... -name “pycache" -type d -exec rm -rf {} +: Удаляетpycacheкаталоги.
  6. || true: ОбеспечиваетRUNкоманда преуспевает, даже еслиfindНе находит никаких файлов (которые могут вернуть ненулевой код выхода).


Воздействие (демонстрируется сdocker history):

С этим синглом, цепьюRUNкомандование, полученный слой для нашегоlayered_imageПроект572 МБПолем Если эти шаги не были раскрыты, начальныеpip installсоздаст слой приблизительно 679 МБ. Аdocker historyКоманда будет отражать это:


docker history bert-classifier-layers                                                                
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
17d0319094f4   2 minutes ago   CMD ["python" "app/predictor.py" "sample_dat…   0B        buildkit.dockerfile.v0
<missing>      2 minutes ago   COPY layered_image/sample_data/ ./sample_dat…   376B      buildkit.dockerfile.v0
<missing>      2 minutes ago   COPY layered_image/app/ ./app/ # buildkit       5.51kB    buildkit.dockerfile.v0
<missing>      2 minutes ago   RUN /bin/sh -c pip install --no-cache-dir -r…   572MB     buildkit.dockerfile.v0
<missing>      2 minutes ago   COPY layered_image/runtime_requirements.txt …   141B      buildkit.dockerfile.v0
<missing>      3 hours ago     WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      11 days ago     CMD ["python3"]                                 0B        buildkit.dockerfile.v0
<missing>      11 days ago     RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
<missing>      11 days ago     RUN /bin/sh -c set -eux;   savedAptMark="$(a…   46.4MB    buildkit.dockerfile.v0
<missing>      11 days ago     ENV PYTHON_SHA256=ae665bc678abd9ab6a6e1573d2…   0B        buildkit.dockerfile.v0
<missing>      11 days ago     ENV PYTHON_VERSION=3.10.18                      0B        buildkit.dockerfile.v0
<missing>      11 days ago     ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8…   0B        buildkit.dockerfile.v0
<missing>      11 days ago     RUN /bin/sh -c set -eux;  apt-get update;  a…   9.17MB    buildkit.dockerfile.v0
<missing>      11 days ago     ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      11 days ago     ENV PATH=/usr/local/bin:/usr/local/sbin:/usr…   0B        buildkit.dockerfile.v0
<missing>      11 days ago     # debian.sh --arch 'arm64' out/ 'bookworm' '…   97.2MB    debuerreotype 0.15

Это прямое сравнение в размере слоя демонстрирует сохранение107 МБПросто путем правильно структурирования очистки в пределах того жеRUNинструкция


Привратник: мастерство.dockerignore

Наши окончательные принципы: самое начало процесса сборки. Когда вы выполняетеdocker build .,.(или любой путь, который вы указали) Определяет «контекст построения». Docker тщательно упаковывает все в этом пути (с уважением.dockerignoreФайл, конечно) в архив и передает его в Docker Daemon. Демон затем распаковывает этот контекст и использует его в качестве единственного источника локальных файлов для любыхCOPYилиADDинструкции в вашемDockerfileПолем Он не имеет доступа ни к чему в вашей файловой системе вне этого контекста.


Проблема, особенно для проектов искусственного интеллекта, заключается в том, что наши каталоги проекта часто представляют собой сокровищники файлов, совершенно не относящиеся к окончательному изображению времени выполнения: локальные наборы данных, модель контрольно -пропускных пунктов, ноутбуки Jupyter, виртуальные среды Python и все.gitистория Отправка контекста с несколькими гигабайтами не просто медленная (особенно если ваш демон удален, как во многих системах CI), это также проблема безопасности и чистоты. Вы рискуете случайноCOPYконфиденциальная информация или артефакты развития в ваш образ.


А.dockerignoreФайл - ваш бдительный привратник. Это простой текстовый файл, помещенный в корень вашего контекста сборки, который использует шаблоны (очень похоже на.gitignore) указать, какие файлы и каталоги должны бытьисключеноиз контекста, прежде чем он когда -либо упаковался и отправлен в демон.


Всеобъемлющий.dockerignoreДля проекта искусственного интеллекта может выглядеть так:

# .dockerignore
# Python virtual environments
.venv/
env/
venv/

# Python caches and compiled files
__pycache__/
*.py[cod] # .pyc, .pyo, .pyd
*.egg-info/
dist/
build/
*.so # Compiled shared objects, unless explicitly needed and copied

# IDE and OS specific
.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db

# Notebooks and exploratory artifacts
notebooks/
*.ipynb_checkpoints

# Test-related files (if not run inside the container build)
tests/
.pytest_cache/
htmlcov/
.coverage

# Large data or model files not intended for baking into the image
data/
models/
model_checkpoints/
*.pt
*.onnx
*.h5

# Log files
*.log

# Dockerfile itself (usually not needed to be COPIED into the image)
# Dockerfile

# Version control (see note below)
# .git
# .gitignore


Тщательно определяя, что игнорировать, вы гарантируете контекст сборки. Это ускоряет начальный шаг «отправка контекста сборки в Docker Daemon ...», снижает вероятность случайного включения данных и делает вашCOPY . .команды безопаснее и более предсказуемо.


Заключение

В некотором смыслеDockerfileэто просто еще один инструмент. Тем не менее, углубляясь в свою механику, понимаякакОн превращает ваши инструкции в изображение, вы получаете контроль ремесленника. Мы видели, что преднамеренный заказ инструкций в честь кэша сборки может превратить минуты ожидания в секунды. Мы узнали, что искусная цепочка внутриRUNКоманды - это не только синтаксис; Речь идет о скульптуре худых, эффективных слоев. И мы узнали.dockerignoreФайл не как незначительные детали, а как решающий опекун целостности и скорости нашего процесса сборки.


Эти принципы, слои, кэширование, цепочка и управление контекстом являются фундаментальными. Освоение их является ключом к тому, чтобы выходить за рамки простого создания изображений Docker, чтобы по -настоящему разработать их для эффективности, скорости и чистоты, особенно в требовательном мире ИИ.


Твоя очередь

Теперь, когда вы понимаете эту механику, пересмотрите свои собственные Dockerfiles. Можете ли вы переупорядочить слои для лучшего кэширования? Вы можете цепоритьRUNкоманды для более агрессивной очистки? Внедрить надежный.dockerignoreПолем Поделитесь своими выводами или вопросами в комментариях ниже!


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE