Несколько малоизвестных возможностей docker-compose

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


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


    Использование нескольких docker-compose.yml файлов


    Бывают сложные конфигурации, где есть некий базовый пласт контейнеров, который, скажем, нужен всегда. И обычно происходит так, что берем у соседней команды\другого проекта\интернета и допиливаем под свои нужды. Но если команд несколько, то можно базовую часть вынести в общий, внутренний репозиторий. И получаем идентичную базовую часть у большинства проектов, которая к тому же версионируется.


    Опишем пример базового docker-compose-base.yml.


    Предположим, это настроенный образ nginx с сертификатами, тюнингом, и скажем метриками. И exporter для prometheus:


    version: '2'
    services:
      nginx:
        image: nginx
      nginx-exporter:
        image: nginx/nginx-prometheus-exporter

    Теперь опишем пример нашего приложения docker-compose-app.yml:


    version: '2'
    services:
      backend:
        image: internal.local/super-app:0.1.2

    Для запуска нужна привычная нам команда с одним отличием. Указывать будем 2 docker-compose файла :


    docker-compose up -d -f docker-compose-base.yml -f docker-compose-app.yml

    И вуаля, мы получаем набор сервисов, как если бы они были описаны в едином docker-compose файле!


    Так же есть второй вариант использования нескольких файлов, через использование директивы extends.


    docker-compose-base.yml:


    version: '2'
    services:
      nginx:
        image: nginx
      nginx-exporter:
        image: nginx/nginx-prometheus-exporter
    

    docker-compose-app.yml:


    version: '2'
    services:
      backend:
        image: internal.local/super-app:0.1.2
      ### Добавляем секцию с веб сервером
      web:
        extends:
          # В какой файл смотрим (относительный или полный путь)
          file: docker-compose-base.yml
          # Какой сервис берем оттуда к нам
          service: nginx
      web-exporter:
        extends:
          file: docker-compose-base.yml
          service: nginx-exporter

    Дополнение от iSlava:
    Можно описать все compose файлы в environment переменных, и использовать docker-compose up -d без указания файлов вручную:


    COMPOSE_PATH_SEPARATOR=:
    COMPOSE_FILE=docker-compose-base.yml:docker-compose-app.yml

    Какой вариант выбрать — выбирать вам. Все индивидуально, я лишь хотел показать варианты =)


    Наследование в docker-compose


    Следующий пример требует версию docker-compose >= 2.4
    Тоже довольно интересная особенность, причем действительно мало где упоминается.
    Этот функционал позволяет нам описывать несколько однотипных сервисов в docker-compose файле, при этом не дублируя их описание, а именно наследуя.
    Например у нас есть такой файл:


    version: '2.4'
    services:
      backend:
        image: internal.local/super-app:0.1.2
        ports:
          - 8080:8080
          - 9090:9090
        volumes:
          - ./conf/some.conf:/etc/app/some.conf:ro

    И появилась необходимость поднимать несколько контейнеров, но с некоторыми различиями, можем конечно "накопипастить" и поменять, а можем сделать так:


    version: '2.4'
    services:
      backend:
       &base-app #все что под данным указателем будет доступно по его имени
        image: internal.local/super-app:0.1.2
        ports:
          - 8080:8080
          - 9090:9090
        volumes:
          - ./conf/some.conf:/etc/app/some.conf:ro
    
      backend-2:
      <<: *base-app #наследуемся
      ports: # переопределяем опубликованные порты
         - 8081:8080

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


    version: '2.4'
    services:
    
    x-backend: #Секции начинающиеся с "x-" будут игнорироваться, но их можно переиспользовать.
      &base-app
      image: internal.local/super-app:0.1.2
      ports:
        - 8080:8080
        - 9090:9090
      volumes:
        - ./conf/some.conf:/etc/app/some.conf:ro
    
      backend:
       <<: *base-app #наследуемся
    
      backend-2:
      <<: *base-app #наследуемся
      ports: # переопределяем опубликованные порты
         - 8081:8080

    Ограничения по ресурсам


    Начиная с версии 2.2 можно использовать ограничения по ресурсам для контейнеров, на самом деле с версии 2.1, но там еще не все завезли =)
    Есть нюанс! В версии 3 эти возможности убрали! Там уже упор на docker swarm.


    Самый простой пример ограничения ресурсов по CPU, MEM:


    version: '2.2'
    services:
      backend:
        cpus: 1.5 #Позволяем использовать полтора ядра.
        cpuset: '0,3' #Использовать первое и четвертое ядро системы.
        mem_limit: 1gb #Позволяем использовать 1Гб памяти
        memswap_limit: 2gb #Ограничиваем SWAP двумя Гб памяти.
        oom_kill_disable: true # В редких случаях, надо гарантировать что OOM Killer не убьет наше приложение в случае нехватки памяти, вот так можем запретить ему убивать контейнер.
    
        image: internal.local/super-app:0.1.2
        ports:
          - 8080:8080
          - 9090:9090
        volumes:
          - ./conf/some.conf:/etc/app/some.conf:ro

    Упаковка образов в архив


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


    #!/bin/bash
    
    dc=${1}
    
    if [ ! -z ${dc} ] && [ -f ${dc} ]; then
      echo "Saving docker images from file ${dc}..."
      images=`grep image: ${dc} | awk '{print $2}'`
      docker save ${images} | gzip > docker-images.gz
      echo "Success!"
    else
      echo "ERROR! You must set path to docker-compose.yml as argument!"
    fi

    Сохраняем в файл скажем docker-compose-images-save.sh
    Даем права на исполнение:
    chmod +x docker-compose-images-save.sh
    Запускаем, и в качестве аргумента передаем путь до docker-compose файла:
    ./docker-compose-images-save.sh /home/some_user/docker-compose-app.yml
    На выходе получим в папке откуда вызвали скрипт архив с образами — docker-images.gz
    Любым доступным образом отправляем на удаленный сервер.
    Теперь на удаленном сервере достаточно выполнить:
    gzip -cd docker-images.gz | docker load
    Все образы загрузятся в локальный реестр, после чего можно тут смело запускать
    docker-compose up -d, по скольку все образы есть в локальном реестре в интернет уже докер не полезет.


    Пробрасываем IPv6


    В определенных задачах ipv6 бывает крайне полезен, взять хотя бы нюанс, что Роскомнадзор пропускает по ipv6 без проблем весь трафик, и тот же телеграм бот работает без проблем.
    Я рассмотрю ситуацию, когда ipv6 нет на вашей машине, будь то виртуалка, или сервер в интернете.
    Неоходимо убедиться, что ipv6 на уровне системы включен:


        sysctl net.ipv6.conf.all.disable_ipv6

    Значение должно быть равно 0, если не так, изменяем:


        sysctl -w net.ipv6.conf.all.disable_ipv6=0

    Устанавливаем miredo (Это сервис с встроенным впн до сервера, который выдаст нам публичный ipv6)


        apt-get install miredo -y

    Проверяем, что сервис запушен:


        systemctl status miredo

    Проверяем, что мы получили ipv6 адрес:


        ifconfig teredo

    Прописываем в /etc/docker/daemon.json


        {
            "ipv6": true,
            "fixed-cidr-v6": "2001:db8:1::/64"
        }

    Рестартуем докер:


        systemctl restart docker

    Ну и осталось включить NAT для ipv6, чтобы внутренние адреса нашего контейнера смогли выходить в внешний мир через наш teredo интерфейс:


        ip6tables -t nat -A POSTROUTING -o teredo -j MASQUERADE

    Поднимаем docker контейнер нужный нам, и он может выходить в свет через ipv6 адрес.


    Приведенный пример с sysctl и iptables будет работать до перезагрузки, если необходимо сделать на постоянной основе, то следует посмотреть инструкции по вашему дистрибутиву, бывают различия.

    Надеюсь кому-то предоставленная информация здесь будет полезна.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 22

      +1
      Нормас, некоторые советы просто супер, особенно касательно наследования в compose
        0
        Как раз столкнулся с этим «наследованием» портов. Насколько я понял, compose не перезаписывает, а конкатенирует списки. Поправьте, если не прав.
          +1
          Если вы имеете ввиду, что при переопределении вместо перезаписи используется конкатенация, то насколько я знаю — нет. В противном случае при поднятии контейнеров сыпались бы ошибки, из-за попытки второго контейнера к примеру занять порт, который уже занял первый.
            0
            Я предполагаю если внешний порт совпадает с уже определенным, то перезаписывает эту часть. Если же добавляется новый порт, то конкатенируется. То есть нельзя отбросить старые записи и добавить совсем новые
              +1
              Я перед публикацией статьи проверял данный момент. В случае переопределения секции с портами происходит перезапись.
        +4
        mem_limit: 1000000000 #Позволяем использовать 1Гб памяти
        memswap_limit: 2000000000 #Ограничиваем SWAP двумя Гб памяти.


        Можно задать проще:
        mem_limit: 1gb
        memswap_limit: 2gb

        Из официальной доки:

        The supported units are b, k, m and g, and their alternative notation kb, mb and gb. Decimal values are not supported at this time.

          +2
          Да, действительно, обновил статью, спасибо!
          +2
          На работе очень активно используем docker-compose -f, а вот наследованием пока не пользовались. Обязательно пишите еще по теме!
            +1
            Спасибо на добром слове, постараюсь!
            +4
            Малоизвестная возможность docker-compose которая малоизвестна даже авторам таких статей:
            Вместо того чтобы набирать портянку в стиле

            docker-compose up -d -f docker-compose-base.yml -f docker-compose-app.yml ... 


            Можно один раз создать .env файл с содержимым где перечислить все compose файлы

            COMPOSE_PATH_SEPARATOR=:
            COMPOSE_FILE=docker-compose.shared.admin.yml:docker-compose.shared.base-images.yml:docker-compose.shared.depends.yml:docker-compose.shared.env.yml:docker-compose.dev.build.yml:docker-compose.dev.command.yml:docker-compose.dev.env.yml:docker-compose.dev.labels.yml:docker-compose.dev.networks.yml:docker-compose.dev.ports.yml:docker-compose.dev.volumes.yml


            И поднимать всё это классическим docker-compose up -d

            Документация: docs.docker.com/compose/reference/envvars/#compose_file
            Пример использования: GitHub
              0
              А если в разных случаях нужно запускать разное сочетание файлов? Не править же каждый раз .env файл. Хотя, возможность и в самом деле занятная.
                0

                .env подцепляется только с текущего каталога, так что можно закостылить структуру каталогов по окружения, переходить в них и из них запускать докер-компоуз с относительным путем к ЯМЛ файлу. Можно? Да. Удобно? Вряд ли.


                Ну, либо оборачивать запуск докер-компоуза в баш скрипт. Но тогда возникает вопрос — а почему тогда не обойтись вообще без него? Т.к. по сути докер компоуз это враппер вокруг команд управления докером (docket build, docker run, docker volume/network create etc.). Больше врапперов богу врапперов ?

                  0
                  ЕМНИП docker-compose также создает сеть, в которой контейнеры из docker-compose.yml могут общаться друг с другом, используя имена из docker-compose.yml. Мы это используем для запуска тестов — в отдельных контейнерах сидят PHP/Apache, MySQL, Selenium (headless, если не нужна отладка), тот же MailCatcher, итп.
                    0

                    Делается руками, прекрасно.
                    Это не является какой-то особенной фичей докер-компоуза.
                    Что хуже — я сталкивался с ситуацией, когда докер-компоуз игнорирует глобальные настройки докер-демона. Например, это особенно неприятно, когда корп. сеть не в 10-й подсети, а в 172-й.
                    Типа такого https://github.com/docker/compose/issues/5204

                      0
                      Хм, я не сталкивался с таким, но мне не приходилось менять подсетки. Интересно, что по тому линку есть вот это:

                      «Don't believe this is a compose issue. Compose just uses docker-py which calls the daemon under the hood, Compose doesn't interact with that file at all.»

                      Т.е. вопросы к демону, а не к компоузу.
                        0

                        Не хочу спорить. Эту фразу можно понять по-разному. Но факт в том, что docker network create создаёт сеть правильно, а docker-compose при тех же настройках — нет. Вывод — что-то сломано, возможно по дороге.

                0
                Спасибо, я дополнил статью!
                +1
                Следующий пример требует версию docker-compose >= 2.4

                Выглядит немного по-издевательски, учитывая, что 3.1 вышел раньше 2.4
                Фактически — это две разные линейки форматов, каждая со своими "фичами"


                Касательно параметра -f — мы ими активно пользуемся но в сочетании с /dev/stdin, чио позволяет генерировать докер-компоуз файл по шаблону той же Jinja, например

                  +3
                  Наследование в docker-compose — это печаль и боль. Сам формат yaml с первой версии поддерживает подстановки/алиасы. Но разработчики почему-то реализовали какой-то свой yaml :-(
                    0

                    Я бы сказал, что проблема не в наследовании как таковом, а в его реализации. Есть 5+ разных механизмов, которые нормально не работают (конкатенация ямлов через -f, extends, docker-compose.override.yml, .env vs env_file, может быть что-то еще, что я забыл).

                    0

                    По IPv6: teredo, серьезно? Я тоже не рад что ipv6 не популизирован в массы. Получить IPv6 в Киеве можно только в ЦОД или через BGP, что для некоторых большой оверхед. Но если и брать вирт адреса IPv6 то хотя бы у лидеров, а не пойми откуда, разверните tunnelbroker.net клиент на шлюзе, не сравнимое преймущество с teredo. Единственное: если вам нужен outgoing smtp server-server с 25 порта: сразу проходите сертификацию в ЛК, без нее он залочен. Заодно получите футболку по почте бесплатно :)

                      0
                      Только уже ради футболки надо попробовать!
                      А по делу — спасибо за дельный комментарий, обязательно попробую!

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

                    Самое читаемое