Docker compose и объединение проектов с помощью mixer-a

    Одна из проблем, с которыми приходится столкиваться, занимаясь настройкой окружения для разработчиков, с использованием 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`, который выдает информацию об автоматически измененных портах.

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

    Ресурсы


    • +7
    • 17,9k
    • 6
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 6
    • +1
      Вам наверное будет интересно узнать о существовании такого инструмента как Crane.
      Правда объединять конфиги он не умеет, но зато в нем можно полностью описать весь ваш проект, а так же:
      • разделить его на группы
      • на одтельном уровне конфига описать все ваши сети, волумы, а так же хуки
      • ссылаться к ним при описании ваших групп и контейнеров
      • проставлять зависимости
      • 0
        Интересный проект но немножко о другом. Crane скорее `другой` docker-compose нежели чем mixer.
      • 0
        как-то не особо понял в чем была проблема

        если у Вас один проект с файлом docker-compose.yml, то в нем, видимо, Вы прописываете и конфигурируете подсервисы ( database server , cache-server , proxy-server ), которые будут слинкованы с application-контейнером, который также сконфигурирован в этом же файле docker-compose.yml

        если у Вас несколько проектов, то они, по идее, не должны пересекаться (разве что у Вас один и тот же контейнер используется разными проектами, к примеру, database-server ), тем более на стадии разработки.

        docker-compose выдает префиксы для имен контейнеров и у Вас не должны пересекаться названия контейнеров. к примеру, у Вас есть проект project_A и он использует db , то контейнер будет иметь имя, соответственно, project_A_db_1 . и так далее

        лично я еще сталкивался с ситуацией, когда у меня возникала подобная проболема с несколькими проектами, которые используют по несколько контейнеров. в разработке я использую как минимум по два контейнера: database + application

        не могбы бы Вы описать практическую ситуацию из жизни на стадии разработки чтобы представить когда нужно будет использовать этот подход?

        спасибо
        • 0
          Привет Дмитрий!

          У нас аналогичные задачи возникают. В идеале хотелось бы что-то похожее на такие слои:

          — есть PostgreSQL в кластере. Ее настройкой занимаются администраторы баз данных. Они поддерживают конфигурацию и docker-compose файл для ее запуска.
          — на следующем слое у нас система управления процессами (Taskurotta). Ей нужна база данных для работы. У нее свои владельцы. У них свой docker-compose для разворачивания системы в кластере. При этом база данных для этих людей может быть черным ящиком. Достаточно compose файл БД подгрузить к проекту, например, с помощью git submodules указав требуемую версию и они вместе стартанут. Владельцы этой системы запускают автоматические тесты на данном окружении.
          — на следующем уровне абстракции есть прикладные микросервисы которым нужна Taskurotta которой нужна база данных. Они так-же пишут автоматические тесты и хотят использовать нужной версии систему управления процессами. И как черный ящик, потому как владельцы не они и им достаточно чтобы система была доступна по нужному порту. И групп таких микросервисов много со своими владельцами.
          — на следующем уровне фронтенд разработка с REST сервисами, которым нужны несколько групп микросервисов, которым… которым нужен дом, который построил Джек :) Им уже тем более тяжело знать все нюансы настройки бэкенд систем. Им нужно свои тесты прогнать подключив бэкенд как черный ящик с торчащими на ружу точками взаимодействия.

          На большом проекте в микросервисной архитектуре уже тяжело знать про всё. Хочется разделять всё на подсистемы со своими владельцами. Но при этом иметь возможность легко собрать окружения для выполнения автоматических тестов.
          • +1
            ага, тоесть из-за такой инкапсуляции работы отдельных команд над отдельными компонентами Вам и приходится в результате собирать единый docker-compose.yml файл для финишного тестирования и разварачивания конечной плаформе…

            спасибо Вам

            мне стало яснее где можно это применить
            • 0
              В целом да — мы примерно тоже самое и делаем

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.