Основные инструкции Docker

Это третья часть серии статей про Docker и она всецело посвящена Docker-файлам. В первой части основные концепции Docker объясняются на простых примерах из жизни. Во второй статье — краткий обзор экосистемы Docker.

Docker-образы

Docker-образ создаётся во время сборки, а Docker-контейнер — во время запуска приложения.

Docker-файл — сердце Docker’а. Он указывает Docker’у как построить образ, который будет использоваться при создании контейнера.

Каждый Docker-образ содержит файл с именем Dockerfile (он без расширения). При вызове docker build предполагается, что Dockerfile будет находиться в текущей рабочей директории. Но с помощью флага -f можно указать другое расположение.

Контейнер состоит из ряда слоёв. Все слои доступны только для чтения, кроме последнего — он располагается над остальными. Docker-файл указывает порядок добавления слоёв.

Каждый слой — это просто файл с изменением предыдущего слоя. В Unix практически всё является файлом.
Базовый слой, его ещё называют родительским, — это начальный слой.

При загрузке Docker-образа из удалённого репозитория скачиваются только отсутствующие у вас слои. Docker экономит место и время, повторно используя уже существующие слои.

Инструкция Docker-файла — слово в верхнем регистре, которое стоит перед аргументом какой-либо команды. Каждая строка в Docker-файле может содержать инструкцию, все они обрабатываются сверху вниз. Инструкции выглядят так:

FROM ubuntu:18.04
COPY . /app

И только инструкции FROM, RUN, COPY и ADD создают слои в конечном образе. Другие инструкции производят настройку, добавляют метаданные или же просто говорят Docker’у сделать что-либо во время запуска (например открыть порт или выполнить команду).

Эта статья предполагает использование Unix Docker-образа. Вы, конечно, можете использовать и Windows Docker-образ, но он медленнее, менее удобный и, вообще, его не часто применяют. Так что, пользуйтесь Unix по возможности.

Несколько Docker-инструкций

  • FROM — задаёт родительский (главный) образ;
  • LABEL — добавляет метаданные для образа. Хорошее место для размещения информации об авторе;
  • ENV — создаёт переменную окружения;
  • RUN — запускает команды, создаёт слой образа. Используется для установки пакетов и библиотек внутри контейнера;
  • COPY  — копирует файлы и директории в контейнер;
  • ADD  — делает всё то же, что и инструкция COPY. Но ещё может распаковывать локальные .tar файлы;
  • CMD — указывает команду и аргументы для выполнения внутри контейнера. Параметры могут быть переопределены. Использоваться может только одна инструкция CMD;
  • WORKDIR — устанавливает рабочую директорию для инструкции CMD и ENTRYPOINT;
  • ARG — определяет переменную для передачи Docker’у во время сборки;
  • ENTRYPOINT — предоставляет команды и аргументы для выполняющегося контейнера. Суть его несколько отличается от CMD, о чём мы поговорим ниже;
  • EXPOSE — открывает порт;
  • VOLUME — создаёт точку подключения директории для добавления и хранения постоянных данных.

Инструкции и примеры к ним

Docker-файл чисто теоретически может содержать только одну строчку:

FROM ubuntu:18.04

FROM

Docker-файл должен начинаться с инструкции FROM или ARG, за которой следует FROM. Команда FROM говорит Docker’у использовать базовый образ, который соответствует репозиторию и тегу.

В этом примере хранилище образов — Ubuntu. Ubuntu — название официального Docker-репозитория, в котором и содержится данная ОС.

Заметьте, что этот Docker-файл содержит тег для базового образа: 18.04, который указывает Docker’у, какую именно версию образа нужно использовать. Если тег не указан, по умолчанию берётся последняя версия образа. Но лучше всё же указывать тег базового образа. Когда Docker-файл, приведённый выше, используется для создания локального Docker-образа впервые, он загружает слои, указанные в образе Ubuntu.

При создании Docker-контейнера, вы помещаете наверх слой, который впоследствии можно будет изменить.

Когда образ запущен, и нужно произвести некоторые изменения, файл копируется на верхний слой, доступный для редактирования. Узнать, как это делается, можно здесь.

Подробнее про Docker-файл

Кроме того, что ваш однострочный образ сжат, он ещё и медленный, предоставляет мало информации и ничего не делает во время запуска контейнера. Посмотрите на более длинный Docker-файл, который запускает более легковесный образ, а также выполняет скрипт во время запуска.

FROM python:3.7.2-alpine3.8
LABEL maintainer="jeffmshale@gmail.com"
ENV ADMIN="jeff"

RUN apk update && apk upgrade && apk add bash

COPY . ./app

ADD https://raw.githubusercontent.com/discdiver/pachy-vid/master/sample_vids/vid1.mp4 \
/my_app_directory

RUN ["mkdir", "/a_directory"]

CMD ["python", "./my_script.py"]

Но что же это всё обозначает?

В роли базового образа выступает официальный Python-образ с тегом 3.7.2-alpine3.8. Как вы можете увидеть из исходников, образ включает в себя Linux, Python и ничего более. Alpine-образы очень популярны, потому что они маленькие, быстрые и безопасные. Однако Alpine-образы не поставляются сразу со всеми компонентами, характерными для вашей ОС. Некоторые пакеты вам придётся установить самостоятельно.

LABEL

Следующая инструкция — LABEL. LABEL добавляет метаданные к образу, предоставляет контактную информацию. Она не замедляет процесс запуска и не занимает много места, наоборот, обеспечивает образ полезной информацией, так что обязательно используйте её. Больше про LABEL читайте здесь.

ENV

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

ENV удобна для обозначения констант. Если константа используется в нескольких местах файла Dockerfile, и вам понадобится изменить её значение позднее, это можно будет сделать в одном месте.

Docker-файл зачастую предоставляет несколько путей решения одной задачи. Будет хорошо, если в вашем решении будет учитываться баланс Docker-соглашений, прозрачность и скорость. К примеру, RUN, CMD и ENTRYPOINT служат различным целям и могут использоваться для выполнения команд.

RUN

RUN создаёт слой во время запуска. Docker фиксирует состояние образа после каждой инструкции RUN.

Чаще всего используется для установки нужных пакетов внутрь контейнера. В примере выше RUN apk update && apk upgrade говорит Docker’у обновить пакеты из базового образа. && apk add bash указывает на то, что для базового образа нужно установить bash.

apk — это сокращение от Alpine Linux package manager. Если вы используете базовый образ не Alpine Linux, то установка пакетов производится командой RUN apt-get.

RUN и её родственные инструкции: CMD, ENTRYPOINT — могут быть как форме оболочки, так и в форме shell-скрипта. Во втором случае используют JSON-синтаксис: RUN ["my_executable", "my_first_param1", "my_second_param2"]. А в примере выше использовалась форма оболочки: RUN apk update && apk upgrade && apk add bash.

Позднее в вашем Docker-файле вы будете создавать новую директорию, используя ["mkdir", "/a_directory"]. Не забывайте, что в JSON нужно использовать двойные кавычки!

COPY

Инструкция COPY . ./app говорит Docker’у, что нужно скопировать файлы и папки из вашей локальной сборки в рабочую директорию образа. COPY создаст все нужные папки, если они отсутствуют.

ADD

ADD делает то же самое, что и COPY, но с двумя отличиями. ADD может загружать файлы по URL, а также извлекать локальные TAR-файлы.

В примере выше ADD копировала файлы по URL внутрь директории контейнера. Но официальныя документация не рекомендует использовать ADD так, потому что потом вы попросту не сможете удалить файлы. А дополнительные файлы увеличивают размер образа.

Ещё официальная документация для ясности рекомендует использовать, когда это возможно, COPY вместе ADD. Жаль только, что в Docker’е невозможно использовать ADD и COPY в одной команде.

Заметьте, инструкция содержит символ \. Это нужно для лучшей читаемости — так вы разбиваете длинную инструкцию на несколько строк.

CMD

CMD — инструкция для запуска чего-либо во время запуска самого контейнера. По ходу сборки она не фиксирует никакого результата. В примере выше во время сборки запускался скрипт my_script.py.

Ещё пара моментов о CMD:

  • Только одна CMD-инструкция на весь Docker-файл. Иначе все кроме последней будут проигнорированы;
  • CMD может включать исполняемый файл;
  • Если же CMD не содержит никакого файла, обязательно должна быть инструкция ENTRYPOINT. В этом случает обе инструкции должны быть в формате JSON;
  • Аргументы командной строки для запуска Docker переопределяют аргументы, предоставленные CMD в Docker-файле.

Готовы к большему?

В следующем примере представлены ещё несколько Docker-инструкций:

FROM python:3.7.2-alpine3.8
LABEL maintainer="jeffmshale@gmail.com"

# Install dependencies
RUN apk add --update git

# Set current working directory
WORKDIR /usr/src/my_app_directory

# Copy code from your local context to the image working directory
COPY . .

# Set default value for a variable
ARG my_var=my_default_value

# Set code to run at container run time
ENTRYPOINT ["python", "./app/my_script.py", "my_var"]

# Expose our port to the world
EXPOSE 8000

# Create a volume for data storage
VOLUME /my_volume

В Docker-файле вы можете добавлять комментарии. Комментарии начинаются со знака #.

Обычно установка пакетов — приоритетная задача для Docker’а. Как говорилось ранее, есть несколько способов загрузки пакетов при помощи инструкции RUN.

Для Alpine Docker-образа вы используете apk. apk для типичной Linux-сборки — apt-get. Например, пакеты для базового Ubuntu-образа могут быть установлены и обновлены так: RUN apt-get update && apt-get install my_package.

В дополнение к apk и apt-get, Python-пакеты могут быть установлены через pipwheel и conda. Методы варьируются в зависимости от языка.

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

Можно использовать RUN вместе с pip и списком нужных пакетов. Для этого объедините команды установки пакетов в одну инструкцию и разделите их символом продолжения строки (\). Этот метод позволяет улучшить читаемость и уменьшить количество слоев (из-за отсутствия возможности использовать несколько RUN инструкций).

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

WORKDIR

Меняет текущую рабочую директорию в контейнере для инструкций: COPY, ADD, RUN и ENTRYPOINT.

Несколько замечаний:

  • Предпочтительно задать абсолютный путь с помощью WORKDIR, а не перемещаться по файловой системе с помощью команд cd в Docker-файле;
  • WORKDIR автоматически создаёт директорию, если её ещё нет;
  • Можно использовать несколько WORKDIR-инструкций. Если используются относительные пути — каждая инструкция поменяет рабочую директорию.

ARG

Определяет переменную для передачи из командной строки в образ. Для ARG можно указать значение по умолчанию: ARG my_var=my_default_value.

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

ENTRYPOINT

ENTRYPOINT тоже позволяет вам задавать дефолтные команды и аргументы во время запуска контейнера. Она похожа на CMD, но параметры ENTRYPOINT не переопределяются, если контейнер запущен с параметрами командной строки.

Вместо этого аргументы командной строки, передаваемые docker run myimagename, добавляются к аргументам инструкции ENTRYPOINT. Например, docker run my_image bash добавляет аргумент bash в конец, ко всем другим аргументам ENTRYPOINT.

Docker-файл обязательно должен содержать либо CMD-инструкцию, либо ENTRYPOINT-инструкцию.
В официальной документации есть несколько советов, которые помогут сделать выбор между CMD и ENTRYPOINT для начальной команды:

  • Если вам нужно запускать одну и туже команду несколько раз, выбирайте ENTRYPOINT;
  • Используйте ENTRYPOINT, когда ваш контейнер выступает в роли исполняющейся программы;
  • При наличии дополнительных дефолтных аргументов, которые могут быть изменены через командную строку, лучше подойдёт CMD.

В примере выше, ENTRYPOINT ["python", "my_script.py", "my_var"] запускает в контейнере Python-скрипт my_script.py с аргументом my_var. Затем переменная my_var может быть использована в my_script argparse. Заметьте, у my_var есть дефолтное значение, ранее установленное в Docker-файле при помощи ARG. Так что, если аргумент не будет задан через командную строку, возьмётся его значение по умолчанию.

Как правило, Docker рекомендует вам использовать исполняемую форму с JSON-синтаксисом ENTRYPOINT ["executable", "param1", "param2"].

EXPOSE

Инструкция EXPOSE показывает, какой порт пробрасывать из контейнера.

Используйте команду docker run с флагом -p для пробрасывания и сопоставления нескольких портов во время запуска. Флаг в верхнем регистре -P будет пробрасывать все открытые порты.

VOLUME

VOLUME определяет, где контейнер будет хранить постоянные данные и получать к ним доступ.

Заключение

В этой статье не были упомянуты такие инструкции, как USER, ONBUILD, STOPSIGNAL, SHELL, и HEALTHCHECK, информацию про них вы сможете найти здесь.

Перевод статьи «Learn Enough Docker to be Useful»