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

Как собирать целые системы на основе докер контейнеров.

Время чтения: 11 мин

Кратко

Скопировано

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
          docker-compose up

        
        
          
        
      

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

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

        
          
          docker-compose down
          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
          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-tiernetworks:  front-tier:    external: true    name: host  back-tier:
          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/datavolumes:  db-data:
          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}
          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
          docker-compose build --build-arg NAME="John Doe" --build-arg EMAIL=john@light.org

        
        
          
        
      

Приложение на стеке MERN

Скопировано

MERN — это аббревиатура от MongoDB, Express, React, 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-mongonetworks:  react-express:  express-mongo:
          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:

        
        
          
        
      

На собеседовании

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Редакция
Игорь Коровченко  отвечает

Скопировано

Обычно жизненный цикл контейнера состоит из следующей последовательности состояний:

  1. Создание контейнера
  2. Работа контейнера
  3. Приостановка контейнера
  4. Возобновление работы контейнера
  5. Запуск контейнера
  6. Остановка контейнера
  7. Перезапуск контейнера
  8. Принудительная остановка контейнера
  9. Удаление контейнера

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

Схема жизненного цикла контейнера Docker с указанием команд управления

На этой диаграмме показаны не только состояния и пути перехода из одного состояния в другое, но и команды, которые позволяют пользователю их менять.