Продвинутая работа с Docker — Docker-compose

В нашей предыдущей статье — «Введение в Docker: образы, контейнеры и докер-файлы», мы разобрали, что такое Docker, из каких элементов он состоит, научились создавать образы и запускать одиночные контейнеры. Настало время более серьезных и масштабных экспериментов.

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

Начнём с разбора того, что такое Docker-compose.

Что такое Docker-compose?

Что такое 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

Создаем проект для запуска в 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

В 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.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

Если запустились не все контейнеры — введите 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 в частности.