skip to main content

Мультиконтейнерное приложение и Docker Compose

js

Кратко 🔗

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

Как начать 🔗

Если вы работаете на операционных системах Mac или Windows и установили Docker Desktop, то Docker Compose уже установлен автоматически. Если вы работаете на операционной системе семейства Linux, вам необходимо его установить, предварительно скачав последний релиз из репозитория. До установки убедитесь, что Docker Engine на Linux уже установлен и готов к работе (подробнее в статье «Что такое Docker?»). Процесс установки описан в официальной документации Docker.

Как понять 🔗

Рассмотрим мультиконтейнерное приложение на примере Wordpress.

Можно создать один образ с установленной базой данных, веб-сервером, интерпретатором PHP и движком Wordpress на борту. А можно сделать иначе.

Способы организации сайта на движке Wordpress с помощью Docker

Веб-приложение — это, как правило, сложная система взаимодействующих частей, которые называют сервисами. Команда разработчиков Docker рекомендует подход: один сервис — один контейнер. Этот подход позволяет легче отлавливать ошибки, проще модернизировать сервисы, избегать работы над всем приложением сразу. При большой нагрузке на отдельные сервисы такой подход упрощает масштабирование с помощью перераспределения сетевых запросов (маршрутизатор можно поместить в отдельный контейнер). Мониторинг и поток ошибок можно осуществить также с помощью отдельного сервиса, запущенного в контейнере. Совокупность описанных сервисов называется мультиконтейнерным приложением.

Вернемся к примеру. Что нужно для запуска сайта на Wordpress? В самом простом случае такое веб-приложение состоит из двух сервисов:

— веб-сервер с Wordpress;
— база данных.

Оба контейнера должны работать совместно. Мы можем написать Dockerfile для каждого из них и настроить взаимодействие друг с другом через виртуальную сеть Docker Network. Но такой ручной подход не очень удобен. Docker Compose — это инструмент, который помогает конфигурировать запуск сразу нескольких контейнеров и указывать им, как работать совместно.

Docker Compose поддерживает файлы конфигурации в формате YAML. Имя файла конфигурации по умолчанию compose.yaml. Для нашего примера такой файл мог бы выглядеть следующим образом (в качестве базы данных будем использовать MySQL):

Конфигурация мультиконтейнерного приложения для сайта на движке Wordpress

compose.yaml
version: "3.9"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: goodpassword
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
volumes:
  db_data: {}

В первой строчке этого файла конфигурации содержится информация о версии. С каждой новой версией формата описания функционал Docker Compose расширяется. Если раньше этот параметр был обязательным, то сейчас его можно не указывать, если не нужно поддерживать старую версию Docker Engine. Таблицу соответствия версий формата описания и движка Docker вы можете посмотреть тут.

В разделе services содержится описание всех контейнеров, запуск которых нужно настроить. Отдельно работающие приложения или службы в терминах Docker Compose называются сервисами.

В подразделе image необходимо указать имя образа, который должен присутствовать локально на компьютере или в реестре. Можно установить и переменные окружения в подразделе environment, порты для подключения к сервису (например, со стороны браузера) в подразделе ports, пути для монтирования томов в подразделе volumes и прочие параметры.

В различных базах данных, не только в MySQL, как у нас, существует возможность хранить данные в отдельном файле или папке. Логично было бы положить в отдельные файлы и данные нашего сайта на Wordpress. Для таких задач используется тома (Docker Volumes). На самом деле — это отдельные образы дисков. Для приложения в контейнере они видны как примонтированные папки. Следовательно, используя том db_data, при остановке или перезапуске контейнера с MySQL все данные сохранятся. Ну а от контейнера с самим движком данные вообще никак не связаны.

Файл конфигурации готов, и можно «поднять» все контейнеры всего одной командой:

docker-compose up

Когда все образы будут загружены, тома включены, переменные окружения установлены, сайт на Wordpress окажется доступным по адресу http://localhost:8000. В настройках compose.yaml указано, что если один из контейнеров упадет, то Docker Compose должен перезапустить его автоматически (restart: always). Вы сможете начать с того же места. Появляется еще одна возможность — можно обновить базу данных и при следующем перезапуске запустится уже новая версия. То же самое можно делать и с веб-сервером, и движком.

Выключить оба контейнера так же просто, как и включить:

docker-compose down

Вы можете подробнее посмотреть все команды Docker Compose CLI в официальной документации.

Как пишется 🔗

Поскольку Docker Compose работает с несколькими типами объектов Docker (образами, контейнерами, томами), то логично представить их настройки в виде дерева, что воплощено в формате YAML, который очень часто применяется в файлах конфигурации. .

Если вы когда-нибудь писали на языке Python, YAML покажется вам очень понятным. Каждый новый блок отделяется отступами. Блоки могут быть вложенными, что очень удобно, и зрительно воспринимается намного легче, чем, скажем, JSON. Несомненным плюсом является популярность YAML среди специалистов по инфраструктуре.

Предпочтительным расположением файла compose.yaml является корневая папка проекта, которая может содержать подпапки с сервисами мультиконтейнерного приложения. Для обеспечения обратной совместимости поддерживаются файлы с именами docker-compose.yaml и docker-compose.yml.

В файле compose.yaml могут быть следующие элементы верхнего уровня:

version (скоро выпилят): информация о версии формата файла конфигурации;
services (обязательный): список всех контейнеров, которые нужно будет запустить:
networks: список подсетей Docker Network, которые объединяют группы контейнеров в виртуальную локальную сеть (она может быть доступна из внешнего мира);
volumes: список томов, которые будут доступны контейнерам, описанным в файле конфигурации;
configs: список параметров, которые позволяют запускать контейнеры в различных режимах, не собирая их заново;
secrets: список чувствительных с точки зрения безопасности параметров (то же, что и configs, но специального назначения).

Services 🔗

Мультиконтейнерное приложение — система взаимодействующих сервисов. Как правило, один сервис обеспечивает какую-то одну функцию системы. Например, веб-сервер только отдает статический сайт (HTML, CSS и JS) браузеру, API служит для обмена данными. Сервисы — это самостоятельные (атомарные) микроприложения или службы, работающие независимо в отдельных контейнерах.

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

Разрабатывая мультиконтейнерное приложение, в голове нужно держать мысль о перспективах его масштабирования и поддержки. Например, один веб-сервер со статическим сайтом может обеспечить тысячу пользователей одновременно. А что если пользователей больше? Docker Compose в этом случае будет автоматически использовать дополнительные экземпляры сервиса, перенаправляя запросы к ним.

Например, мы запускаем два сервиса frontend и backend:

services:
  frontend:
    image: awesome/webapp
    build: ./webapp
    deploy:
      mode: replicated
      replicas: 6
  backend:
    image: awesome/database
    build:
      context: backend
      dockerfile: ../backend.Dockerfile
    deploy:
    resources:
      limits:
        cpus: '0.50'
        memory: 50M
      reservations:
        cpus: '0.25'
        memory: 20M

Скажем, нам нужно обеспечить до шести экземпляров сервиса frontend, ресурсы для которого будут расходоваться, пока они будут доступны. Тогда мы указываем это явно, как сделано в примере выше, с помощью настроек mode и replicas для элемента deploy. Настроек для развертывания сервиса (запуска, использования ресурсов процессора, памяти и прочее) очень много.

Сборка сервиса frontend описывается отдельно от параметров развертывания. В нашем случае она будет производиться из папки ./webapp с помощью файла с именем по умолчанию Dockerfile.

Для backend — другие настройки. Нам нужно собрать образ перед тем, как мы будем использовать приложение. Настройка context будет содержать относительный путь к папке сервиса. Вариантов сборки образа несколько, но наш будет описываться в файле backend.Dockerfile. А еще будут требования к ресурсам, которые использует приложение. Docker Compose:
— будет использовать процессор не более, чем на 50% в штатном режиме, и не более 75% в пиковых нагрузках;
— сервис будет использовать не более, чем 50 МБ оперативной памяти и 70 МБ в пике.

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

Networks 🔗

Параметры, описанные в элементе networks, позволяют настроить виртуальную сеть Docker Network для совместной работы нескольких контейнеров. Например, можно указать две подсети, одна из которых back-tier будет обеспечивать прямую связь между frontend и backend, в то время как другая front-tier будет связывать frontend с внешним миром. В файле конфигурации это можно записать так:

services:
  frontend:
    image: awesome/webapp

    <...>

    networks:
      - front-tier
      - back-tier

  <...>

  backend:
    image: awesome/database

    <...>

    networks:
      - back-tier

<...>

networks:
  front-tier:
    external: true
    name: host
  back-tier:

Volumes 🔗

С помощью Docker Compose вы можете использовать тома Docker Volume для хранения данных. Например, нужно обеспечить работу базы данных, расположенную в отдельной папке в томе. Проще всего это сделать следующим образом:

services:

  <...>

  backend:
    image: awesome/database

    <...>

    volumes:
      - db-data:/etc/data

volumes:
  db-data:

Подробная спецификация элементов описана в отдельном репозитории.

В работе 🔗

🛠 Вы можете поискать подходящие образы на GitHub. Есть, например, целый каталог наиболее удачных: Awesome Compose.

Использование терминальных команд 🔗

На этапе сборки образа можно передать аргументы командной строки, а также выполнить произвольные команды по окончании сборки. Иногда это бывает крайне полезно. Например, вы хотите после сборки образа сделать паузу, в течение которой можно будет подождать какой-то процесс или посмотреть вывод в терминал результатов работы программы. В этом случае можно использовать элемент command:

version: "3.9"
services:
  phys-website:
    env_file: ./.env
    command: /bin/sh -c "while sleep 1000; do :; done"
    build:
      context: ./
      args:
        USER_NAME: ${NAME}
        USER_EMAIL: ${EMAIL}

В примере также описываются аргументы командной строки, которые будут использоваться в Dockerfile и могут передаваться на этапе сборки образа командой:

docker-compose build --build-arg NAME="John Doe" --build-arg EMAIL=john@light.org

Приложение на стеке MERN (MongoDB, Express, React.js, Node.js) 🔗

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

Обычно структура папок MERN-проекта выглядит следующим образом:

.
├── backend
│   ├── Dockerfile
│   ...
├── compose.yaml
├── frontend
│   ├── ...
│   └── Dockerfile
└── README.md

Файл конфигурации compose.yaml можно сделать так:

services:
  frontend:
    build: frontend
    ports:
      - 3000:3000
    stdin_open: true
    volumes:
      - ./frontend:/usr/src/app
      - /usr/src/app/node_modules
    container_name: frontend
    restart: always
    networks:
      - react-express
    depends_on:
      - backend

  backend:
    container_name: backend
    restart: always
    build: backend
    volumes:
      - ./backend:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      - mongo
    networks:
      - express-mongo
      - react-express

  mongo:
    container_name: mongo
    restart: always
    image: mongo:4.2.0
    volumes:
      - ./data:/data/db
    networks:
      - express-mongo

networks:
  react-express:
  express-mongo: