В нашей предыдущей статье — «Введение в Docker: образы, контейнеры и докер-файлы», мы разобрали, что такое Docker, из каких элементов он состоит, научились создавать образы и запускать одиночные контейнеры. Настало время более серьезных и масштабных экспериментов.
В этой статье разберемся с тем, что такое Docker-compose и как он работает на реальном небольшом проекте — положим Nginx и php-mfp в контейнеры, так чтобы они работали как одно целое.
Начнём с разбора того, что такое 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
Наш проект состоит из двух связанных контейнеров: 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.
Создайте VPS/VDS с виртуализацией Enterprise-уровня
Вы можете создать свой проект и изучить на нем работу Docker-compose. Для этого разверните виртуальный сервер в облаке 1cloud. На выбор представлены все популярные ОС, а конфигурацию вашей ВМ можно гибко настроить под любой проект.
Следующая логически связанная с 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.