Одна из проблем, с которыми приходится столкиваться, занимаясь настройкой окружения для разработчиков, с использованием Docker и Docker-compose, это вопрос о том, как сводить вместе несколько различных проектов. При условии, что все проекты, конечно же, имеют docker-compose.yml файл.



Причин, по которым становится необходимо делать это, может быть несколько:
  • Разработка низко связанных компонентов огромной системы. Где каждый проект, по сути, может являться отдельным самостоятельным приложением
  • Подключение отдельных компонентов для тестирования. Вынесение mock-сервисов и тестов в отдельные контейнеры со своей логикой линковки и взаимодействия
  • Внешнии, по отношению к проекту, системы, которые тем не менее 'живут' в docker среде


Содержание




Проблема


Собственно, комбинирование N файлов в один и запуск их как цельного приложения, и стало основной проблемой. Что же стало второстепенными проблемами, которые не позволили просто на просто объединить один за одним существующие файлы?

  • Конфликты имен контейнеров c обновлением всего дерева конфигурации
  • Конфликты портов пробрасываемых на хост-машину
  • Переопределение свойств сервиса
  • Разрешение относительных путей
  • Удаление лишних или дублирующихся сервисов


Решение


Таким образом потратив на ум пришло потратить пару выходных и написать простой python препроцессор, который и будет делать всю грязную рутинную работу по объединению docker-compose файлов.
Как следствие, получился небольшой (на 600 строчек кода) скрипт в целом и полностью решающий проблемы возникшие некогда передо мной.

Однако, как бонус, по мимо обозначенных выше проблем, скрипт помог решить еще одну интересную задачу. А именно после небольших умозаключений, удалось параллельно запускать несколько экземпляров приложения с одним docker-compose файлом.

Примеры использования


Несколько шагов которые позволят быстро использовать данное решение

Установка


Необходимо скачать бинарный файл, который является упакованным python модулем
Установка
sudo wget https://github.com/paunin/docker-compose-mixer/blob/master/dist/dc-mixer?raw=true -O /usr/local/bin/dc-mixer
sudo chmod +x /usr/local/bin/dc-mixer


Конфигурирование


Содержание конфигурационного docker-compose-mixer.yml файла по своей сути является небольшим конфигом, который описывает как именно два или более проектов будут стартовать вместе.

Скажем у нас есть 2 проекта project A и project B c двумя docker-compose.yml файлами.
Проекты, являясь разными версиями системы, представляют собой приложение с набором сервисов: java-application, redis, rabbitmq, mail, проект project A(новая версия) так же содержит mysql сервис.
project A
$ cat ../proj_a/docker-compose.yml
sources:
  build: images/sources
  volumes:
    - .:/var/application.host

application:
  build: images/java
  dockerfile: application.yml
  dns:
    - 8.8.8.8
    - 9.9.9.9
  hostname: application.host
  working_dir: /var/application.host
  cgroup_parent: m-executor-abcd
  links:
    - redis:redis
    - rabbitmq
    - mail
    - mysql:db
  volumes_from:
    - sources
  command: "/start.sh"
  env_file:
    - ./env_files/application.env
    - ./env_files/rabbit.env
  environment:
    - DB_DRIVER: mysql
    - DB_PORT: 3306
  ports:
    - "80:80"     #http
    - "1098:1098" #jmx
  external_links:
    - redis_1
    - project_db_1:mysql
  extra_hosts:
    - "somehost:162.242.195.82"
  labels:
    com.example.description: "Accounting webapp"
    com.example.department: "Finance"

redis:
  labels:
    - "com.example.description=Accounting webapp"
    - "com.example.department=Finance"
  extends:
    file: ../redis.yml
    service: redisbase
  expose:
  ports:
    - "6379:6379"
    - "127.0.0.1:6370:6370"
  log_driver: "syslog"
  log_opt:
    syslog-address: "tcp://192.168.0.42:123"
  net: "bridge"

rabbitmq:
  build: images/rabbitmq
  ports:
    - "15672:15672"
  volumes_from:
    - sources
  command: /start.sh
  env_file:
    - ./env_files/rabbit.env

mail:
  build: images/mail
  hostname: mail
  domainname: application.host
  expose:
    - 25
    - 143
  ports:
    - "25:25"
    - "143:143"
  volumes:
    - ./images/mail/spamassassin:/tmp/spamassassin/
    - ./images/mail/postfix:/tmp/postfix/
    - ./images/mail/mail:/tmp/mail/

mysql:
  build: images/mysql
  ports:
    - 3602

project B
$ cat ./proj_b/docker-compose.yml
sources:
  build: images/sources
  volumes:
    - .:/var/application.host

application:
  build: images/java
  dockerfile: application.yml
  dns:
    - 8.8.8.8
    - 9.9.9.9
  hostname: application.host
  working_dir: /var/application.host
  cgroup_parent: m-executor-abcd
  links:
    - redis:redis
    - rabbitmq
    - mail
  volumes_from:
    - sources
  command: "/start.sh"
  env_file:
    - ./env_files/application.env
    - ./env_files/rabbit.env
  environment:
    - VARIABLE: value
  ports:
    - "80:80"     #http
    - "1098:1098" #jmx
  external_links:
    - redis_1
    - project_db_1:mysql
  extra_hosts:
    - "somehost:162.242.195.82"
  labels:
    com.example.description: "Accounting webapp"
    com.example.department: "Finance"

redis:
  labels:
    - "com.example.description=Accounting webapp"
    - "com.example.department=Finance"
  extends:
    file: ../redis.yml
    service: redisbase
  expose:
  ports:
    - "6379:6379"
    - "127.0.0.1:6370:6373"
  log_driver: "syslog"
  log_opt:
    syslog-address: "tcp://192.168.0.42:123"
  net: "bridge"

rabbitmq:
  build: images/rabbitmq
  ports:
    - "15672:15672"
  volumes_from:
    - sources
  command: /start.sh
  env_file:
    - ./env_files/rabbit.env

mail:
  build: images/mail
  hostname: mail
  domainname: application.host
  expose:
    - 25
    - 143
  ports:
    - "25:25"
    - "143:143"
  volumes:
    - ./images/mail/spamassassin:/tmp/spamassassin/
    - ./images/mail/postfix:/tmp/postfix/
    - ./images/mail/mail:/tmp/mail/


Базовая задача — объеденить два окружения в одно с использованием лишь одного rabbitmq сервиса. К тому же необходимо создать новый сервис pgsql и подключить его к первому проекту (заменив mysql)
Конфигурация для объединения файлов должна лежать в файле ./docker-compose-mixer.yml (опция -i, --input-file может определить другое имя для файла)
docker-compose-mixer.yml
$ cat ./docker-compose-mixer.yml
includes:
  proja: ../proj_a/docker-compose.yml
  projb: ./proj_b/docker-compose.yml
ignores:
  - projbrabbitmq
overrides:
  projbapplication:
    links:
      - projaredis:redis
      - projarabbitmq:rabbitmq
      - projamail:mail
  projaapplication:
    links:
      - projaredis:redis
      - projarabbitmq:rabbitmq
      - projamail:mail
      - pgsql:db
    environment:
      - DB_DRIVER: pgsql
      - DB_PORT: 5432
master_services:
  pgsql:
    image: pgsql:latest
    expose: 
      - 5432
    ports:
      5432:5432


Запуск


Команда `dc-mixer -v` запускает препроцессор в вербальном режиме и сохраняет результат в файл. Опции запуска помогают управлять поведением.
Опции запуска
$ dc-mixer --help
Compile docker-compose from several docker-compose.yml files

Usage:
  dc-mixer [options]

Options:
  -h, --help                Print help information
  -i, --input-file          Input file (default `docker-compose-mixer.yml` in current directory)
  -o, --output-file         Output file (default `docker-compose.yml` in current directory)
  -h, --help                Print help information
  -v, --verbose             Enable verbose mode

For more information read documentation: https://github.com/paunin/docker-compose-mixer


Результат


По умолчанию, результат объединения файлов будет сохранен в файл ./docker-compose.yml. (опция -o, --output-file может определить другое имя для файла)
Результат работы скрипта
$ dc-mixer -v -o docker-compose.yml 
DEBUG:root:Start compiling compose file...
DEBUG:root:Input file: /Users/paunin/Sites/dc-mixer.local/examples/example2/proj/docker-compose-mixer.yml; output file: docker-compose.yml
DEBUG:root:Mixer config is below:
        {'overrides': {'projbapplication': {'links': ['projaredis:redis', 'projarabbitmq:rabbitmq', 'projamail:mail']}, 'projaapplication': {'environment': {'DB_DRIVER': 'pgsql', 'DB_PORT': 5432}, 'links': ['projaredis:redis', 'projarabbitmq:rabbitmq', 'projamail:mail', 'pgsql:db']}}, 'master_services': {'pgsql': {'image': 'pgsql:latest', 'expose': [5432], 'ports': '5432:5432'}}, 'ignores': ['projbrabbitmq'], 'includes': {'projb': './proj_b/docker-compose.yml', 'proja': '../proj_a/docker-compose.yml'}}
DEBUG:root:Creating scope for file: /Users/paunin/Sites/dc-mixer.local/examples/example2/proj/proj_b/docker-compose.yml and prefix: projb
DEBUG:root:Creating scope for file: /Users/paunin/Sites/dc-mixer.local/examples/example2/proj_a/docker-compose.yml and prefix: proja
DEBUG:root:Resolving services names
DEBUG:root:Resolving services paths with
DEBUG:root:Resolving services ports
DEBUG:root:Redefined ports:
        {'projaapplication': {80: 81, 1098: 1099}, 'projaredis': {6370: 6371, 6379: 6380}, 'projamail': {25: 26, 143: 144}}
DEBUG:root:Result scope is:
        {'projasources': {'build': '../proj_a/images/sources', 'volumes': ['./../proj_a:/var/application.host']}, 'projaredis': {'log_opt': {'syslog-address': 'tcp://192.168.0.42:123'}, 'log_driver': 'syslog', 'expose': None, 'labels': ['com.example.description=Accounting webapp', 'com.example.department=Finance'], 'extends': {'service': 'redisbase', 'file': '../redis.yml'}, 'net': 'bridge', 'ports': ['6380:6379', '127.0.0.1:6371:6370']}, 'projbmail': {'domainname': 'application.host', 'expose': [25, 143], 'hostname': 'mail', 'build': 'proj_b/images/mail', 'volumes': ['./proj_b/images/mail/spamassassin:/tmp/spamassassin/', './proj_b/images/mail/postfix:/tmp/postfix/', './proj_b/images/mail/mail:/tmp/mail/'], 'ports': ['25:25', '143:143']}, 'projamail': {'domainname': 'application.host', 'expose': [25, 143], 'hostname': 'mail', 'build': '../proj_a/images/mail', 'volumes': ['./../proj_a/images/mail/spamassassin:/tmp/spamassassin/', './../proj_a/images/mail/postfix:/tmp/postfix/', './../proj_a/images/mail/mail:/tmp/mail/'], 'ports': ['26:25', '144:143']}, 'pgsql': {'image': 'pgsql:latest', 'expose': [5432], 'ports': '5432:5432'}, 'projbapplication': {'hostname': 'application.host', 'links': ['projaredis:redis', 'projarabbitmq:rabbitmq', 'projamail:mail'], 'cgroup_parent': 'm-executor-abcd', 'labels': {'com.example.description': 'Accounting webapp', 'com.example.department': 'Finance'}, 'extra_hosts': ['somehost:162.242.195.82'], 'environment': [{'VARIABLE': 'value'}], 'working_dir': '/var/application.host', 'command': '/start.sh', 'build': 'proj_b/images/java', 'dns': ['8.8.8.8', '9.9.9.9'], 'volumes_from': ['projbsources'], 'env_file': ['proj_b/env_files/application.env', 'proj_b/env_files/rabbit.env'], 'dockerfile': 'application.yml', 'ports': ['80:80', '1098:1098'], 'external_links': ['redis_1', 'project_db_1:mysql']}, 'projaapplication': {'hostname': 'application.host', 'links': ['projaredis:redis', 'projarabbitmq:rabbitmq', 'projamail:mail', 'pgsql:db'], 'cgroup_parent': 'm-executor-abcd', 'labels': {'com.example.description': 'Accounting webapp', 'com.example.department': 'Finance'}, 'extra_hosts': ['somehost:162.242.195.82'], 'environment': {'DB_DRIVER': 'pgsql', 'DB_PORT': 5432}, 'working_dir': '/var/application.host', 'command': '/start.sh', 'build': '../proj_a/images/java', 'dns': ['8.8.8.8', '9.9.9.9'], 'volumes_from': ['projasources'], 'env_file': ['../proj_a/env_files/application.env', '../proj_a/env_files/rabbit.env'], 'dockerfile': 'application.yml', 'ports': ['81:80', '1099:1098'], 'external_links': ['redis_1', 'project_db_1:mysql']}, 'projamysql': {'build': '../proj_a/images/mysql', 'ports': []}, 'projarabbitmq': {'volumes_from': ['projasources'], 'env_file': ['../proj_a/env_files/rabbit.env'], 'command': '/start.sh', 'build': '../proj_a/images/rabbitmq', 'ports': ['15672:15672']}, 'projbredis': {'log_opt': {'syslog-address': 'tcp://192.168.0.42:123'}, 'log_driver': 'syslog', 'expose': None, 'labels': ['com.example.description=Accounting webapp', 'com.example.department=Finance'], 'extends': {'service': 'redisbase', 'file': 'redis.yml'}, 'net': 'bridge', 'ports': ['6379:6379', '127.0.0.1:6370:6373']}, 'projbsources': {'build': 'proj_b/images/sources', 'volumes': ['./proj_b:/var/application.host']}}
DEBUG:root:Save result scope in the file "docker-compose.yml"


Результирующий docker-compose.yml
pgsql: 
  expose: 
    - 5432
  image: "pgsql:latest"
  ports: "5432:5432"
projaapplication: 
  build: ../proj_a/images/java
  cgroup_parent: m-executor-abcd
  command: /start.sh
  dns: 
    - "8.8.8.8"
    - "9.9.9.9"
  dockerfile: application.yml
  env_file: 
    - ../proj_a/env_files/application.env
    - ../proj_a/env_files/rabbit.env
  environment: 
    DB_DRIVER: pgsql
    DB_PORT: 5432
  external_links: 
    - redis_1
    - "project_db_1:mysql"
  extra_hosts: 
    - "somehost:162.242.195.82"
  hostname: application.host
  labels: 
    com.example.department: Finance
    com.example.description: "Accounting webapp"
  links: 
    - "projaredis:redis"
    - "projarabbitmq:rabbitmq"
    - "projamail:mail"
    - "pgsql:db"
  ports: 
    - "81:80"
    - "1099:1098"
  volumes_from: 
    - projasources
  working_dir: /var/application.host
projamail: 
  build: ../proj_a/images/mail
  domainname: application.host
  expose: 
    - 25
    - 143
  hostname: mail
  ports: 
    - "26:25"
    - "144:143"
  volumes: 
    - "./../proj_a/images/mail/spamassassin:/tmp/spamassassin/"
    - "./../proj_a/images/mail/postfix:/tmp/postfix/"
    - "./../proj_a/images/mail/mail:/tmp/mail/"
projamysql: 
  build: ../proj_a/images/mysql
  ports: []
projarabbitmq: 
  build: ../proj_a/images/rabbitmq
  command: /start.sh
  env_file: 
    - ../proj_a/env_files/rabbit.env
  ports: 
    - "15672:15672"
  volumes_from: 
    - projasources
projaredis: 
  expose: ~
  extends: 
    file: ../redis.yml
    service: redisbase
  labels: 
    - "com.example.description=Accounting webapp"
    - com.example.department=Finance
  log_driver: syslog
  log_opt: 
    syslog-address: "tcp://192.168.0.42:123"
  net: bridge
  ports: 
    - "6380:6379"
    - "127.0.0.1:6371:6370"
projasources: 
  build: ../proj_a/images/sources
  volumes: 
    - "./../proj_a:/var/application.host"
projbapplication: 
  build: proj_b/images/java
  cgroup_parent: m-executor-abcd
  command: /start.sh
  dns: 
    - "8.8.8.8"
    - "9.9.9.9"
  dockerfile: application.yml
  env_file: 
    - proj_b/env_files/application.env
    - proj_b/env_files/rabbit.env
  environment: 
    - 
      VARIABLE: value
  external_links: 
    - redis_1
    - "project_db_1:mysql"
  extra_hosts: 
    - "somehost:162.242.195.82"
  hostname: application.host
  labels: 
    com.example.department: Finance
    com.example.description: "Accounting webapp"
  links: 
    - "projaredis:redis"
    - "projarabbitmq:rabbitmq"
    - "projamail:mail"
  ports: 
    - "80:80"
    - "1098:1098"
  volumes_from: 
    - projbsources
  working_dir: /var/application.host
projbmail: 
  build: proj_b/images/mail
  domainname: application.host
  expose: 
    - 25
    - 143
  hostname: mail
  ports: 
    - "25:25"
    - "143:143"
  volumes: 
    - "./proj_b/images/mail/spamassassin:/tmp/spamassassin/"
    - "./proj_b/images/mail/postfix:/tmp/postfix/"
    - "./proj_b/images/mail/mail:/tmp/mail/"
projbredis: 
  expose: ~
  extends: 
    file: redis.yml
    service: redisbase
  labels: 
    - "com.example.description=Accounting webapp"
    - com.example.department=Finance
  log_driver: syslog
  log_opt: 
    syslog-address: "tcp://192.168.0.42:123"
  net: bridge
  ports: 
    - "6379:6379"
    - "127.0.0.1:6370:6373"
projbsources: 
  build: proj_b/images/sources
  volumes: 
    - "./proj_b:/var/application.host"


Хочется обратить внимание на раздел вывода дебагера `DEBUG:root:Redefined ports`, который выдает информацию об автоматически измененных портах.

Исходники примера можно найти тут

Ресурсы


Комментарии (2)


  1. kvaps
    14.01.2016 11:53
    +1

    Вам наверное будет интересно узнать о существовании такого инструмента как Crane.
    Правда объединять конфиги он не умеет, но зато в нем можно полностью описать весь ваш проект, а так же:

    • разделить его на группы
    • на одтельном уровне конфига описать все ваши сети, волумы, а так же хуки
    • ссылаться к ним при описании ваших групп и контейнеров
    • проставлять зависимости


    1. ZmeeeD
      15.01.2016 14:04

      Интересный проект но немножко о другом. Crane скорее `другой` docker-compose нежели чем mixer.