Развертывание нескольких контейнеров с помощью Docker Compose
В этой статье показано, как развернуть несколько контейнеров ИИ Azure. В частности, вы узнаете, как использовать Docker Compose для оркестрации нескольких образов контейнеров Docker.
Docker Compose — это средство для определения и запуска приложений Docker с несколькими контейнерами. При работе в Compose вы используете файл YAML для настройки служб приложения. Затем вы создаете и запускаете все службы из конфигурации путем выполнения одной команды.
Иногда полезно выполнять оркестрацию нескольких образов контейнеров на одном главном компьютере. В этой статье мы объединим контейнеры Read и Document Intelligence.
Предварительные требования
Для выполнения этой процедуры необходимо установить и запустить в локальной среде несколько средств.
- Подписка Azure. Если у вас еще нет подписки Azure, создайте бесплатную учетную запись, прежде чем начать работу.
- Подсистема Docker. Проверьте работоспособность Docker CLI в окне консоли.
- Ресурс Azure с правильной ценовой категорией. Этот контейнер поддерживают только следующие ценовые категории.
- Ресурс Azure AI Vision только с ценовой категорией F0 или «Стандартный».
- Ресурс аналитики документов только с ценовой категорией F0 или «Стандартный».
- Ресурс служб ИИ Azure с ценовой категорией S0.
Файл Docker Compose
Файл YAML определяет все службы, которые необходимо развернуть. Эти службы зависят либо от DockerFile , либо от существующего образа контейнера. В данном случае мы будем использовать два образа предварительной версии. Скопируйте и вставьте следующий файл YAML, а затем сохраните его как docker-compose.yaml. Задайте в файле надлежащие значения apikey, billing и EndpointUri.
version: '3.7' services: forms: image: "mcr.microsoft.com/azure-cognitive-services/form-recognizer/layout" environment: eula: accept billing: # < Your Document Intelligence billing URL >apikey: # < Your Document Intelligence API key >FormRecognizer__ComputerVisionApiKey: # < Your Document Intelligence API key >FormRecognizer__ComputerVisionEndpointUri: # < Your Document Intelligence URI >volumes: - type: bind source: E:\publicpreview\output target: /output - type: bind source: E:\publicpreview\input target: /input ports: - "5010:5000" ocr: image: "mcr.microsoft.com/azure-cognitive-services/vision/read:3.1-preview" environment: eula: accept apikey: # < Your Azure AI Vision API key >billing: # < Your Azure AI Vision billing URL >ports: - "5021:5000"Создайте на главном компьютере каталоги, указанные в узле тома. Этот подход необходим, поскольку каталоги должны существовать до попытки подключения образа с помощью привязок томов.
Запуск настроенных служб Docker Compose
Файл Docker Compose обеспечивает управление всеми этапами в жизненном цикле определенной службы: запуском, остановкой и перестроением служб; просмотром состояния службы; и потоковой передачей журналов. Откройте интерфейс командной строки из каталога проекта (где находится файл docker-compose.yaml).
Во избежание ошибок убедитесь, что главный компьютер правильно предоставляет подсистеме Docker общий доступ к дискам. Например, если E:\publicpreview используется в качестве каталога в файле docker-compose.yaml, предоставьте Docker общий доступ к диску E.
В интерфейсе командной строки выполните следующую команду, чтобы запустить (или перезапустить) все службы, определенные в файле docker-compose.yaml:
docker-compose upПри первом выполнении команды docker-compose up с помощью этой конфигурации Docker извлекает образы, настроенные в узле служб, а затем загружает и подключает их:
Pulling forms (mcr.microsoft.com/azure-cognitive-services/form-recognizer/layout:). latest: Pulling from azure-cognitive-services/form-recognizer/layout 743f2d6c1f65: Pull complete 72befba99561: Pull complete 2a40b9192d02: Pull complete c7715c9d5c33: Pull complete f0b33959f1c4: Pull complete b8ab86c6ab26: Pull complete 41940c21ed3c: Pull complete e3d37dd258d4: Pull complete cdb5eb761109: Pull complete fd93b5f95865: Pull complete ef41dcbc5857: Pull complete 4d05c86a4178: Pull complete 34e811d37201: Pull complete Pulling ocr (mcr.microsoft.com/azure-cognitive-services/vision/read:3.1-preview:). latest: Pulling from /azure-cognitive-services/vision/read:3.1-preview f476d66f5408: Already exists 8882c27f669e: Already exists d9af21273955: Already exists f5029279ec12: Already exists 1a578849dcd1: Pull complete 45064b1ab0bf: Download complete 4bb846705268: Downloading [=========================================> ] 187.1MB/222.8MB c56511552241: Waiting e91d2aa0f1ad: Downloading [==============================================> ] 162.2MB/176.1MBПосле скачивания образов запускаются службы образов:
Starting docker_ocr_1 . done Starting docker_forms_1 . doneAttaching to docker_ocr_1, docker_forms_1forms_1 | forms_1 | forms_1 | Notice: This Preview is made available to you on the condition that you agree to the Supplemental Terms of Use for Microsoft Azure Previews [https://go.microsoft.com/fwlink/?linkid=2018815], which supplement your agreement [https://go.microsoft.com/fwlink/?linkid=2018657] governing your use of Azure. If you do not have an existing agreement governing your use of Azure, you agree that your agreement governing use of Azure is the Microsoft Online Subscription Agreement [https://go.microsoft.com/fwlink/?linkid=2018755] (which incorporates the Online Services Terms [https://go.microsoft.com/fwlink/?linkid=2018760]). By using the Preview you agree to these terms. forms_1 | forms_1 | forms_1 | Using '/input' for reading models and other read-only data. forms_1 | Using '/output/forms/812d811d1bcc' for writing logs and other output data. forms_1 | Logging to console. forms_1 | Submitting metering to 'https://westus2.api.cognitive.microsoft.com/'. forms_1 | WARNING: No access control enabled! forms_1 | warn: Microsoft.AspNetCore.Server.Kestrel[0] forms_1 | Overriding address(es) 'http://+:80'. Binding to endpoints defined in UseKestrel() instead. forms_1 | Hosting environment: Production forms_1 | Content root path: /app/forms forms_1 | Now listening on: http://0.0.0.0:5000 forms_1 | Application started. Press Ctrl+C to shut down. ocr_1 | ocr_1 | ocr_1 | Notice: This Preview is made available to you on the condition that you agree to the Supplemental Terms of Use for Microsoft Azure Previews [https://go.microsoft.com/fwlink/?linkid=2018815], which supplement your agreement [https://go.microsoft.com/fwlink/?linkid=2018657] governing your use of Azure. If you do not have an existing agreement governing your use of Azure, you agree that your agreement governing use of Azure is the Microsoft Online Subscription Agreement [https://go.microsoft.com/fwlink/?linkid=2018755] (which incorporates the Online Services Terms [https://go.microsoft.com/fwlink/?linkid=2018760]). By using the Preview you agree to these terms. ocr_1 | ocr_1 | ocr_1 | Logging to console. ocr_1 | Submitting metering to 'https://westcentralus.api.cognitive.microsoft.com/'. ocr_1 | WARNING: No access control enabled! ocr_1 | Hosting environment: Production ocr_1 | Content root path: / ocr_1 | Now listening on: http://0.0.0.0:5000 ocr_1 | Application started. Press Ctrl+C to shut down.Проверка доступности службы
Используйте команду docker images, чтобы получить список скачанных образов контейнеров. Например, следующая команда возвращает таблицу со списком идентификаторов, репозиториев и тегов для каждого скачанного образа контейнера:
docker images --format "table >\t>\t>" IMAGE ID REPOSITORY TAG
Вот пример выходных данных:
IMAGE ID REPOSITORY TAG 2ce533f88e80 mcr.microsoft.com/azure-cognitive-services/form-recognizer/layout latest 4be104c126c5 mcr.microsoft.com/azure-cognitive-services/vision/read:3.1-preview latestdocker compose pull
Pulls an image associated with a service defined in a compose.yaml file, but does not start containers based on those images.
Options
Option Short Default Description —ignore-buildable Ignore images that can be built. —ignore-pull-failures Pull what it can and ignores images with pull failures. —include-deps Also pull services declared as dependencies. —no-parallel true DEPRECATED disable parallel pulling. —parallel true DEPRECATED pull multiple images in parallel. —policy Apply pull policy («missing»|»always»). —quiet -q Pull without printing progress information. Examples
Consider the following compose.yaml :
content_copy
- - - If you run docker compose pull ServiceName in the same directory as the compose.yaml file that defines the service, Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example, you would run docker compose pull db .content_copy
docker compose pull tries to pull image for services with a build section. If pull fails, it lets you know this service image must be built. You can skip this by setting —ignore-buildable flag.
Что такое Docker: для чего он нужен и где используется
В статье мы расскажем, что такое контейнеры, где они применяются и чем могут быть полезны.

В первую очередь эта статья будет полезна тем, кто вообще не знаком с контейнерами или Docker. Мы расскажем самые базовые вещи, а наш пример по созданию приложения будет довольно простым. Но это позволит вам понять основы Docker и затем двигаться дальше — изучать более сложные материалы.
В конце будет практическая часть: мы создадим небольшое приложение, обернем его в образ и запустим. Все действия будем показывать на примере виртуальной машины облачной платформы Selectel.
Managed Kubernetes помогает разворачивать контейнерные приложения в инфраструктуре Selectel. Сосредоточьтесь на разработке, а мы займемся рутинными операциями по обеспечению работы вашего кластера Kubernetes.
Что такое контейнеры
Прежде чем рассказывать про Docker, нужно сказать несколько слов о технологии контейнеризации.
Контейнеры — это способ стандартизации развертки приложения и отделения его от общей инфраструктуры. Экземпляр приложения запускается в изолированной среде, не влияющей на основную операционную систему.
Разработчикам не нужно задумываться, в каком окружении будет работать их приложение, будут ли там нужные настройки и зависимости. Они просто создают приложение, упаковывают все зависимости и настройки в некоторый единый образ. Затем этот образ можно запускать на других системах, не беспокоясь, что приложение не запустится.
Docker — это платформа для разработки, доставки и запуска контейнерных приложений. Docker позволяет создавать контейнеры, автоматизировать их запуск и развертывание, управляет жизненным циклом. Он позволяет запускать множество контейнеров на одной хост-машине.
Контейнеризация похожа на виртуализацию, но это не одно и то же. Виртуализация запускает полноценный хост на гипервизоре со своим виртуальным оборудованием и операционной системой. При этом внутри одной ОС можно запустить другую ОС. В случае контейнеризации процесс запускается прямо из ядра основной операционной системы и не виртуализирует оборудование. Это означает, что контейнеризованное приложение может работать только в той же ОС, что и основная. Контейнеры не виртуализируют оборудование, поэтому потребляют меньше ресурсов.
Преимущества использования контейнеров Docker
Контейнеры упрощают работу как программистам, так и администраторам, которые развертывают эти приложения.
Docker решает проблемы зависимостей и рабочего окружения
Контейнеры позволяют упаковать в единый образ приложение и все его зависимости: библиотеки, системные утилиты и файлы настройки. Это упрощает перенос приложения на другую инфраструктуру.
Например, разработчики создают приложение в системе разработки — там все настроено, приложение работает. Когда оно готово, его нужно перенести в систему тестирования, а затем в продуктивную среду. Если в одной из них нет нужной зависимости, приложение не будет работать. Программистам придется отвлечься от разработки и совместно с командой поддержки разобраться в ситуации.
В контейнерах такой проблемы нет, так как они содержат в себе все необходимое для запуска приложения. Специалисты занимаются разработкой, а не решением инфраструктурных проблем.
Изоляция и безопасность
Контейнер — это набор процессов, изолированных от основной операционной системы. Приложения работают только внутри контейнеров и не имеют доступа к основной операционной системе. Это повышает безопасность приложений:они не смогут случайно или умышленно навредить основной системе. Если приложение в контейнере завершится с ошибкой или зависнет, это никак не затронет основную ОС.
Ускорение и автоматизация развертывания приложений и масштабируемость
Контейнеры упрощают развертывание приложений. В классическом подходе для установки программы нужно совершить несколько действий: выполнить скрипт, изменить файлы настроек и так далее. В этом процессе не исключена вероятность человеческой ошибки: пользователь запустит скрипт два раза, перепутает последовательность или что-то не поймет. Контейнеры позволяют полностью автоматизировать этот процесс, так как включают в себя все нужные зависимости и порядок выполнения действий.
Также контейнеры упрощают развертывание на нескольких серверах. В классическом подходе для того, чтобы развернуть одно и то же приложение на нескольких машинах, нужно будет повторять одни и те же действия. Контейнеры избавляют от этой рутинной работы и позволяют автоматизировать развертывание.
Контейнеры приближают к микросервисной архитектуре
Контейнеры хорошо вписываются в микросервисную архитектуру. Это подход к разработке, при котором приложение разбивается на небольшие компоненты, по возможности независимые. Обычно противопоставляется монолитной архитектуре, где все части системы сильно связаны друг с другом.
Это позволяет разрабатывать новую функциональность быстрее, ведь в случае с монолитной архитектурой изменение какой-то части может затронуть всю остальную систему.
Docker compose — одновременно развернуть несколько контейнеров
Docker-compose позволяет разворачивать и настраивать несколько контейнеров одновременно. Например, для веб-приложения нужно развернуть стек LAMP: Linux + Apache, MySQL, PHP. Каждое из приложений — это отдельный контейнер для ОС Linux. Но в этой ситуации нам нужны именно все контейнеры вместе, а не отдельно взятое приложение. Docker-compose позволяет развернуть и настроить все приложения одной командой, а без него пришлось бы разворачивать и настраивать каждый контейнер отдельно.
Хранение данных в Docker
Одна из главных особенностей контейнеров — эфемерность. Это означает, что контейнеры могут быть в любой момент остановлены, перезапущены или уничтожены. При этом все накопленные данные в контейнере будут потеряны. Поэтому приложения нужно разрабатывать так, чтобы они не полагались на хранилище данных в контейнере, это называется принципом Stateless.
Это хорошо подходит для приложений или сервисов, которые не сохраняют результаты своей работы. Например, функции расчета или преобразования данных: им на вход поступил один набор данных, они его преобразовали или рассчитали и вернули результат. Все, ничего никуда сохранять не нужно.
Но далеко не все приложения такие, и есть много данных, которые нужно сохранить. В контейнерах для этого предусмотрены несколько способов.
Тома (Docker volumes)
Это способ, при котором Docker сам создает директории для хранения данных. Их можно сделать доступными для разных контейнеров, чтобы они могли обмениваться данными. По умолчанию эти директории создаются на хост-машине, но можно использовать и удаленные хранилища: файловый сервер или объектное хранилище.
Монтирование каталога (bind mount)
В этом случае директория сначала создается на хост-машине а уже потом монтируется в контейнеры.
Но этот способ не рекомендуется, потому что он усложняет резервное копирование, миграцию и совместное использование данных несколькими контейнерами.
Архитектура (компоненты) Docker
Теперь расскажем подробнее про компоненты, из которых состоит Docker.
Docker daemon
Это некоторый резидентный процесс, который запущен на хост-машине постоянно. Он владеет всей инфраструктурой, а также предоставляет интерфейс взаимодействия с контейнерами, включающего создание и удаление, запуск и остановку.
В ранних версиях платформы Docker можно встретить упоминание о dockerd, но на текущий момент демоны уже успели разбиться на отдельные проекты. Все чаще можно встретить его современника — containerd.
Docker client (клиент)
Это интерфейс командной строки для управления Docker daemon. Мы пользуемся этим клиентом, когда создаем и разворачиваем контейнеры, а клиент отправляет эти запросы в Docker daemon.
Docker image (образ)
Это неизменяемый файл (образ), из которого разворачиваются контейнеры. Приложения упаковываются именно в образы, из которых потом уже создаются контейнеры. В технической литературе можно также встретить описание image как шаблона запуска процесса.
Приведем аналогию на примере установки операционной системы. В дистрибутиве (образе) ОС есть все, что необходимо для ее установки. Но этот образ нельзя запустить, для начала его нужно «развернуть» в готовую ОС. Так вот, дистрибутив для установки ОС — это образ, а установленная и работающая ОС — это контейнер. Но контейнеры обычно разворачиваются одной командой — это намного проще и быстрее, чем установка ОС.
Docker container (контейнер)
Это уже развернутое из образа и работающее приложение.
Docker Registry
Это репозиторий с образами. Разработчики создают образы своих программ и выкладывают их в репозиторий, чтобы их можно было скачать и воспользоваться ими. Распространенный публичный репозиторий — Docker Hub. В нем собраны образы множества популярных программ или платформ: базы данных, веб-серверы, компиляторы, операционные системы и так далее. Также можно создать свой приватный репозиторий, например внутри компании. Разработчики будут размещать там образы, которые будут использоваться всей компанией.
Dockerfile
Dockerfile — это инструкция для сборки образа. Это простой текстовый файл, содержащий по одной команде в каждой строке. В нем указываются все программы, зависимости и образы, которые нужны для разворачивания образа.
Для примера рассмотрим Dockerfile, который мы будем использовать далее в этой статье чтобы развернуть собственное приложение:
FROM python:3 COPY main.py / CMD [ "python", "./main.py" ]Первая строчка означает, что за основу мы берем образ с названием python версии 3 это называется базовый образ. Docker найдет его в docker registry, скачает и будет использовать за основу. Вторая строчка означает, что нужно скопировать файл main.py в корень файловой системы контейнера. Третья строчка означает, что нужно запустить python и передать ему в качестве параметра название файла main.py.
Далее рассмотрим примеры нескольких команд докер и что происходит, когда мы их выполняем.

Все эти команды выполняются в Docker client, который отправляет их в Docker daemon:
- Команда docker build (зеленая стрелка) читает dockerfile и собирает образ.
- Команда docker pull (красная стрелка) скачивает образ из docker registry. По умолчанию docker скачивает образы из публичного репозитория Docker Hub. Но можно создать свой репозиторий и настроить докер, чтобы он работал с ним.
- Команда docker run (черная стрелка) берет образ и запускает из него контейнер.
Создаем виртуальную машину для работы с Docker
Перейдем к практической части. Мы установим докер, создадим приложение, обернем его в контейнер и запустим. Мы для примера будем использовать виртуальную машину на платформе Selectel.
В панели управления заходим в раздел «Облачная платформа» — «Серверы», нажимаем кнопку «Создать сервер».

На следующем экране выбираем параметры сервера: имя, регион, ОС, параметры производительности и так далее. Сейчас для нас важны параметры «Источник» — выбираем ОС Ubuntu 20.04 и «Конфигурация» — выбираем 2 vCPU и 8 ГБ оперативной памяти.

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

После этого внизу страницы нажимаем кнопку «Создать». Виртуальная машина создается за несколько минут, и после того, как она перейдет в статус ACTIVE, к ней можно подключаться по SSH.

Установка Docker
Мы рассмотрим установку докера на примере Ubuntu. Если у вас другой дистрибутив Linux или операционная система — ищите соответствующую инструкцию на официальном сайте.
Для начала синхронизируем пакетную базу apt и установим нужные зависимости:
sudo apt-get updatesudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg \ lsb-releaseДалее импортируем GPG-ключ для репозитория docker:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgТеперь добавим новый репозиторий в список apt:
echo \ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullТеперь можно устанавливать докер:
sudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.ioПо умолчанию, доступ к docker daemon есть только у пользователя root. Чтобы с докером могли работать и другие пользователи, их нужно добавить в специальную группу — docker. Выполните эту команду из под обычного пользователя:
sudo usermod -aG docker $USERПосле этого необходимо перелогиниться, чтобы изменение вступило в силу.
Запуск контейнера
Теперь попробуем запустить какое-нибудь готовое приложение. Выполните команду:
docker run ubuntu echo 'hello from ubuntu'Команда docker run создает и запускает контейнер из образа. В этом примере мы создаем контейнер из образа ubuntu, затем выполняем в нем команду echo ‘hello from ubuntu’. Но так как у нас чистая установка докера и мы не скачали ни одного образа, докер сначала найдет этот образ в публичном репозитории Docker Hub, скачает, а потом создаст из него контейнер. В следующий раз, когда нам понадобится образ ubuntu, докер уже не будет его скачивать.
После выполнения команды в терминале появится строка hello from ubuntu, и контейнер сразу остановится. Теперь выполним другую команду:
docker run -it ubuntuЭта команда запустит контейнер в интерактивном режиме, то есть контейнер запустится и будет ждать дальнейших команд. При этом мы окажемся внутри операционной системы контейнера: запустится оболочка (bash), и мы сможем выполнять какие-то команды внутри контейнера. Чтобы выйти из контейнера, введите команду exit.
Создание собственного образа и запуск контейнера
Теперь создадим HelloWorld-приложение на Python, обернем его в образ и запустим.
Для начала создадим директорию, в которой мы будем работать и перейдем в нее:
mkdir first-docker-appcd first-docker-appСоздадим файл main.py и запишем в него одну строчку кода:
echo 'print("Hello from python");' >> main.pyПроверим, что наша программа работает. Для этого выполним команду:
python main.pyВ консоли должно выйти сообщение Hello from python. Это и есть наше простое приложение. Теперь нужно обернуть его в докер-образ. Для этого создадим файл Dockerfile и напишем в нем три строчки:
FROM python:3 COPY main.py / CMD [ "python", "./main.py" ]В первой строке мы указываем образ, который берем за основу. Так как мы пишем приложение на Python, нужно чтобы в нашем образе он уже был установлен. Самый простой способ это сделать — использовать готовый официальный образ с Docker Hub. Цифра 3 — это тег. Он означает, что нужно использовать третью версию Python. Вместо этого можно было бы использовать тег latest, который означает самую последнюю версию, или можно было указать номер конкретной версии, например 3.8.8.
Во второй строчке мы копируем наш файл main.py в корневую директорию образа.
Третья строчка — запускаем python и передаем ему в качестве параметра имя нашего файла.
Теперь из этого докер-файла можно собирать образ. Выполним команду:
docker build -t first-docker-app .Параметр -t обозначает имя нашего образа, мы назвали его first-docker-app.
Так как у нас еще нет скачанного образа python, то докер сам скачает его из Docker Hub и затем будет использовать его в качестве основы для создания нашего образа.
Проверим список установленных у нас образов:
docker imagesМы увидим, что у нас установлено три образа:
REPOSITORY TAG IMAGE ID CREATED SIZE first-docker-app latest 649cceb4dfd2 4 seconds ago 885MB python 3 b1aa63f57d3c 2 days ago 885MB ubuntu latest 8e428cff54c8 4 days ago 72.9MBfirst-docker-app — это наш образ, который мы только что создали. python — это образ python, который докер автоматически скачал чтобы собрать наш образ. ubuntu — образ, который мы пробовали для запуска готового приложения.
Теперь создадим контейнер из нашего образа и запустим его:
docker run first-docker-appВ результате нам выведется результат: Hello from python.
Итог: Мы создали свое приложение, упаковали его в докер-образ и запустили. Конечно, это очень простой пример. Наша программа состоит всего из одной строчки, а dockerfile из трех. Но это позволяет понять базовые принципы работы докера, как он устроен, как создавать свои образы и запускать контейнеры.
Готовые кластеры Kubernetes с GPU
Создайте за несколько кликов.
Продвинутая работа с Docker — Docker-compose


Docker-compose — это надстройка над докером, приложение написанное на Python, которое позволяет запускать множество контейнеров одновременно и маршрутизировать потоки данных между ними. Для каждого проекта (кластера контейнеров) Docker создаёт свою сеть, где контейнеры могут обращаться друг к другу по именам, которые мы укажем в docker-compose.yml. Все настройки запуска кластера контейнеров находятся в этом же файле, который располагается в корневой директории проекта. Docker-compose.yml мало чем похож на знакомые нам уже docker-файлы. В отличие от них, docker-compose.yml записан не в декларативном ini-стиле как docker-файлы, а в древовидном YAML. Если вы ещё не знакомы с YAML — рекомендуем потратить 10-15 минут и ознакомиться с нашим кратким мануалом по YAML. С ним всё нижеизложенное станет куда более понятным.
Создаем проект для запуска в Docker-compose

Наш проект состоит из двух связанных контейнеров: Nginx и php. Структура нашего проекта выглядит так:

- www — каталог содержит index.html, index.php, подкаталог phpinfo. Эти файлы нужны для корректной работы Nginx и php-fpm. www также скопировано в каталоги nginx и php, из которых будут собираться контейнеры;
- nginx — директория содержит конфигурационный файл nginx custom.conf, Dockerfile, по которому будет собираться nginx-контейнер и подкаталог www (копию корневого www), который будет скопирован в контейнер для проверки работоспособности и подменен в будущем;
- php — директория содержит Dockerfile по которому будет собираться php-контейнер и аналогичный подкаталог www.
Такая структура проекта продиктована логикой сборки проекта. Сначала собираются образы Nginx и php из docker-файлов, расположенных в одноименных директориях, затем запускаются контейнеры в соответствии с инструкциями в Docker-compose.yml
Dockerfile Nginx
FROM nginx:mainline-alpine #Берем родительский образ Nginx
EXPOSE 80 # Открываем 80 порт
COPY custom.conf /etc/nginx/conf.d #Подменяем конфиг Nginx conf.d в контейнере на custom.conf
COPY ./www /var/www #Копируем содержимое www в /var/www
CMD [«nginx», «-g», «daemon off;»] #Запускаем Nginx как процесс.Dockerfile php
FROM php:7.4-fpm #Берем родительский образ php:7.4-fpm
EXPOSE 9000 #Открываем 9000 порт
WORKDIR /var/www/ # Устанавливаем рабочим каталогом контейнера /var/www/
COPY ./www/ /var/www/ #Копируем содержимое хостового каталога www в директорию var/www/
CMD [«php-fpm»] #Выполняем команду php-fpmВ Docker-файле Nginx есть два ключевых момента для нашего проекта, которые нужно разобрать:
- инструкция COPY custom.conf /etc/nginx/conf.d копирует custom.conf из локальной папки nginx и подменяет им conf.d в контейнере Nginx. Без него Nginx не отработает.
- инструкция CMD с параметром [«nginx», «-g», «daemon off;»] запускает Nginx как процесс, а не как демон.
Кстати, в составлении Docker-файлов есть свои best practice. О некоторых из них, вы можете узнать из нашего небольшого мануала по оптимизации сборки Docker-образов.
Теперь, чтобы были понятны наши следующие действия нужно обратиться к настройкам Nginx, тому самому конфигурационному файлу custom.conf, которым мы подменяли стандартный conf.d, находящийся в Nginx-контейнере.
Внутри custom.conf выглядит так:
server < listen 80;
server_name 5.101.77.54;
root /var/www;
index index.html index.php;
location ~ \.php$ try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
>
>Нас интересуют следующие параметры:
- server_name — имя сервера, к которому мы обращаемся. В нашем случае, это IP-адрес нашей VPS.
- root — корневая папка, в которой Nginx будет искать файлы для исполнения. Мы её будем подменять в дальнейшем через docker-compose.yml для визуализации процесса разработки.
- fastcgi_pass php:9000 — проброс запроса на отдачу динамического контента в контейнер php на порт 9000.
Со структурой проекта, docker-файлами и настройками Nginx разобрались. Переходим к ключевому — разбираемся с docker-compose.yml.
docker-compose.yml — центр управления полётами

Docker-compose файл описывает процесс загрузки и настройки контейнеров. Разберём Docker-compose.yml, который мы подготовили для нашего проекта. Код нашего файла выглядит так:
version: «2.3» #Задаем версию docker-compose.yml
services: #Задаем контейнеры
nginx: #Задаем название первого контейнера — nginx и настраиваем его
build: ./nginx #Указываем откуда будет вестись сборка
ports: # Указываем какие порты нужно пробросить наружу
— «80:80»
volumes: #Подключаем рабочий каталог с кодом проекта
— ./www:/var/www
depends_on: #Устанавливаем последовательность загрузки контейнеров
php: # php-контейнер запуститься раньше Nginx
condition: service_healthy #Устанавливаем условия при котором запуститься контейнер nginx
php: #Задаем название первого контейнера — php и настраиваем его
build: ./php #Указываем откуда будет вестись сборка
volumes: #Подключаем тот же рабочий каталог с кодом проекта
— ./www:/var/www
healthcheck: #Проверка работы приложения внутри контейнера
test: [«CMD»,»php-fpm»,»-t»] #Команда теста, которую мы хотим выполнить
interval: 3s #Интервал попыток запуска теста
timeout: 5s #Отложенность запуска команды
retries: 5 #Количество повторений
start_period: 1s #Через сколько стартовать тест после запуска контейнераНаш Docker-compose.yml невелик, однако он может быть огромным и иметь сложную структуру из разных типов организации данных: строк, списков, словарей. По началу даже в не очень сложном Docker-compose.yml можно наделать ошибок, которые на глаз не видны, поэтому перед запуском сборки проекта — проверьте код, каким-нибудь YAML-валидатором, например, yamllint.
В начале файла мы задали инструкцию version со значением 2.3 — это сделано специально, так как разные версии Docker-compose.yml содержат разный набор инструкций. Так в версии 3 нет инструкции healthcheck, а она критически важна для нас в этом проекте.
Инструкция healthcheck (блок php-контейнера) позволяет нам проверить работоспособность приложения в контейнере, указав с помощью другой инструкции test команду для тестирования. Смежные инструкции interval, timeout, retries, start_period устанавливают временные условия выполнения инструкции test.
Следующая логически связанная с healthcheck инструкция — depends_on (блок nginx-контейнера) — контроль порядка запуска. Логика работы depends_on такова: пока успешно не закончатся все действия указанные в блоке condition над контейнером заданным выше строчкой, контейнер, в котором расположена инструкция depends_on не запустится.
В нашем случае пока успешно не выполнится тест php-контейнера, Nginx-контейнер не запустится. Это сделано для правильной очередности загрузки инфраструктурных элементов нашего проекта.
depends_on: #Устанавливаем последовательность загрузки контейнеров
php: # php-контейнер запуститься раньше Nginx
condition: service_healthy #Устанавливаем условия при котором запуститься контейнер nginxСкажем еще несколько слов об инструкциях build и volumes:
- build — указывает на директорию из которой будет собран контейнер, в ней должен быть dockerfile;
- volumes — прокидывает локальную папку в контейнер. В нашем случае все изменения внесенные в файлы директории www будут автоматически доступны в контейнерах по пути /var/www.
Обратимся к логической блок-схеме для полного понимания процесса сборки и запуска нашего проекта, а потом перейдём непосредственно к сборке проекта.
Схема взаимодействия структурных элементов проекта и их свойств

Теперь, когда мы понимаем внутренние взаимосвязи внутри проекта — настало время его собрать. Для запуска контейнеров через docker-compose используются следующие команды:
- docker-compose build — собрать проект
- docker-compose up -d — запустить проект
- docker-compose down — остановить проект
- docker-compose logs -f [service name] — посмотреть логи сервиса
- docker-compose ps — вывести список контейнеров
- docker-compose exec [service name] [command» — выполнить команду в контейнере
- docker-compose images — список образов
Находясь в корневом каталоге проекта вызовем команду docker-compose up -d. Вот какие действия будут выполнены docker-compose: 1) Сборка образа php; 2) Сборка образа Nginx; 3) Запуск контейнера php; 4) Тест php; 5) Запуск контейнера Nginx. Проверим, запустились контейнеры командой docker ps. В ответ команда вернет сообщение следующего вида:

Если запустились не все контейнеры — введите docker ps -a и docker logs . Вывод будет содержать коды и наименования ошибок. В нашем случае оба контейнера запустились и работают на нужных портах.
Проверим работу Nginx и php с помощью команды curl:
curl -I http://5.101.77.54/ 2>/dev/null | head -n 1 | cut -d$’ ‘ -f2
curl -I http://5.101.77.54/phpinfo/ 2>/dev/null | head -n 1 | cut -d$’ ‘ -f2В обоих случаях curl нам вернул код ответа 200. Это значит, что Nginx нашёл нужную страницу и отдал ее нам.
На этом сборка и тестирование проекта завершена, можно подводить итоги.
Что получается в итоге

Docker-compose — это система сборки, запуска и управления множеством контейнеров. Docker-compose не входит в единый пакет поставки Docker и устанавливается отдельно. Для сборки кластера контейнеров используется docker-compose.yml.
Docker-compose.yml — конфигурационный файл в YAML-формате, описывающий логику запуска и взаимодействия контейнеров между собой и внешним миром. В сущности инструкции заложенные в docker-compose.yml по логике работы идентичны ключам команды docker run.
На первое место при работе с Docker-compose выходит структура проекта. Она должна следовать правилам работы Docker-контейнеров — в одном контейнере должен быть только один процесс. Хорошей практикой является составление процессной карты взаимодействия элементов вашего проекта между собой и её перенос на логику работы Docker-compose.
В статье мы показали базовые возможности и основные взаимодействия с docker-compose, которых вполне будет достаточно для самостоятельной практики и экспериментов. Лучше всего для этого использовать заранее подготовленные виртуальные серверы.
Если материал этой статьи вам показался непонятным — рекомендуем ознакомиться с предыдущими нашими публикациями, они помогут заполнить возможные пробелы по теории контейнеризации и по работе Docker в частности.

Введение в Docker
Разбираемся в том, что такое Docker, из каких компонентов состоит и какие технологии контейнеризации использует.

История контейнеризации
Краткая история контейнеризации и разбор конкретных технологий: chroot, jail, namespaces и cgroups.