Введение в Docker: образы, контейнеры и докер-файлы

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

Начнем с понятия Docker.

 

Что такое Docker?

Docker

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

Поставляется Docker как набор Open Source программ, свободно доступных для скачивания как из репозиториев Linux, так и с сайта www.docker.com. Несмотря на то, что Docker стал уже давно стандартом в DevOps, до сих пор его установка у начинающих пользователей вызывает затруднения. Чтобы у наших читателей не было с этим проблем — мы подготовили краткую инструкцию по его установке.

Появление докера сильно упростило процесс доставки и запуска приложений у конечных пользователей. Он смог стандартизировать эти процессы, предоставляя разработчикам сразу 3 мощных механизма:

  1. механизм сборки приложений в неизменяемые образа, которые избавляют от надобности сбора всех нужных зависимостей по всей хостовой ОС;
  2. механизм доставки программных продуктов до конечных пользователей, гарантирующий 100% совместимость с хостовой ОС;
  3. механизм развертывания приложений на любой *nix-системе, все зависимости от языка программирования на котором написано приложение.

Docker, следуя специальным инструкциям, прописанным разработчиком в конфигурационных файлах (Dockerfile и Docker-compose.yaml), собирает всё необходимое для запуска приложения в одно место — в образ. Docker-образ можно сравнить с CD-диском, с которого в будущем будет установлен и запущен некий софт. Контейнер в свою очередь — это запущенная копия образа.

В начале статьи мы сказали, что Docker — это целая экосистема, которая содержит различные компоненты. Давайте теперь рассмотрим эти компоненты поближе.

Компоненты экосистемы Docker

Структура Docker

Компоненты экосистемы Docker можно разделить на две группы:

  1. Системные компоненты: Docker host (докер-хост), Docker daemon (докер-демон), Docker client (докер-клиент) и Docker-compose (менеджер запуска кластера контейнеров);
  2. Переменные компоненты: Docker image (докер-образ), Docker container (докер-контейнер), Docker registry (репозиторий), Dockerfile (докер-файл), Docker-compose.yaml (конфигурационный файл кластера контейнеров).

Пройдёмся вкратце по первой группе, затем подробно разберем компоненты второй группы:

  • Docker host (докер-хост) — это просто компьютер или виртуальный сервер, на котором установлен Docker. Кстати, Docker можно запустить и из WSL 2. Это удобно, если вы работаете под Windows и используете Visual Studio Code. Прочитать о том как настроить WSL для работы с докером можно здесь.
  • Docker daemon (докер-демон) — центральный системный компонент, который управляет всеми процессами докера: создание образов, запуск и остановка контейнеров, скачивание образов. Работает Docker daemon как фоновый процесс (демон) и постоянно следит за состоянием других компонентов.
  • Docker client (докер-клиент) — это утилита, предоставляющая API к докер-демону. Клиент может быть консольным (*nix-системы) или графическим (Windows).

Разберём теперь подробнее компоненты второй группы. Начнём с центральных элементов — докер-образа.

Docker-образ

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

Docker-образ создаётся с помощью команды docker build, которая считывает конфигурацию создаваемого образа из специального конфигурационного файла — dockerfile.

В Dockerfile записываются команды и опции создания образа, а также некоторые настройки будущего контейнера, такие как порты, переменные окружения и другие опции.

Каждая команда записанная в dockerfile создаёт свой слой. Чем больше слоёв, тем дольше будет собираться образ и дольше загружаться контейнер. Финальный Docker-образ — это объединение всех слоев в один. Благодаря такому подходу можно переиспользовать уже готовые образа для создания новых образов.

Контейнер Docker

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

Получается, чтобы запустить приложение на питоне, в контейнере должна быть ОС и Python. Возникает вопрос: откуда нам это всё взять? Разработчики Docker и здесь о нас позаботились, ведь если вы впервые собираете Docker-образ никаких готовых образов у вас на машине нет, поэтому Docker сам скачает указанный вами родительский образ в Dockerfile из репозитория hub.docker.com.

 

В нашем случае команды в Dockerfile будут такими:

FROM python:latest
RUN mkdir -p /usr/app/
WORKDIR /usr/app/
CMD ["python", "main.py"]

 

Первая команда говорит докеру следующие: возьми образ Python с тегом latest, а если его нет локально — скачай из хаба. Вторая команда — создаст директорию app. Третья команда установит директорию /usr/app/ в качестве рабочей директории, а команда CMD осуществит системный вызов, который будет выполнен при старте контейнера.

 

Docker заботится об используемых ресурсах, поэтому скачанные или собранные самостоятельно образы хранятся локально. Посмотреть их можно командой docker images. В ответ команда вернёт таблицу следующего вида:

REPOSITORY TAG IMAGE ID CREATED SIZE
acc-info latest 7069c8d4c76b 27 hours ago 1.05GB
python latest a5d7930b60cc 3 weeks ago 917MB

 

Вы могли заметить, что образы довольно объемны и хранить их локально накладно. Для экономии места и «правильной передачи образов другим разработчикам», Docker предлагает удобный инструмент загрузки образов в удалённый репозиторий.

Для загрузки образа в репозиторий сначала нужно залогиниться на hub.docker.com, а затем и в терминале, с помощью команды docker login. После логина соберите образ командой docker build и в качестве названия образа укажите свой никнейм с сайта hub.docker.com и через “/” желаемое название образа. В нашем случае это выглядит так:

docker build -t pseudolukian/acc-info .

После успешного создания образа, его можно загружать в удаленный репозиторий командой docker push с указанием названия образа:

docker push pseudolukian/acc-info

Загруженные образы появятся по адресу: https://hub.docker.com/repositories. Теперь вы можете их скачать на любую машине командой docker pull название образа.

 

Что нужно знать о Docker-образах:

  • Образ — это только шаблон для создания контейнеров.
  • В основе любого образа лежит родительский образ, который, как правило, содержит ОС.
  • Образ состоит из слоев.
  • Каждая команда в Dockerfile создаёт новый слой.
  • Образы можно переиспользовать много раз.
  • Образы можно загружать в удаленный репозиторий.

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

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

Docker-контейнер

Docker-контейнер

Контейнер — это запущенный и изолированный образ с возможностью временного сохранения данных. Данные записываются в специальный слой «сверху» контейнера и при удалении контейнера данные также удаляются.

Для работы с контейнерами Docker предоставляет всего несколько основных команд: docker run/stop/restart. Однако команда run имеет множество полезных ключей, о которых просто необходимо знать. Ознакомиться с ними можно в базе знаний, в разделе Docker.

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

Структура Docker-контейнера

Но как же быть, если мы хотим, скажем, запускать базы данных или иные приложения в контейнере, для которых важна целостность данных? Docker предлагает два решения:

  1. Монтирование папки с хоста к контейнеру. Такое решение отлично подходит для разработки и отладки приложения, когда вносимые изменения на хосте сразу отражаются в контейнере. Конечно, для продакшна такой подход не годится.
  2. Создание специальных томов для хранения данных — Docker volume. DV — это обычные каталоги хоста, которые могут быть смонтированы как отдельные файловые системы (bind mounting) внутри контейнера.

Лучше всегда использовать второй вариант. Подключается Docker volume к контейнеру двумя различными способами: с помощью команды docker run с ключом -v — docker run --name python --rm -v home/test-volume/ : /data/ -d (сначала идёт путь к папке хоста, затем указывается папка монтирования) или через инструкцию VOLUME /data в Dockerfile. Результат будет одинаковым.

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

По умолчанию демон Docker использует собственный драйвер Docker runc, который тесно связан со следующими механизмами ядра:

  • Cgroups (контрольные группы) — механизм управления ресурсами, используемыми контейнером (процессор, оперативная память и т.д.). С помощью cgroups Docker также реализует возможность остановки контейнеров — docker pause (freezing и unfreezing).
  • Namespaces (пространства имен) — механизм изоляции контейнеров, гарантирующий, что файловая система, имя хоста, пользователи, сетевая среда и процессы любого контейнера полностью отделены от остальной части системы.

Ещё одна технология, которую Docker использует для хранения слоев в контейнере — файловая система с каскадно-объединенным монтированием (Union File System – UnionFS). Как видите Docker умело использует уже хорошо работающие технологии, и в этом его сила. Именно поэтому ранее мы выпустили большую обзорную статью о контейнеризации и разобрали там базовые механизмы ядра, использующиеся в контейнеризации.

История контейнеризации
История контейнеризации

Из статьи вы узнаете, что такое cgroups и namespaces, как они работают и, какими технологиями пользовались до их появления.

Читать статью

Сами по себе контейнеры не эфемерны, более того Docker их не скрывает и вы можете совершенно спокойно заглянуть в них. Они располагаются здесь: /var/lib/docker/containers. Обращаться к контейнеру как к набору файлов бывает полезно, когда вам нужно прочитать логи контейнера.

Подытожим, что мы узнали о Docker-контейнерах:

  1. Docker-контейнер — это запущенный Docker-образ со специальным слоем для записи и временного хранения информации.
  2. В одном контейнере должен находится один процесс — это философия Docker.
  3. Контейнеры полностью изолированы от хостовой ОС и других контейнеров.
  4. Контейнеры хранятся в виде папок и файлов в директории /var/lib/docker/containers.
  5. Контейнерам могут быть назначены внешние порты инструкцией EXPOSE и ключом -P команды docker run.
  6. Контейнеры могут сохранять информацию после уничтожения в томах Docker volume.

Как вы наверное уже догадались, условия запуска контейнера могут быть заданы двумя путями: прописаны в конфигурационном файле — Dockerfile или указаны в качестве аргументов и ключей команды docker run. Разберём подробнее вариант с указанием инструкций в Dockerfile.

Dockerfile

Docker-контейнер

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

Например, мы изменили последнюю инструкцию в Dockerfile и собрали образ заново. Docker не будет заново всё пересобирать. Он возьмет все предшествующие слои и переиспользует их. Вот как образно выглядит соотношение инструкций в Dockerfile со слоями в Docker-образе:

Существуют определенные правила и логика заполнения Dockerfile. Разберем базовые инструкции на примере Dockerfile с иллюстрации выше.

1. FROM

Любой код или набор инструкций выполняется сверху вниз. Поэтому Dockerfile всегда начинается с открывающей инструкции FROM, которая говорит демону Docker, какой образ для основы нужно взять. Если образа локально нет — он будет скачан с Docker hub.

 

2. RUN

Далее идет инструкция RUN с определенной командой. В нашем случае мы использовали команду mkdir -p для создания папки /app. Инструкций RUN может быть неограниченное количество, но стоит помнить, что каждая инструкция создает свой слой, поэтому хорошей практикой является запись цепочки команд через &&: RUN comand_1 && comand_2 && comand_3.

 

3. WORKDIR

Есть инструкции логически связанные между собой. Инструкция WORKDIR устанавливает активный рабочий каталог. Все последующие команды, такие как COPY, RUN, CMD и некоторые другие будут выполнены из рабочего каталога, установленного через WORKDIR.

 

4. COPY

С инструкцией COPY всё просто. Первым аргументом указывается папка для копирования, а вторым аргументом — папка в контейнере куда будут помещены файлы из копируемой директории. В нашем случае, из текущего каталога (“.” — каталог, в котором находится пользователь, “..” — каталог выше относительно “.”.), в котором находится пользователь, был скопирован файл requirements.txt и помещен в папку /app в контейнере.

 

5. RUN

Инструкцию RUN мы рассмотрели ранее. Тут лишь хотим обратить ваше внимание на её поведение в сочетании с инструкцией WORKDIR. Ранее инструкция COPY перенесла файл requirements.txt в контейнер. Кстати, в качестве финального пути мы могли указать “.”, так как инструкция WORKDIR установила в качестве рабочей директории контейнера папку /app. И теперь команда RUN будет выполнена именно из директории /app.

 

6. CMD

Финальной инструкцией в любом Dockerfile является CMD или ENTRYPOINT. В отличие от других инструкций CMD может быть только одна и она может быть переопределена при старте контейнера командой docker run. Инструкция CMD наследует условия установленные инструкцией WORKDIR.

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

Однако после запуска контейнера «постучаться» на 80 порт к нему мы не сможем. Так как для подтверждения инструкции EXPOSE используется ключ -P команды docker run. Если требуется прокинуть порт и переназначить его снаружи — используется следующая команда: docker run -p 80:80 --name test_cont -d.

Подробнее об инструкциях Dockerfile и их специфики вы можете узнать из нашей статьи в базе знаний.

Итак, мы познакомились с основными элементами экосистемы Docker. Есть ещё специальная надстройка для управления множеством контейнеров Docker-compose, ей мы посвятим отдельную статью с подробным разбором, а пока подведем итог.

Что же такое Docker?

Docker

Docker — это система упаковки, доставки и развертывания приложений, распространяющаяся бесплатно. Docker состоит из следующих компонентов: Docker host, Docker daemon, Docker client, Docker-compose (не входит в стандартную поставку).

Центральный системный элемент инфраструктуры Docker — Docker daemon. Именно он создаёт образы и контейнеры, следит за их состоянием, управляет сетевым окружением контейнеров и работает с локальным и удалённым репозиторием.

Двумя основными сущностями, которыми оперирует Docker являются: образ и контейнер. Эти понятия тесно связаны.

Docker-образ — это шаблон, из которого создаются контейнеры. Образ содержит всё необходимое для запуска контейнеризированного приложения на любой *nix клиентской системе. Docker старается максимально переиспользовать уже готовые шаблоны, поэтому если нужный шаблон не будет найден локально — он будет скачан из удалённого репозитория.

Docker-контейнер — это запущенный и изолированный образ с дополнительным верхним write/read-слоем, хранящим временные данные, которые уничтожаются после удаления контейнера. Контейнерам можно назначать лимиты ресурсов и строить между ними сети. Для управления ресурсами используются cgroups, а для изоляции — namespaces.

Образы собираются исходя из инструкций заданных в специальном конфигурационном файле — Dockerfile. Контейнеры при запуске также используют часть инструкций Dockerfile и опции команды docker run. А для управления кластером или группой контейнеров используется надстройка Docker-compose и конфигурационный файл Docker-compose.yml.

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

Docker отлично справляется с созданием и управлением образами и небольшим количеством контейнеров, однако современные инфраструктуры состоят из 100 микросервисов, для их менеджмента используется Kubernetes. О нём мы расскажем в ближайших публикациях, а пока мы готовим материалы, вы можете поупражняться в Docker, используя самые доступные серверы 1cloud на базе VMware и статьи из нашей базы знаний.