Docker & Python

Часть 3 – докер файл (dockerfile)

В предыдущих постах «Часть 1 – терминология» мы рассмотрели основные термины и в части “Часть 2 – основные команды” провели обзор основных команд по управлению докер. Теперь расмотрим, что такое докер файл, зачем он нужен и как его создавать.

Сам по себе докер файл представляет из себя текстовый документ, содержащий инструкции п созданию образа для докера. Запустить процесс создания образа можно с помощью команды build.

Пример докер файла:

Файл представляет из себя набор команд, выполняя который докер создаст нужный образ. Стоит отметить, что построение образа происходит в контексте. Под этим контекстом подразумевается файл или группа файлов, расположенных по указанному пути или URL. Для лучшего понимания, что такое контекст, попробуем описать это понятие. Контекст — это некоторые вспомогательные файлы, которые необходимы для запуска или настройки вашего приложения. Например, это файл конфигурации или дамп базы данных, который необходимо развернуть перед запуском приложения. А может быть это и сам код вашего приложения.

Контекст

Контекст может быть как локальным, так и удаленным, в качестве контекста можно указать URL Git репозитория, с указанием необходимой ветки. В этом случае команда сборки могут выглядеть так:

docker build https://github.com/sample-repo.git#mybranch

docker build https://github.com/sample-repo.git#mytag

Команда сборки устанавливает контекст для указанного пути или URL, после чего загрузит указанные файлы в демон докер и разрешит ему построить указанный образ.  Также в качестве контекста может быть использован tar архив, содержимое которого и будет считаться контекстом.

ВНИМАНИЕ: если в качестве контекста использовать корень файловой системы (значение «/»), то все содержимое жесткого дика будет перенесено в демон докер.

Dockerignore

Важно понимать, что комнада сборки образа переносит содержимое текущего контекста в докер в процесссе сборки образа. В том случае если внутрииконтекста есть файлы которые не нужны для сборки образа, то их можно указать в специальном файле “.dockerignore” (по анологии с gitignore). Который позволяет указать список каталогов или файлов, который должны быть исключены из контекста.

Список игнорирования предоставляется файлом, .dockerignore и когда Docker находит этот файл, он изменяет контекст, чтобы исключить файлы и/или шаблоны, указанные в файле. Все, что начинается с хеша (#)
считается комментарием и игнорируется.

Пример такого файла:

В этом файле описано исключение из контекста директорий .git и .DS_Store, а также всех директорий имена которых начинатся с temp, где бы они не находились.

Инструкции докер файла

Внутри докер файла могут быть использованы следующие инструкции:

  • FROM
  • ADD
  • COPY
  • RUN
  • CMD
  • ENTRYPOINT
  • ENV
  • VOLUME
  • LABEL
  • EXPOSE

Рассмотрим их назначение.

Инструкция FROM

Каждый образ должен начинаться с сылки на базовый образ. В
инструкция FROM указывается, какой базовый образ использовать для
последующих инструкции. Каждый допустимый файл Dockerfile должен начинаться с FROM
инструкции. Синтаксис следующий:

FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

Где <image> – это имя образа из публичного или личного репозитория, который в начале сборки будет скачан и все дальнейшие инструкции будут относится к этому образу.

Несмотря на не обязательность тега, настоятельно рекомендуется его указание, что привяжет образ к конкретной версии и исключит неожиданности в дальнейшем связанные с будущими переборками образа, который может стянуть более новую версию базового образа, если конкретный не был указан.

Инструкция WORKDIR

Эта инструкция служит для задания текущего рабочего каталога для таких инструкций как RUN, RUN, CMD, ENTRYPOINT, COPY, и ADD. Имеет следующий синтаксис:

WORKDIR </путь/к/каталогу>

Докер файл может содержать несколько инструкций WORKDIR, при этом все последующие инструкции выбирают пути относительно предыдущих. Например:

WORKDIR /usr

WORKDIR src

WORKDIR app

Первая инструкция задаст рабочий каталог «/usr», вторая «/usr/src», а третья «/usr/src/app».

Стоит отметить, что WORKDIR относится к файловой системе внутри докер образа и никак не затрагивает файловую систему хоста. 

Если инструкция не задана, то по умолчанию в качестве рабочего каталога берется корневой каталог образа.

Инструкции ADD и COPY

Обе команды служат для копирования файлов и папок внутрь образа. Разница между ними заключается в том, что команда ADD может работать с путями заданными через URL (другими словами может скачивать файлы из интернета), а также умеет  работать с архивами tar (при копировании архив будет извелчён).

ADD <source> <destination>
COPY <source> <destination>

ADD –chown=<user>:<group> <source> <destination>
COPY –chown=<user>:<group> <source> <destination>

Форма с параметром –chown, позволяет при копировании сразу же изменить владельца файла иликаталога. Обе команда поддерживают символ маски “*”, позволяющий выполнять групповое копирование.

Например (копирование файлов с раширением py из текущего контекста в каталог /apps/ внутри образа):

ADD *.py /apps/
COPY *.py /apps/

По умолчанию рекомендуется к использованию инструкция COPY (особенно если происходит копирование локальных файлов). Вот основные моменты применения инструкции:

Если <destination> – не существует он будет создан. Все файлы копируются с пользователем и группой 0, то есть от имени root (если не указаны явно через –chown). Если имя каталога содержит специальные символы, то они должны быть экранированы. Если <destination> указан как абсолютный путь, то используется этот путь, если указан как относительный, то для определения пути в качестве базы используется значение инструкции WORKDIR. Если <destination> – указан без завершающего слеша (символ «/»), то содержимое <source> будет записано внутрь <destination> (таким образом <destination> просто считается файлом).

Если <source> задано с применением шаблонов, то обязательно указание <destination> с завершающим слешем, иначе процесс сборки будет завершен с ошибкой (что в общем то и логично, как множество файлов записать внутрь одного).  <source> всегда задается только относительно контекста сборки и не может выйти за его пределы.

В случае инструкции ADD есть ряд особенностей:

  • Если в< source> задан URL, а <destination> – указан без завершающего слеша, то данные скачанные по указанному адресу будет записаны в файл заданный destination.
  • Если в< source> задан URL, а <destination> – указан с завершающем слешем, то из URL будет извлечено имя файла (<filename>) и данные скачанные по указанному адресу будет записаны в файл заданный <destination>/<filename>.
  • Если в <source> задан локальный архив tar и он сжат известным системе архиватором, то он будет распакован по указанному адресу как директория. Удаленный архивы никогда не расплакиваются.

Инструкция RUN

Инструкция RUN выполняет команду командной оболочки внутри образа используя текущий слой и по результатам выполнения формирует новый слой. Существует две формы команды.

RUN <command> (форма шелл shell)
RUN [“executable”, “parameter 1″, ” parameter 2″] (форма exec)

В формате шелл, команда RUN позволяет использовать переменные окружения, а также соединять команды в каналы, что невозможно для формата exec.

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

В качестве ключа для кэша используется сама команда RUN, а не ее результат.

Например, пускай мы имеем два докер файла:

построения которого мы приступаем к построению второго:

в результате второй образ может содержать старые пакеты, так как в качестве слоя возмется результат выполнения команды RUN из первого файла. Что бы обязательно выполнить обновления необходимо применить –no-cache флаг к инструкции.

Создание каждой командой RUN нового слоя, с одной стороны, хорошо, так как позволяет использовать результаты в последующем, однако такое поведение увеличивает размеры, занимаемые образом. Поэтому настоятельно рекомендуется по возможности соединять команды RUN в одну. Например команды:

можно преобразовать в команду:

в результате которой буде создан только один слой, вместо 4-х.

Инструкции CMD и ENTRYPOINT

Эти инструкции выполняются, когда контейнер запускается.

CMD [“executable”,”param1″,”param2″] (формат exec)
CMD [“param1″,”param2”] (формат параметров по молчанию для ENTRYPOINT)
CMD command param1 param2 (формат shell)
ENTRYPOINT [“executable”, “param1”, “param2”] (формат exec)
ENTRYPOINT command param1 param2 (формат shell)

Первая и важная особенность CMD это возможность переопределения этих параметров в момент запуска контейнера. 

Например docker run <образ> <параметр 1> <парметр 2> – эта команда заменит команду CMD на значения <параметр 1> <парметр 2>.  Однако если в файле есть инструкция ENTRYPOINT, то ее значения сохранятся и дополнятся вновь переданными ENTRYPOINT

Таблица описывает зависимость выполнения команд с параметрами от наличия инструкций CMD и/или ENTRYPOINT

  в фале нет ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
нет CMD ошибка /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd
p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh
-c exec_cmd p1_cmd

Вы можете указать RUN, CMD и ENTRYPOINT в форме вызова оболочки shell или в форме исполнения exec. Общее руководство, о том когда какую форму применять:

  • Форма shell команда запускается в в командной оболочке с переданной командой в качестве параметра. В этой форме доступно применение переменных оболочки, доступны подкоманды, в возможно связывание команд в каналы.
  • Форме exec вызывает команду непосредственно, без использования командной оболочка. Это означает, функции командной оболочки не доступны (например, переменные $VARIABLE, каналы, и т. д.).
  • Программа, запущенная в форме оболочки, будет работать как подкоманда / bin / sh -c. Это означает
    исполняемый файл не будет иметь выделенный PID и не будет получать сигналы UNIX. Как следствие, Ctrl + C для отправки SIGTERM не будет перенаправлен на контейнер и приложение не сможет их получить.

Инструкция ENV

Инструкция устанавливает значение переменной окружения внутри образа. Есть две формы инструкции:

ENV <key> <value>
ENV <key>=<value> …

В первой форме вся строка после <key> будет считаться значением, включая пробельные символы. Для каждой инструкции может быть установлена только одна переменная.
Во второй форме одновременно можно установить несколько переменных, указывая значение через равно.
Набор переменных среды сохраняется в контейнере во время выполнения. Их можно просмотреть с помощью docker inspect.

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

Например: docker run -it -e LOGS_DIR=”/logs” sathyabhat:env-example

Эта команда при заруске переопределит переменную LOGS_DIR.

Инструкция VOLUME

Инструкция VOLUME указывает Docker создать каталог на хосте и
смонтируйте его по пути, указанному в инструкции.
Например, такая инструкция:

VOLUME /var/logs/nginx

Сообщает Docker о необходимости создания каталога на хосте Docker (обычно внутри
Корневой путь Docker) и соединяет с каталогом внутрииконтейнера.

Инструкция EXPOSE

Инструкция указывает докер прослушивать соединения на указанном порту. Имеет следующий синтаксис:

EXPOSE <port> [<port>/<protocol>…]

Можем указать номер порта и протокол TCP/UDP или оба. Если протокол не указан, Docker предполагает, что протоколом является TCP.

Обратите внимание, что инструкция EXPOSE не публикует порт. Для того, что бы порт 
был опубликованным на хосте, вам нужно использовать флаг -p, при вызове 
docker run, чтобы опубликовать и сопоставить порты.

Например: docker run -d -p 8080:80 sathyabhat:web

Инструкция LABEL

Добавляет некотороые метаданные в контейнер в формате ключ зачение.

LABEL <key>=<value> <key>=<value> <key>=<value> …

Образ может иметь несколько меток, которые служат в качестве метаданных и помогают в организации поска и структирирования образов и объектов докер.

Рекомендации по применению:

  • для ключей значения типа “com.docker.*, io.docker.*, org.dockerproject.* ” – зарезервированы докер, все метки должны начинатся и заканчиватся только с текстовым символом в нижнем регистре, внутри имени метки доступны только символы число буквенные, а таке точка и тире. Рекомендуется использовать ключи с добавлением им домменнов влядельцами которых являются авторы. Например: com.sathyasays.my-image
  • для значений – это всегда строка, которая может содежать более сложные типы например JSON, XML, YAML, CSV.

Рекомендации

Ниже приведены некоторые рекомендации и лучшие практики для написания Dockerfiles.

  • Контейнеры должны быть недолговечными.
    Docker рекомендует, чтобы образ, сгенерированный Dockerfile должен быть максимально эфемерным. Что значит, мы должны иметь возможность останавливать, уничтожать и перезапускать контейнер в любой точке с минимальной настройкой.
  • Сохраняйте минимальный контекст сборки
    Важно, чтобы контекст сборки был минимальным насколько это возможно, чтобы уменьшить время сборки и размер образа. Это можно сделать с помощью файла .dockerignore.
  • Используйте многоэтапные сборки.
    Многоступенчатые сборки помогают значительно уменьшить размер образа без необходимости писать сложные скрипты для передачи / хранения необходимых артефактов. 
  • Пропускать нежелательные пакеты
    Наличие нежелательных или желаемых пакетов увеличивает размер образа, вводит
    нежелательные зависимости пакетов и увеличивает площадь поверхности для атак.
  • Минимизируйте количество слоев
    Важно уменьшить количество слоев в образе. Начиная с Docker 1.10 и выше, только RUN,
    COPY и ADD создают слои.

Многоэтапные сборки

Начиная с версии 17.05 и выше, в Docker добавлена ​​поддержка многоступенчатой сборки, позволяющие выполнять сложные сборки образов без лишнего раздувания размера. Многоступенчатые сборки
особенно полезны для создания образов приложений, требующих некоторых дополнительные зависимости во время сборки, но не нужнын во время выполнения.
Наиболее распространенными примерами являются приложения, написанные с использованием языков программирования таких, как Go или Java, где до многоэтапных сборок обычно есть два разных Dockerfile, один для сборки, а другой для запуска. Управление образами может быть создан с помощью сценариев оболочки.
При многоступенчатой ​​сборке для сборки можно использовать один файл Dockerfile, а для развертывания другой образ. Образ для сборки могут содержать необходимые инструменты сборки для создания двоичного файла или артефакта, а на втором этапе артефакт можно скопировать в образ во время выполнения, тем самым значительно уменьшив размер образа среды выполнения.

Для типичной многоступенчатой ​​сборки этап сборки имеет несколько слоев – каждый уровень для установки своего инструмента, необходимого для создания приложения.

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

Обсуждение закрыто.