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


Docker в 2017


На конференции Dockercon 2016 CEO компании Docker рассказал, что количество приложений, которые запускаются в Docker выросло на 3100% за последние два года. Боле 460 тысяч приложений по всему миру запускаются в Docker. Это невероятно!


Если вы все еще не используете Docker, я бы посоветовал почитать отличную статью об использовании Docker во всем мире. Docker полностью изменил то, как мы пишем приложения и стал неотъемлемой частью для разработчиков и DevOps команд. В этой статье мы полагаем, что вы уже знакомы с Docker и хотим дать вам еще одну серьезную причину продолжать использовать его.


Что не так?


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


Многие проекты просты в начале, но становятся больше со временем. Это приводит к увеличению внешних зависимостей, таких как базы данных, очереди. В связи с ростом популярности микросервисов, многие проекты перестают быть монолитными и разделяются на несколько небольших частей. Любое такое изменение требует внимания всей команды, так как после таких изменений, проект нужно запускать по-другому. Обычно, разработчики, занимающиеся корневыми изменениями, пишут письмо, либо создают вики страничку с описанием шагов, которые нужно сделать, чтобы проект снова запустился на рабочих окружениях. Обычно это работает, но не всегда :) Однажды наша команда попала в ситуацию, когда разработчик с другого континента сделал много изменений в проекте, написал длинное письмо и ушел спать. Я полагаю, Вы знаете, что было дальше. Все верно, он забыл упомянуть несколько важных моментов. В результате, на следующий день часть команды просто не смогла запустить проект и день был потерян.


Как инженеру, мне нравится автоматизировать все вокруг. Я верю, что запуск, тестирование и развертывание всегда должны быть одношаговыми. В этом случае, команда сможет сфокусироваться на важных задачах: разработке и улучшении продукта. Это было сложнее сделать 10 лет назад, но сейчас автоматизировать стало гораздо проще и, как мне кажется, каждая команда должна уделять этому время. Чем раньше — тем лучше.


Быстрый старт с docker-compose


Docker-compose это простой инструмент, который позволяет настроить и запустить несколько контейнеров одной командой. До того, как мы нырнем глубже в docker-compose, нужно немного остановиться на структуре проекта. Мы используем "monorepo". Код каждого сервиса (frontend, api, worker, etc) находится в своей директории и имеет Dockerfile. Пример структуры проекта можно посмотреть здесь.


Вся конфигурация для docker-compose описывается в файле docker-compose.yml, который обычно лежит в корне проекта. Начнем с автоматизации простого Node.JS приложения, которое работает с базой данных MongoDB. Вот так будет выглядеть конфигурационный файл:


version: '2'
services:
  web:
    build:
      context: ./web
      dockerfile: Dockerfile.dev
    volumes:
      - "./web/src:/web/src"
    ports:
      - "8080:8080"
  mongo:
    command: mongod
    image: mongo:3.2.0
    ports:
      - "27100:27017" # map port to none standard port, to avoid conflicts with locally installed mongodb. 
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Чтобы запустить проект, нам понадобиться одна команда:


$ docker-compose up

При первом старте, все контейнеры будут построены или скачаны. Если вы работали с Docker, конфигурационный файл для docker-compose должен быть более-менее понятен, но стоит обратить внимание на несколько деталей:


  1. context: ./web — таким образом указывается путь к докер файлу сервиса внутри нашего репозитория.
  2. dockerfile: Dockerfile.dev — мы используем отдельный Dockerfile.dev для рабочих окружений. Для "production" окружений, мы копируем код в образ Docker, а на рабочих окружениях мы добавляем код как "volume". При использовании "volume" не приходится перезапускать docker-compose каждый раз после изменений в коде.
  3. volumes: - "./web/src:/web/src" — так код добавляется как "volume" в Docker.
  4. Docker-compose автоматически "связывает" контейнеры. Благодаря этому есть возможно обращаться к сервису по имени. Например, из сервиса web вы можете подключится к базе данных MongoDB: mongodb://mongo:27017

Всегда используйте --build


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


Совет: Вы можете обернуть команду запуска проект в простой баш скрипт:


#!/bin/sh
docker-compose up --build "$@"

Это даст вам возможность поменять аргументы или подход для запуска приложения в целом. Для команды это всегда будет просто как: ./bin/start.sh.


Частичный запуск


В этом примере docker-compose.yml некоторые сервисы зависят друг от друга:


  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
    volumes:
      - "./api/src:/app/src"
    ports:
      - "8081:8081"
    depends_on:
      - mongo

В данном случае, api сервису необходима база данных для работы. При запуске docker-compose, вы можете передать название сервиса для того, чтобы запустить только его и все его зависимости: docker-compose up api. Эта команда запустит MongoDB и только после этого запустит api.


В больших проектах всегда есть части, которые нужны лишь время от времени. Различные члены команды могут работать над различными частями приложения. Фронтенд разработчику, который работает над landing сайтом, нет нужды запускать проект целиком. Он может просто запустить только те части, которые ему действительно нужны.


>/dev/null назойливые логи


Часто мы используем инструменты, которые генерируют много логов, тем самым отвлекая нас от полезных логов нашего приложения. Чтобы отключить логи для конкретного сервиса, нужно просто установить logging driver в none.


  mongo:
    command: mongod
    image: mongo:3.2.0
    ports:
      - "27100:27017"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    logging:
      driver: none

Несколько файлов docker-compose


По умолчанию, когда вы запускаете docker-compose up, docker-compose ищет конфигурационный файл docker-compose.yml в текущей директории. В некоторых случаях (поговорим об этом через минуту), вам придется создать несколько таких конфигурационных файлов. Чтобы сделать это, вам достаточно использовать аргумент --file:


docker-compose --file docker-compose.local-tests.yml up

Так почему же вам может понадобиться несколько конфигурационных файлов? Первый вариант использования — это разбиение большого проекта на несколько более мелких. Интересно, что даже если вы запускаете несколько отдельных docker-compose, сервисы все равно смогут общаться друг с другом по имени из docker-compose. Например, вы можете разделить инфраструктурные контэйнеры (базы данных, очереди и т.д.) и контейнеры приложения в отдельные docker-compose файлы.


Запуск тестов


Наши тесты включают в себя различные типы: юнит, интеграционные, UI тестирование, проверку синтаксиса кода. У каждого сервиса свой набор тестов. Интеграционные и UI тесты требуют api и web frontend для их работы.


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


version: '2'
services:
  api-tests:
    image: app_api
    command: npm run test
    volumes:
      - "./api/src:/app/src"
  web-tests:
    image: app_web
    command: npm run test
    volumes:
      - "./web/src:/app/src"

Для запуска тестов необходимо, чтобы основной docker-compose был запущен. Интеграционные тесты используют рабочую версию api сервиса, а UI тесты используют web frontend сервиса. По сути тесты просто используют образы, которые собраны в основном docker-compose. Также возможен запуск тестов только для конкретного сервиса, например:


docker-compose --file docker-compose.local-tests.yml up api-tests

Данная команда запустит только тесты для api сервиса.


Префикс для контейнеров


По умолчанию, все контэйнеры, которые запускаются с помощью docker-compose, используют название текущей директории как префикс. Название этой директории может отличаться в рабочих окружениях у разных разработчиков. Этот префикс (app_) используется, когда мы хотим сослаться на контейнер из основного docker-compose файла. Чтобы зафиксировать этот префикс, нужно создать файл .env рядом с конфигурационными файлами docker-compose в той директории, из которой запускается docker-compose:


COMPOSE_PROJECT_NAME=app

Таким образом, префикс будет одинаковым во всех рабочих окружениях.


Заключение


Docker-compose это очень полезный и гибкий способ автоматизации запуска проектов.


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


  1. Установить Docker и Docker-compose
  2. Склонировать репозиторий
  3. Запустить в терминале ./bin/start.sh

Для того, чтобы Вам было проще понять эту статью, у нас есть пример проекта на Github. Делитесь вашим опытом и задавайте вопросы.


Надеемся, что статья была полезной и поможет сделать Ваш проект лучше :)


Версию на английском, можно почитать здесь.

Поделиться с друзьями
-->

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


  1. samizdam
    23.02.2017 00:26
    +1

    Спасибо за перевод.
    Действительно docker-compose незаменимый инструмент, который очень естественно вписывается в концепции докер и упрощает жизнь.
    Если начинаешь докеризировать приложение, то логично разнести сервисы по отдельным образам / контейнерам. И вот тут композ очень просто решает большинство задач по запуску / конфигурации всего зоопарка.


  1. hlogeon
    23.02.2017 04:10
    +6

    У меня от докера такие странные ощущения. С одной стороны, это инструмент, который призван упростить развертывание приложения, но по факту все оказывается совершенно не так, как пишут в статьях. Проблемы начинаются с разницы окружений(а ведь мы с Вами помним, что докер изначально вроде бы должен был ее нивелировать) windows, mac, linux запускают докер по-разному. Кому-то нужно запускать контейнеры внутри виртуальной машины, кому-то она не нужна. Потом ты сталкиваешься с несовмесимыми с прошлой версией изменениями в самом docker, либо в доп. инструментах вроде docker-compose и вынужден обновлять конфиги, снова пересобирать и тестировать свои контейнеры. Тот же самый docker-compose.yml можно описать используя разные версии синтаксиса этого файла.
    И вот, собрал ты такой счастливый проект, состоящий, скажем, из 10 сервисов. И думаешь: «О! Как хорошо, что я использовал докер, ведь теперь для запуска проекта мне нужно только выполнить `docker-compose up`» Но не тут то было, я бы даже сказал ДАЛЕКО не тут! Чтобы понимать, о чем я говорю — просто пойдите и попробуйте задеплоить любой Hello, World на тот же Amazon ECS.


    1. arzonus
      23.02.2017 10:39
      +1

      windows, mac, linux запускают докер по-разному. Кому-то нужно запускать контейнеры внутри виртуальной машины, кому-то она не нужна.

      Работаю с docker на 3 системах сразу же, дома на маке, на работе windows и linux, и не видел проблем с запуском. Одно из условий запуска docker — это наличие linux ядра. Соответственно на не linux системах, ему требуется виртуальная машина. Однако docker-toolbox настраивает за вас все это и вы не задумываетесь о настройке.

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

      Какие изменение были сделаны, которые стали несовместимы с прошлой версией? Я просто не припомню кардинальные отличия, которые требовали изменения файлов. То что продукт развивается и изменяется — это нормально. Не забывайте, он появился в 2013 году и ему «всего лишь» 4 года.

      Чтобы понимать, о чем я говорю — просто пойдите и попробуйте задеплоить любой Hello, World на тот же Amazon ECS.

      Не скажу за Amazon, но скажу за Azure. Вы можете взять за основу официальный скрипт установки от Docker и установить его. А потом docker run -rm helloworld.
      Расскажите подробнее в чем были сложности :)


      1. myrkoxx
        23.02.2017 17:01

        есть отличие между Amazon EC2 и Amazon ECS. По простому: Amazon EC2 ака твой VPS, ака ваш пример с Azure, где тебе надо будет ставить докер и тд для его запуска. Для Amazon ECS же достаточно указать (в теории) просто откуда взять твой Docker образ и указать условия запуска. Ето 2 разных сервиса.

        hlogeon прав:

        просто пойдите и попробуйте задеплоить любой Hello, World на тот же Amazon ECS

        не так просто как взять установить самому докер на vps и прогнать


        1. Caravus
          25.02.2017 14:06

          Ну вообще какой-то странный аргумент, в целом. Попробуйте запустить докер-контейнеры в кубернетес на GCE, можно будет тогда целые статьи писать о проблемах и нуждах, но к докеру это каким образом относится?


    1. Sergey89
      23.02.2017 11:22
      +1

      На обычный линукс хост деплоится за 3 команды.


      docker-machine create --driver generic --generic-ip-address={ip} --generic-ssh-key ~/.ssh/id_rsa test
      eval $(docker-machine env test)
      docker-compose up -d

      Если нужно раскидать контейнеры по разным хостам, то это решается через swarm mode.


    1. AndrewOrsich
      23.02.2017 11:47

      Я тоже не верю в silver bullet, но, увы, выбирать приходится. У меня станные ощущения вызывает любой новый инструмент. Так и не разобрался хорошо это или плохо :) Пару лет иcпользую экслюзивно докер для развертывания приложений, в которых учавствовал. Попробую поделиться своими мыслями, но перед этим спешу поделиться несколькими фактами и наблюдениями:


      1. Первое приложение в докере задеплоил в начале 2015 (версия докера 1.5, потом обновили до 1.6). Никаких проблем по вине докера не возникало. Недавно обновили проект до последней версии — по прежнему все отлично. Сильных изменений изменений с точки зрения приложения не было.
      2. В средине 2015 довелось работать над приложением побольше. Разворачивали Mesos, Marathon, все таски в докерах. 20+ различных сервисов, 60+ контэйнеров в кластере. 1.5 года — не одной проблемы с докером, больше с мезосом возились, но это отдельная история.
      3. В команде ребята работают OSX, Ubuntu & Arch Linux — тут никаких проблем не возникало.
      4. Участвовал в гибридных проектах, где часть приложения написанна на .Net, а вторая на Node.js/Java. Для таких проектов docker-compose был просто спасением. Не было нужды писать гайды. Просто docker-compose up и спокойно продолжаем писать на любимом .NET.
      5. До появления докера, активно использовал Vagrant для тех же целей — было не так весело.

      Теперь мысли:


      1. Разница окружений (window, linux, osx). Докер ипользует внутренние фишки ядра линукса (cgroups, cnames) — отсюда и нужна запускать виртуальную машинну в бэкраунде. Не так уж и страшно.
      2. Microsoft поверил в докер и два года работала с командой Docker, чтобы сделать нативную поддержку на Windows Server 2016. Они верят, должны ли мы? :)
      3. Docker-compose действительно менял свой синтаксис — ничего от вас не утаить :). Но это все во благо прогресса. Главное, что это изменение прошло фактически безболезненно. Один раз обновил — и забыл. Радикальных изменений, как, например, в Angular.JS не было :)
      4. Не имею опыта использования docker-compose для развертывания и не вижу в этом большого смысла, так как на серверах все же вещи обстоят несколько иначе. Для этого уже есть целый зоопарк хороших инструментов: Mesos, Kubernetes, Swarm, Amazon ECS. Конечно, хотелось бы "Вжух, вжух и в продакшен", но так точно не получится.
      5. Amazon, Azure, Google Cloud — все предлагают свои "container service". Docker уже стал дефакто стандартом.

      Несомненно, перед выбором любого инструмента, нужно оценивать его сильные и слабые стороны и решать для себя использовать или нет. На сегодняшний день, для простого запуска сложных проектов в рамках большой, распределенной команды — docker-compose мне кажется лучшим вариантом. Но это, всего лишь мое мнение :)


      1. hlogeon
        23.02.2017 19:50

        Не имею опыта использования docker-compose для развертывания и не вижу в этом большого смысла, так как на серверах все же вещи обстоят несколько иначе. Для этого уже есть целый зоопарк хороших инструментов: Mesos, Kubernetes, Swarm, Amazon ECS. Конечно, хотелось бы «Вжух, вжух и в продакшен», но так точно не получится.


        Да, вы правы, конечно, но что меня удивило, никто из этих провайдеров не представляет действительно легкого и интуитивного способа это сделать. Заходишь в ECS, там тебя нагружают лапшой из EC2, ECR, Clusters, EC2 instances, Services, task definitions и еще просто тонной информации, которую предстоит переварить даже для минимальной конфигурации. Я находил решение в виде CodeShip, они помогают автоматизировать все эти трудности и сделали прекрасную консольную утилиту jet. Потом написал свои скрипты, которые делают почти тоже самое, потратил целую неделю, развернул, а потом подумал: «Да ну его нах», купил простой instance на linode и там в пару команд все завелось. Но осадочек всё-таки остался. В общем, я считаю, что пока что очень нехватает бесплатных и интуитивнопонятных интсрументов для управления более сложными системами. За рекомендации буду очень признателен)


    1. hlogeon
      23.02.2017 14:35
      +1

      Тут налетело много «защитников» докера, судя по комментариям которых, я немного сомневаюсь, что им нужен докер. Лично для меня главным его преимуществом, из-за которого я его выбрал была именно возможность создавать микросервисы и поставлять их не только как самодостаточные независимые решения, но и целыми «кластерами», инкапсулирующими решение каких-то конкретных бизнес-задач. И если с первым пунктом практически никаких проблем не возникает, то со вторым они начинаются. И это можно заметить даже по комментам. Кто-то предложил swarm-mode, кто-то тулзы наподобие code ship, я имел дело с docker-compose и Amazon task-definitions и они показали мне себя ой как недружелюбно. Входе попытки задеплоить я постоянно узнавал, что например «Фича Х из docker-compose v2 не поддерживается Amazon». Или что ты не можешь нормально использовать volume-контейнеры. Реально проблем было столько, что приложение, которое я с другим разработчиком нормально разворачивали локально на множестве различных машин(windows, mac, linux) мы деплоили целую неделю. И в итоге так и не удовлетворившись результатом и числом действий, которые необходимо проделать, что бы это задеплоить в том виде, в котором мы этого хотели просто подняли обычную linux-машину на которой выполнили docker-compose up и отложили нормальный деплой до времени появления dev.ops.
      Я очень люблю докер, но хочу предостеречь новичков, что все не так просто и радужно, каким это может казаться в начале. В общем, не серебряная пуля.


  1. VolCh
    23.02.2017 07:34
    +1

    Проблемы, с которыми сталкиваюсь постоянно:


    • передача ключей и прочих секретов в процесс сборки контейнеров (текущее решение — base64 кодирование содержимого файлов типа .ssh/id_rsa в одну строку .env, ARG в докерфайле, декодирование в RUN типа
      ARG ID_RSA
      RUN mkdir ~/.ssh && ssh-keyscan -H -t rsa github.com >> ~/.ssh/known_hosts && echo "$ID_RSA" | base64 --decode > ~/.ssh/id_rsa && unset ID_RSA && chmod 600 ~/.ssh/id_rsa && git clone git://someuser.github.com/simerepo.git && rm -r ~/.ssh
      ``` - довольно большая обвязка для одной целевой строчки, а вопросы по секурности остаются
    • то же с собственно выполняющимися контейнерами, но там немного полегче, можно шаринг файлов с хостом volume использовать
    • при шаринге активно изменяющихся файлов с хостом куча проблем с правами, никогда не угадаешь от какого пользователя коллега будет разворачивать систему, и если читать файлы из контейнера обычно проблем не составляет, то вот записанные в нём переписать или удалить на хосте обычно требует sudo
    • попытки следовать лучшим практикам типа запуска программ в контейнере не от рута ещё больше проблем доставляют
    • связывание с внешними системами (как и относительно внешними типа баз данных, так и и полностью типа платежных систем) по DNS-именам

    Это только навскидку о самом наболевшем. Как решаете эти проблемы?


    И в чём плюсы монолитного репозитория? Что делаете если один сервис нужен в нескольких проектах?


    Кстати, использование разных конфигов очень удобно делать с каскадированием, один общий docker-compose.yml и "диффы" с ним для разных окружений, для разработки стандартный docker-compose.override.yml чтобы вообще без файлов запускать, а для тестов, например docker-compose --file docker.compose.yml --file docker-compose.test.yml


    В общем соглашусь с hlogeon — странные ощущения


    1. arzonus
      23.02.2017 10:30

      передача ключей и прочих секретов в процесс сборки контейнеров (текущее решение — base64 кодирование содержимого файлов типа .ssh/id_rsa в одну строку .env, ARG в докерфайле, декодирование в RUN типа


      Обычно dockerfile находится в том репозиторий, с которым вы работаете и собираете, поэтому зачастую проблема по передаче ключей для git clone решается не на уровне контейнера, и она не возникает на процессе сборки. Однако проблема с передачей всяких секретов также решается на уровне docker-entrypoint.sh, в котором уже задаются все специфичные настройки.

      при шаринге активно изменяющихся файлов с хостом куча проблем с правами, никогда не угадаешь от какого пользователя коллега будет разворачивать систему, и если читать файлы из контейнера обычно проблем не составляет, то вот записанные в нём переписать или удалить на хосте обычно требует sudo


      Процессы в контейнере можно запускать с заданным uid. Если выставить его таким же как и на хостовой системе, вы решите проблему с правами. Конечно если у вас разные пользователей на хост системе, работающие с теми же самыми данными, то это не проблема контейнеров.

      попытки следовать лучшим практикам типа запуска программ в контейнере не от рута ещё больше проблем доставляют


      Можно по подробнее в чем была проблема?

      связывание с внешними системами (как и относительно внешними типа баз данных, так и и полностью типа платежных систем) по DNS-именам


      А здесь в чем проблема? По умолчанию в контейнер пробрасывается адрес DNS сервера хост системы. Можно самому задать DNS сервер.


      1. VolCh
        23.02.2017 11:05

        Обычно dockerfile находится в том репозиторий, с которым вы работаете и собираете, поэтому зачастую проблема по передаче ключей для git clone решается не на уровне контейнера, и она не возникает на процессе сборки. Однако проблема с передачей всяких секретов также решается на уровне docker-entrypoint.sh, в котором уже задаются все специфичные настройки.

        Речь именно о фазе сборке, о внешних приватных зависимостях. git clone как самый простой пример, обычно это npm/yarn/composer или иной пакетный менеджер.


        Процессы в контейнере можно запускать с заданным uid. Если выставить его таким же как и на хостовой системе, вы решите проблему с правами. Конечно если у вас разные пользователей на хост системе, работающие с теми же самыми данными, то это не проблема контейнеров.

        Проблема в том, что на фазе билда (читай на фазе разработки Dockerfile) я не знаю под каким uid будет выполняться docker run, более того я не знаю под каким uid работают докер-демоны.


        Можно по подробнее в чем была проблема?

        С правами вестимо. Когда entrypoint требует root прав для подготовки данных для "демона", а потом должна запустить демон с правами обычного юзера. ну и плюс сама entrypoint висит с pid 1


        А здесь в чем проблема? По умолчанию в контейнер пробрасывается адрес DNS сервера хост системы. Можно самому задать DNS сервер.

        Не всё так просто. Во-первых, очень легко может установиться 8.8.8.8, если хост с NetworkManager с DNS 127.0.0.1. Во-вторых, я немного не о том, а о links — можно задать, например, что имя some-link соотвествует в контейнере 10.0.12.45, но нельзя что test-db.local


        1. arzonus
          23.02.2017 11:27

          Речь именно о фазе сборке, о внешних приватных зависимостях. git clone как самый простой пример, обычно это npm/yarn/composer или иной пакетный менеджер.

          А с пакетными менеджерами вам не хватает функционала ARG?

          Проблема в том, что на фазе билда (читай на фазе разработки Dockerfile) я не знаю под каким uid будет выполняться docker run, более того я не знаю под каким uid работают докер-демоны.

          Если вам важно какой uid используется в контейнере при сборке и при запуске после сборки используйте команду USER.
          Под каким uid работают docker daemon на хост системе? А зачем вам это знать при работе с файлами?

          С правами вестимо. Когда entrypoint требует root прав для подготовки данных для «демона», а потом должна запустить демон с правами обычного юзера. ну и плюс сама entrypoint висит с pid 1

          Вы можете разбить на 2 файла entrypoint, в первом вы выполните работу которая требует root, потом смену uid с помощью USER, а потом запуск entrypoint. А зачем вам требуется pid?

          Не всё так просто. Во-первых, очень легко может установиться 8.8.8.8, если хост с NetworkManager с DNS 127.0.0.1. Во-вторых, я немного не о том, а о links — можно задать, например, что имя some-link соотвествует в контейнере 10.0.12.45, но нельзя что test-db.local

          Или я не понял ваш кейс или я не понимаю зачем смешивать эти разные вещи. links — это же связка конкретного контейнера с другим конкретным контейнером, зачем вам задавать url данного контейнера, если вы и так знаете его прямой IP адрес?


          1. VolCh
            23.02.2017 13:36

            А с пакетными менеджерами вам не хватает функционала ARG?

            Одна команда выливается в почти десяток строчек в докерфайле, плюс требует ручного заполнения .env файла.


            Если вам важно какой uid используется в контейнере при сборке и при запуске после сборки используйте команду USER.
            Под каким uid работают docker daemon на хост системе? А зачем вам это знать при работе с файлами?

            Мне не важен uid, мне важно:


            • минимум действий от рута в контейнере при запуске
            • отсутствие конфликта прав при монтировании каталога с исходниками приложения на контейнер в процессе разработки.

            И в общем случае я не знаю под каким uid на хост-системе работает и демон докера, и пользователь отдающий команду на запуск контейнера. На своей локальной знаю — 0 и 1000, но это мало помогает, если контейнер работает от 0 или не 1000


            Вы можете разбить на 2 файла entrypoint, в первом вы выполните работу которая требует root, потом смену uid с помощью USER, а потом запуск entrypoint. А зачем вам требуется pid?

            Это как? entrypoint выполняется уже в контейнере, там нет команд типа USER, максимум sudo/su


            Или я не понял ваш кейс или я не понимаю зачем смешивать эти разные вещи. links — это же связка конкретного контейнера с другим конкретным контейнером, зачем вам задавать url данного контейнера, если вы и так знаете его прямой IP адрес?

            Есть в docker-compose секция тип external для link, для ссылки из контейнеров на внешние по отношению к этой системе контейнерам или физхосты. Но только по IP. Полезно, например, для подключения кучи стэков к одной базе или для мокания совсем внешних сервисов.


            1. alexkunin
              23.02.2017 15:19

              Про внешние линки. Да вроде бы не только по айпи, в доках этого не указано — там вообще имена сервисов используются, а не адреса. Зато указано что вся концепция — это легаси, лучше используйте секцию networks (и, наверное, aliases — это уже от меня, а не от документации).

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

              Про сборку. Лично мне нравится концепция отдельного сборочного контейнера, которая подтягивает все инструменты для сборки. В таком случае вполне логично в докерфайле (или ентрипоинте) держать подробную процедуру сборки. Еще лучше бы иметь там набор скриптов: сборка, пересборка, вотч какой-нибудь, и т.д. А вот все эти докерфайлы, которые в первых строках устанавливают инструменты сборки, потом собирают, а в конце вычищают инструментарий — это вот мне не очень нравится, каждый билд гарантированно затягивается.

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

              Про повторное использование одного сервиса в разных проектах. Очевидно, нужно использовать более общий репозиторий — публичный или приватный. Точно так же, как если бы написанная вами библиотека на конкретном ЯП использовалась в двух и более проектах. Можно, конечно, сделать зависимость проекта Б от проекта А, в котором нужный сервис находится, но лучше бы сервис выделить в проект В, и поставить зависимости у А и Б от В — по правилу минимальной связности.


              1. VolCh
                25.02.2017 10:12

                Вот эту опцию имел в виду: https://docs.docker.com/compose/compose-file/#/extrahosts


                Есть еще вариант шаринга томов между контейнерами, типа volumes_from когда надо дать одному контейнеру права на чтения тома, в который пишет другой.


                Концепция мне тоже нравится, но на практике для php/node приложений реализовать её не смог за разумное время, не теряя прелестей docker-compose по сборке. Как-то не нашел нормального способа билдить и запускать всю систему с билд-контейнерами без многочисленных баш-скриптов на хосте для подготовки образов на хосте, чтобы при запуске докер-композ он брал готовые, а не пытался билдить.


                С секретами начали что-то делать, но только в swarm-mode и только в рантайме, вряд ли будут на обычный демон в билд-тайм переносить. Да, отдельный билд-контейнер решит проблему билд-секретов в сварм-моде.


                Я про повторное использование имеющегося сервиса, а не про повторное использование кода или даже образа. Есть работающий сервис, на который проект завязан. Для простоты можно считать его внешним, на который нет никакого влияния и очень сложным для моканья в проекте. Да даже если внутренний и простой, то поднимать отдельный инстанс мока для каждого разработчика и тестировщика (а то и несколько для разных ветвей или проектов) просто расточительно.


    1. ReklatsMasters
      23.02.2017 10:35
      +1

      А какую проблему вы решаете, передавая так ssh ключи? И почему вы считаете, что DNS это проблема? Это как минимум позволяет нивилировать проблемы, связанные с переездом.


      1. VolCh
        23.02.2017 11:07

        Установка зависимостей типа npm_modules, vendor и т. п. из приватных репозиториев.


        Проблема в том, что нельзя контейнеру задать link с cсылкой на внешнее DNS-имя, только на IP-адрес.


        1. alexkunin
          23.02.2017 15:28

          Я выше про линки ответил, но тут подробнее напишу. Мне в одном проекте нужно было общаться с внешним АПИ по конкретному адресу и порту, причем на продакшене там поднимается впн и все такое, т.е. нельзя просто так постучаться по этому адресу.

          В моей девелоперской среде можно было бы поднять впн и т.д… Но в общем проблему решил так:

          1. Создал контейнер, в котором есть ссш и команда по установке туннеля на продакшн (или на стейджинг — в зависимости от того, тестируется ли сэндбокс или лайв АПИ). Туннель локально оканчивается на том же порту, на который стучится в АПИ (443 в моем случае).

          2. Этот контейнер указан в обычных линках в докер-композе, и алиас линка указан как полное доменное имя реального АПИ.

          3. В сервисе приложения, который этот линк использует, я просто стучусь на полное доменное имя и правильный порт, а на самом деле связь устанавливается с моим ссш-контейнером, который уже поднял туннель на другой хост, где впн и все дела.

          Возможно, это не вполне соответствует вашему случаю. Но где-то читал, что имя сервиса в докер-композе с какой-то версии будет именно именем хоста внутри сети контейнеров (будет во внутреннем ДНС), и что там можно использовать точки, и что эти имена ресолвятся до всего остального (т.е. реальные хосты «закрываются» вашими линками — если имена совпадают).


    1. Sergey89
      23.02.2017 11:43

      И в чём плюсы монолитного репозитория? Что делаете если один сервис нужен в нескольких проектах?

      Это решается через реестр образов.


  1. mrmot
    23.02.2017 10:50

    Раз мы заговорили о «девелопмент» среде на основе докера. Как решить вопрос запуска разных приложений хотя бы на разных IP минимальными телодвижениями для программиста и автоматически прописывать DNS запись на новый IP?


    1. vvasilenok
      23.02.2017 11:52

      Очень просто ports: ip:80:80. Но я сейчас использую nginx-proxy как описано здесь, только на Win пришлось поставить локально nginx, для проксирования на Docker Api. На linux машине прописал в DNS домен, для локальной разработки


    1. AndrewOrsich
      23.02.2017 11:55

      Если вы говорите о локальной среде, мне не совсем понятно, зачем запускать сервисы на разных ip. При развертывании проблемы DNS (service discovery) уже решены за счет других сторонних инструментов: Mesos/Marathon, Kubernetes, Consul, Swarm. Осталось выбрать :)


      1. alexkunin
        23.02.2017 15:33
        +1

        Работаю над тремя проектами, все висят на порту 80, зайти с браузера на хосте — нужно мапить на локалхост под разными портами. Иногда это не проблема, иногда это конкретный батхерт, если проект легаси, и у него порт прибит гвоздями (а иногда и хостнейм...). Можно запускать один проект за раз, но пришел быстрый запрос на фикс, я переключаюсь… и в общем переключение стоит заметного времени.

        Тут где-то пробегал вариант с со скриптом автоматической настройки прокси, но там были определенные проблемы, что-то вроде перезагрузки чего-то при каждом новом адресе (браузер жестко кешировал скрипт настройки прокси).


    1. artarn
      23.02.2017 15:31

      Я на совоей Ubuntu использую tonistiigi/dnsdock.

      Настраивается в два шага:

      1. прописываю в resolv.conf строку nameserver 172.17.42.1
      2. колдую docker run --name 'ddns' --restart=unless-stopped -d -v '/var/run/docker.sock:/run/docker.sock' -p '172.17.42.1:53:53/udp' tonistiigi/dnsdock -domain='docker'


      где,
      --restart=unless-stopped — авто перезапуск dnsdock контейнера после перезагрузки компьютера или возникновении ошибки в контейнере
      -p '172.17.42.1:53:53/udp' — проброс DNS сервиса на ip docker0 интерфейса
      -domain='docker' — домен наших контейнеров.

      Адрес контейнера формируется из переменных окружения DNSDOCK_NAME, DNSDOCK_IMAGE и домена который был указан при запуске dnsdock

      Например в docker-compose контейнеру nginx я добавляю переменные окружения DNSDOCK_NAME и DNSDOCK_IMAGE

      project_nginx:
      image: nginx

      environment:
      DNSDOCK_NAME: site
      DNSDOCK_IMAGE: project

      и после запуска контейнера, сразу могу стучаться по адресу http://site.project.docker


      1. alexkunin
        23.02.2017 15:35

        Вот спасибо, нужно будет посмотреть.

        А там обязательно подсаживать эти ДНС-переменные в проекты? Нет какого-нибудь режима «использовать имя сервсиса-директории»?


        1. artarn
          23.02.2017 15:52

          Честно говоря не знаю. Я это дело как настроил себе год назад, с тех пор для нового проекта только копирую структуру директорий и docker-compose.yaml. Потом bash скриптом массово переименовываю project на другой префикс.


    1. rustler2000
      24.02.2017 21:26

      Есть контейнер который DNS на запущенные контейнеры держит.


  1. cag01
    23.02.2017 11:52

    А вы запускаете докер на физических или виртуальных серверах?


    1. AndrewOrsich
      23.02.2017 11:57

      Использую OSX, т.е. локально Docker запускается в виртуальной машине, но это происходит "за ширмой" и не доставляется проблем. Когда использовал Linux, докер запускался непосредственно на физической машине.


      Если Вы имели ввиду, как мы запускаем докер в "production" окружениях — то там да, виртуальные машины (Amazon, Google Cloud, Digital Ocean).


      1. cag01
        23.02.2017 12:36

        Немного не в тему, то я плохо представляю как можно проект на Amazon, Google Cloud, Digital Ocean держать с их ценами… Имхо, лишь если крутой инвестор вложился в проект, то это позволительно… Или я ошибаюсь?


        1. AndrewOrsich
          23.02.2017 12:45

          Самый дешевый вариант, который достаточно долго использовал для разных, в том числе и своих проектов — это Digital Ocean дроплет за $5 в месяц. Вполне достаточно для старта большинства приложений. Можно увеличивать размер сервера по необходимости. Amazon, Google Cloud — предлагают хорошую интергацию с их сервисами. Позволяют сместить фокус с работы над инфраструктурой на работу над приложением путем больших затрат. Тут все индивидуально.


          Мне кажется, что все же крутой инвестор не самая большая проблема :)


          1. alexkunin
            23.02.2017 15:40

            Если на амазоне разворачивться минимально, то будет одна нода в вашем клауде, и это вроде бы 14 у.е. в месяц на t2.micro, который входит во фри тир (первый год бесплатно). И там еще вроде t2.nano ноды появились, это еще меньше — $5.11 в месяц. Остальные вещи бесплатны, по большей части, в том числе и ССЛ сертификат на балансере.

            Ну, я могу ошибаться, Амазон штука сложная…


  1. cag01
    23.02.2017 12:33

    А как вы поступаете в продакше с обшими папками, ну, например, загруженные юзерами файлы и т.п. Просто шарите папку, чтобы файлы на хосте хранились?


    1. AndrewOrsich
      23.02.2017 12:47

      Чтобы избежать мучений с закончившимся местом на диске — используем, в основном, Amazon S3. Для разного рода временных файлов — используем volumes и храним на хосте.


    1. Sergey89
      23.02.2017 13:16
      +1

      Для этого есть named volumes. Данные в них не удаляются даже после удаления контейнера. По умолчанию данные будут храниться на хосте в папке докера, но можно использовать другие драйверы.


  1. AccessGranted
    23.02.2017 13:00
    +1

    Постоянно делать docker-compose up --build не обязательно, если уверен что конфигурация не изменялась. На крайний случай можно git хук повесить, который будет пересобирать образы при изменении Dockerfile, но это лишнее на мой взгляд.

    У меня сборка всего проекта занимает около 15 минут даже с учетом кеша, так что иногда это может быть проблемой.

    Что касается Docker, так мы его уже на боевых серверах используем, выкатываем не сам код а собраные образы вместе с окружением и кодом.

    Количество отложенных кирпичей после каждого обновления резко уменшилось. Появилась какая-то увереность что все будет работать так как это работало на тестовом сервере.


    1. AndrewOrsich
      23.02.2017 13:28
      +1

      Да, абсолютно. Можно жить и без --build, но у нас это часто приводило к станностям в работе приложения. Обновил проект, забыл запустить с --build. Мы измеряли, у нас разница с --build и без --build всего несколько секунд, что не существенно, особенно с учетом того, что проект рестартуется максимум два раза в день. Проект в целом запускается с --build за секунд 5-10 (2 базы данных, 6 сервисов), не более того. Если запускается дольше, есть вероятность, что Dockerfile не совсем корректно построен. Несколько самых популярных ошибки из нашей практики:


      1. Сторонние зависимости должны идти в начале докера. Они меняются редко и не будут перестраиваться каждый раз, при изменении кода приложения.
      2. Копирование файлов, в которых описаны сторонние зависимости должно идти отдельным шагом, что бы при каждом запуске они не ставились заново. В примере ниже, сразу происходит копирование package.json (зависимости для Node.js приложения), а лишь потом происходит установка зависимостей. Если package.json не меняется — то и зависимости не будут ставиться заново.
      3. На рабочих окружениях, код не копируется в Docker, а добавляется как volume.

      FROM node:6.3
      EXPOSE 8082
      COPY package.json /app/
      RUN cd /app &&     npm install --quiet
      WORKDIR /app

      По поводу сборки у нас подход похожий, но немного на стероидах. Поделюсь этим в отдельной статье.


      Уменьшение кирпичиков при развертывании — это как глоток свежего воздуха, морозным зимним утром.


      1. cag01
        04.03.2017 16:10

        У вас весь проект умещается на одном сервере или развертываете на нескольких через swarm mode? Получилось ли со swarm mode автоматизировать build и deploy?


      1. cag01
        04.03.2017 18:35

        Сторонние зависимости должны идти в начале докера. Они меняются редко и не будут перестраиваться каждый раз, при изменении кода приложения.

        Например, в случае с гитлабом и при использовании рекомендуемого метода docker-in-docker для runner, то, как я понимаю, перестраиваться будут все равно каждый раз.


  1. 6ec_uk
    23.02.2017 15:32
    +2

    при шаринге активно изменяющихся файлов с хостом куча проблем с правами, никогда не угадаешь от какого пользователя коллега будет разворачивать систему, и если читать файлы из контейнера обычно проблем не составляет, то вот записанные в нём переписать или удалить на хосте обычно требует sudo

    Данную проблему решил с помощью запуска докера с параметром --userns-remap=user.
    В контейнере процессы запускают из под рута, но файлы созданные в volume имеют права вашего локального пользователя и группы, указанного в ремапинге.


  1. hlogeon
    23.02.2017 22:04

    Может кому поможет, но внезапно наткнулся в reddit на ссылочку
    https://nanobox.io/app-deployment-tool/


  1. cag01
    26.02.2017 11:34

    Как поступить, если код приложения должен быть доступен в нескольких контейнерах — nginx, php, cron-php (для крона у меня отдельный).
    Стоит ли его включать внутрь (как делаете вы) всех контейнеров или же лучше маунтить внешнюю папку?


    1. VolCh
      26.02.2017 12:19

      Я пока остановился на volume_from, то есть один контейнер делаем базовым (я обычно php-fpm том делаю, cli внутри него использую при необходимости), выставляющий нужные для других тома. Но вообще считаю костылём, контейнеры должны быть независимыми, но пока не нашел красивого удобного способа собрать, например, nginx контейнер со статикой отдельно от php-fpm контейнера.


      1. cag01
        26.02.2017 14:51

        Может стоит тогда в nginx контейнер запихивать тоже вообще весь код приложения, а не только статику?


        1. VolCh
          27.02.2017 07:25

          Не имеет значения особого в плане штатной сборки, в nginx контейнер нужно будет установить php со всеми нужными расширениями, собрать, удалить php. Удалять только php или ещё и основной код — особой разницы нет. Мне не нравится сама идея при сборке nginx-контейнера использовать php. Ну и всякие каталоги /uploads всё равно нужно будет шарить между nginx и php контейнерами.


          1. cag01
            27.02.2017 14:21

            Я имел ввиду, что код приложения целиком запихивать как в nginx, так и в php контейнеры. При этом не комбинировать их. Как вам это решение?

            Мне не нравится, что при этом придется думать о синхронизации(чтобы в обеих контейнерах был одинаковый код) и пересоздавать также nginx контейнер при обновлении кода.

            Иначе получается, что нельзя код включать внутрь контейнера и надо хранить его в отдельном volume. Но как тогда делать деплой чере докер? Ведь не получится просто обновить php-контейнеры, ибо код в volume.


            1. VolCh
              27.02.2017 16:48

              Я это понял. Но чтобы его запихнуть нужно, как правило, хотя бы composer install запустить, а это значит установить php и composer в nginx контейнер, если следовать классическому подходу. А поскольку ни php, ни php-код для nginx-контейнера не нужны, то логично их удалить.


              В принципе есть много разных вариантов сборки контейнеров только с тем, что им нужно и без шаринга между собой, но вот не нашёл ни одного, который бы работал нормально с docker-compose — он рассчитан только на сборку образов с помощью docker build, который рассчитан только на копирование файлов из контекста :(


              1. cag01
                27.02.2017 17:55

                Может тогда правильно было бы так:
                1. На сервере, на котором происходит сборка (или же в CI) установить php, composer
                2. clone из репозитори
                3. composer install. В итоге мы имеем проект целиком, но еще не в контейнерах
                4. Строим контейнеры через build. Индивидуально включая в каждый из них лишь те файлы, которые нужны контейнеру.
                5. Делаем ролаут контейнеров nginx(со статикой проекта), php и т.п.

                P.S. Единственная проблема, что при ролауде nginx оборвутся соединения из-за перезапуска. Как её решить?


                1. VolCh
                  27.02.2017 18:24

                  Ну совсем правильно тогда так:


                  1. Делаем билд-контейнер с php, composer,… Пользователь и прочее строго синхронизировано с целевыми контейнерами, чтобы не возникло проблем с правами.

                  Делать грэйсфул стоп контейнеров, обязательно перед ними балансер и минимум два контейнера, из обновление разнести по времени достаточным для стопа и запуска.


      1. hlogeon
        26.02.2017 15:36

        Лично я начинал с volumes_from подхода, но в итоге отказался от него из-за сопутствующих трудностей деплоя в пользу включения внутрь контейнера в качестве volume


        1. VolCh
          27.02.2017 07:25

          Не очень понял. Можно пример?


        1. cag01
          27.02.2017 14:22

          Докерский деплой ведь не получится? Как обновляешь код?