В этой статье мы поделимся опытом автоматизации запуска, тестирования и конфигурации больших проектов с использованием 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 должен быть более-менее понятен, но стоит обратить внимание на несколько деталей:
context: ./web
— таким образом указывается путь к докер файлу сервиса внутри нашего репозитория.dockerfile: Dockerfile.dev
— мы используем отдельный Dockerfile.dev для рабочих окружений. Для "production" окружений, мы копируем код в образ Docker, а на рабочих окружениях мы добавляем код как "volume". При использовании "volume" не приходится перезапускать docker-compose каждый раз после изменений в коде.volumes: - "./web/src:/web/src"
— так код добавляется как "volume" в Docker.- 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 это очень полезный и гибкий способ автоматизации запуска проектов.
Когда новые разработчики добавляются к нам в команду, мы даем им небольшую задачу, которую они должны закончить к концу первого рабочего дня. Каждый, кто присоединялся к нашей команде, справился с этим и был самым счастливым человеком на Земле. Уже с первых минут новые разработчики могут сфокусироваться на важных задачах и не тратить времени на то, чтобы запустить проект. Наша документация для старта проекта состоит из трех пунктов:
- Установить Docker и Docker-compose
- Склонировать репозиторий
- Запустить в терминале
./bin/start.sh
Для того, чтобы Вам было проще понять эту статью, у нас есть пример проекта на Github. Делитесь вашим опытом и задавайте вопросы.
Надеемся, что статья была полезной и поможет сделать Ваш проект лучше :)
Версию на английском, можно почитать здесь.
Комментарии (53)
hlogeon
23.02.2017 04:10+6У меня от докера такие странные ощущения. С одной стороны, это инструмент, который призван упростить развертывание приложения, но по факту все оказывается совершенно не так, как пишут в статьях. Проблемы начинаются с разницы окружений(а ведь мы с Вами помним, что докер изначально вроде бы должен был ее нивелировать) windows, mac, linux запускают докер по-разному. Кому-то нужно запускать контейнеры внутри виртуальной машины, кому-то она не нужна. Потом ты сталкиваешься с несовмесимыми с прошлой версией изменениями в самом docker, либо в доп. инструментах вроде docker-compose и вынужден обновлять конфиги, снова пересобирать и тестировать свои контейнеры. Тот же самый docker-compose.yml можно описать используя разные версии синтаксиса этого файла.
И вот, собрал ты такой счастливый проект, состоящий, скажем, из 10 сервисов. И думаешь: «О! Как хорошо, что я использовал докер, ведь теперь для запуска проекта мне нужно только выполнить `docker-compose up`» Но не тут то было, я бы даже сказал ДАЛЕКО не тут! Чтобы понимать, о чем я говорю — просто пойдите и попробуйте задеплоить любой Hello, World на тот же Amazon ECS.arzonus
23.02.2017 10:39+1windows, 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.
Расскажите подробнее в чем были сложности :)myrkoxx
23.02.2017 17:01есть отличие между Amazon EC2 и Amazon ECS. По простому: Amazon EC2 ака твой VPS, ака ваш пример с Azure, где тебе надо будет ставить докер и тд для его запуска. Для Amazon ECS же достаточно указать (в теории) просто откуда взять твой Docker образ и указать условия запуска. Ето 2 разных сервиса.
hlogeon прав:
просто пойдите и попробуйте задеплоить любой Hello, World на тот же Amazon ECS
не так просто как взять установить самому докер на vps и прогнатьCaravus
25.02.2017 14:06Ну вообще какой-то странный аргумент, в целом. Попробуйте запустить докер-контейнеры в кубернетес на GCE, можно будет тогда целые статьи писать о проблемах и нуждах, но к докеру это каким образом относится?
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.
AndrewOrsich
23.02.2017 11:47Я тоже не верю в silver bullet, но, увы, выбирать приходится. У меня станные ощущения вызывает любой новый инструмент. Так и не разобрался хорошо это или плохо :) Пару лет иcпользую экслюзивно докер для развертывания приложений, в которых учавствовал. Попробую поделиться своими мыслями, но перед этим спешу поделиться несколькими фактами и наблюдениями:
- Первое приложение в докере задеплоил в начале 2015 (версия докера 1.5, потом обновили до 1.6). Никаких проблем по вине докера не возникало. Недавно обновили проект до последней версии — по прежнему все отлично. Сильных изменений изменений с точки зрения приложения не было.
- В средине 2015 довелось работать над приложением побольше. Разворачивали Mesos, Marathon, все таски в докерах. 20+ различных сервисов, 60+ контэйнеров в кластере. 1.5 года — не одной проблемы с докером, больше с мезосом возились, но это отдельная история.
- В команде ребята работают OSX, Ubuntu & Arch Linux — тут никаких проблем не возникало.
- Участвовал в гибридных проектах, где часть приложения написанна на .Net, а вторая на Node.js/Java. Для таких проектов docker-compose был просто спасением. Не было нужды писать гайды. Просто
docker-compose up
и спокойно продолжаем писать на любимом .NET. - До появления докера, активно использовал Vagrant для тех же целей — было не так весело.
Теперь мысли:
- Разница окружений (window, linux, osx). Докер ипользует внутренние фишки ядра линукса (cgroups, cnames) — отсюда и нужна запускать виртуальную машинну в бэкраунде. Не так уж и страшно.
- Microsoft поверил в докер и два года работала с командой Docker, чтобы сделать нативную поддержку на Windows Server 2016. Они верят, должны ли мы? :)
- Docker-compose действительно менял свой синтаксис — ничего от вас не утаить :). Но это все во благо прогресса. Главное, что это изменение прошло фактически безболезненно. Один раз обновил — и забыл. Радикальных изменений, как, например, в Angular.JS не было :)
- Не имею опыта использования docker-compose для развертывания и не вижу в этом большого смысла, так как на серверах все же вещи обстоят несколько иначе. Для этого уже есть целый зоопарк хороших инструментов: Mesos, Kubernetes, Swarm, Amazon ECS. Конечно, хотелось бы "Вжух, вжух и в продакшен", но так точно не получится.
- Amazon, Azure, Google Cloud — все предлагают свои "container service". Docker уже стал дефакто стандартом.
Несомненно, перед выбором любого инструмента, нужно оценивать его сильные и слабые стороны и решать для себя использовать или нет. На сегодняшний день, для простого запуска сложных проектов в рамках большой, распределенной команды — docker-compose мне кажется лучшим вариантом. Но это, всего лишь мое мнение :)
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 и там в пару команд все завелось. Но осадочек всё-таки остался. В общем, я считаю, что пока что очень нехватает бесплатных и интуитивнопонятных интсрументов для управления более сложными системами. За рекомендации буду очень признателен)
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.
Я очень люблю докер, но хочу предостеречь новичков, что все не так просто и радужно, каким это может казаться в начале. В общем, не серебряная пуля.
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 — странные ощущения
arzonus
23.02.2017 10:30передача ключей и прочих секретов в процесс сборки контейнеров (текущее решение — base64 кодирование содержимого файлов типа .ssh/id_rsa в одну строку .env, ARG в докерфайле, декодирование в RUN типа
Обычно dockerfile находится в том репозиторий, с которым вы работаете и собираете, поэтому зачастую проблема по передаче ключей для git clone решается не на уровне контейнера, и она не возникает на процессе сборки. Однако проблема с передачей всяких секретов также решается на уровне docker-entrypoint.sh, в котором уже задаются все специфичные настройки.
при шаринге активно изменяющихся файлов с хостом куча проблем с правами, никогда не угадаешь от какого пользователя коллега будет разворачивать систему, и если читать файлы из контейнера обычно проблем не составляет, то вот записанные в нём переписать или удалить на хосте обычно требует sudo
Процессы в контейнере можно запускать с заданным uid. Если выставить его таким же как и на хостовой системе, вы решите проблему с правами. Конечно если у вас разные пользователей на хост системе, работающие с теми же самыми данными, то это не проблема контейнеров.
попытки следовать лучшим практикам типа запуска программ в контейнере не от рута ещё больше проблем доставляют
Можно по подробнее в чем была проблема?
связывание с внешними системами (как и относительно внешними типа баз данных, так и и полностью типа платежных систем) по DNS-именам
А здесь в чем проблема? По умолчанию в контейнер пробрасывается адрес DNS сервера хост системы. Можно самому задать DNS сервер.
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
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 адрес?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. Полезно, например, для подключения кучи стэков к одной базе или для мокания совсем внешних сервисов.
alexkunin
23.02.2017 15:19Про внешние линки. Да вроде бы не только по айпи, в доках этого не указано — там вообще имена сервисов используются, а не адреса. Зато указано что вся концепция — это легаси, лучше используйте секцию networks (и, наверное, aliases — это уже от меня, а не от документации).
Про рута в ентрипоинт. А можете привести пример, пожалуйста, какие действия от рута вам нужны при запуске? В моей практике проблема с UID была только при шаринге директории с хоста, эта часть запутана, вы это уже описали как проблему. Но в продакшене такое все равно почти никогда нельзя использовать, т.к. облачные штуки как бы в отрыве от хоста.
Про сборку. Лично мне нравится концепция отдельного сборочного контейнера, которая подтягивает все инструменты для сборки. В таком случае вполне логично в докерфайле (или ентрипоинте) держать подробную процедуру сборки. Еще лучше бы иметь там набор скриптов: сборка, пересборка, вотч какой-нибудь, и т.д. А вот все эти докерфайлы, которые в первых строках устанавливают инструменты сборки, потом собирают, а в конце вычищают инструментарий — это вот мне не очень нравится, каждый билд гарантированно затягивается.
Про шаринг секретов. Это большая боль, тут ничего не поделать средствами докера, на сколько я знаю. Для рантайма нужно бы что-то вроде Ваулт от ХатиКорп (что лично я вижу как большой оверкил для большинства своих задач), для сборки — мапить файлы и директории с хоста (и если делать это в отдельном сборочном контейнере, то неплохой барьер образуется — между рантаймом и сборочными секретами).
Про повторное использование одного сервиса в разных проектах. Очевидно, нужно использовать более общий репозиторий — публичный или приватный. Точно так же, как если бы написанная вами библиотека на конкретном ЯП использовалась в двух и более проектах. Можно, конечно, сделать зависимость проекта Б от проекта А, в котором нужный сервис находится, но лучше бы сервис выделить в проект В, и поставить зависимости у А и Б от В — по правилу минимальной связности.VolCh
25.02.2017 10:12Вот эту опцию имел в виду: https://docs.docker.com/compose/compose-file/#/extrahosts
Есть еще вариант шаринга томов между контейнерами, типа volumes_from когда надо дать одному контейнеру права на чтения тома, в который пишет другой.
Концепция мне тоже нравится, но на практике для php/node приложений реализовать её не смог за разумное время, не теряя прелестей docker-compose по сборке. Как-то не нашел нормального способа билдить и запускать всю систему с билд-контейнерами без многочисленных баш-скриптов на хосте для подготовки образов на хосте, чтобы при запуске докер-композ он брал готовые, а не пытался билдить.
С секретами начали что-то делать, но только в swarm-mode и только в рантайме, вряд ли будут на обычный демон в билд-тайм переносить. Да, отдельный билд-контейнер решит проблему билд-секретов в сварм-моде.
Я про повторное использование имеющегося сервиса, а не про повторное использование кода или даже образа. Есть работающий сервис, на который проект завязан. Для простоты можно считать его внешним, на который нет никакого влияния и очень сложным для моканья в проекте. Да даже если внутренний и простой, то поднимать отдельный инстанс мока для каждого разработчика и тестировщика (а то и несколько для разных ветвей или проектов) просто расточительно.
ReklatsMasters
23.02.2017 10:35+1А какую проблему вы решаете, передавая так ssh ключи? И почему вы считаете, что DNS это проблема? Это как минимум позволяет нивилировать проблемы, связанные с переездом.
VolCh
23.02.2017 11:07Установка зависимостей типа npm_modules, vendor и т. п. из приватных репозиториев.
Проблема в том, что нельзя контейнеру задать link с cсылкой на внешнее DNS-имя, только на IP-адрес.
alexkunin
23.02.2017 15:28Я выше про линки ответил, но тут подробнее напишу. Мне в одном проекте нужно было общаться с внешним АПИ по конкретному адресу и порту, причем на продакшене там поднимается впн и все такое, т.е. нельзя просто так постучаться по этому адресу.
В моей девелоперской среде можно было бы поднять впн и т.д… Но в общем проблему решил так:
1. Создал контейнер, в котором есть ссш и команда по установке туннеля на продакшн (или на стейджинг — в зависимости от того, тестируется ли сэндбокс или лайв АПИ). Туннель локально оканчивается на том же порту, на который стучится в АПИ (443 в моем случае).
2. Этот контейнер указан в обычных линках в докер-композе, и алиас линка указан как полное доменное имя реального АПИ.
3. В сервисе приложения, который этот линк использует, я просто стучусь на полное доменное имя и правильный порт, а на самом деле связь устанавливается с моим ссш-контейнером, который уже поднял туннель на другой хост, где впн и все дела.
Возможно, это не вполне соответствует вашему случаю. Но где-то читал, что имя сервиса в докер-композе с какой-то версии будет именно именем хоста внутри сети контейнеров (будет во внутреннем ДНС), и что там можно использовать точки, и что эти имена ресолвятся до всего остального (т.е. реальные хосты «закрываются» вашими линками — если имена совпадают).
Sergey89
23.02.2017 11:43И в чём плюсы монолитного репозитория? Что делаете если один сервис нужен в нескольких проектах?
Это решается через реестр образов.
- передача ключей и прочих секретов в процесс сборки контейнеров (текущее решение — base64 кодирование содержимого файлов типа .ssh/id_rsa в одну строку .env, ARG в докерфайле, декодирование в RUN типа
mrmot
23.02.2017 10:50Раз мы заговорили о «девелопмент» среде на основе докера. Как решить вопрос запуска разных приложений хотя бы на разных IP минимальными телодвижениями для программиста и автоматически прописывать DNS запись на новый IP?
vvasilenok
23.02.2017 11:52Очень просто ports: ip:80:80. Но я сейчас использую nginx-proxy как описано здесь, только на Win пришлось поставить локально nginx, для проксирования на Docker Api. На linux машине прописал в DNS домен, для локальной разработки
AndrewOrsich
23.02.2017 11:55Если вы говорите о локальной среде, мне не совсем понятно, зачем запускать сервисы на разных ip. При развертывании проблемы DNS (service discovery) уже решены за счет других сторонних инструментов: Mesos/Marathon, Kubernetes, Consul, Swarm. Осталось выбрать :)
alexkunin
23.02.2017 15:33+1Работаю над тремя проектами, все висят на порту 80, зайти с браузера на хосте — нужно мапить на локалхост под разными портами. Иногда это не проблема, иногда это конкретный батхерт, если проект легаси, и у него порт прибит гвоздями (а иногда и хостнейм...). Можно запускать один проект за раз, но пришел быстрый запрос на фикс, я переключаюсь… и в общем переключение стоит заметного времени.
Тут где-то пробегал вариант с со скриптом автоматической настройки прокси, но там были определенные проблемы, что-то вроде перезагрузки чего-то при каждом новом адресе (браузер жестко кешировал скрипт настройки прокси).
artarn
23.02.2017 15:31Я на совоей Ubuntu использую tonistiigi/dnsdock.
Настраивается в два шага:
- прописываю в resolv.conf строку nameserver 172.17.42.1
- колдую 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.dockeralexkunin
23.02.2017 15:35Вот спасибо, нужно будет посмотреть.
А там обязательно подсаживать эти ДНС-переменные в проекты? Нет какого-нибудь режима «использовать имя сервсиса-директории»?artarn
23.02.2017 15:52Честно говоря не знаю. Я это дело как настроил себе год назад, с тех пор для нового проекта только копирую структуру директорий и docker-compose.yaml. Потом bash скриптом массово переименовываю project на другой префикс.
cag01
23.02.2017 11:52А вы запускаете докер на физических или виртуальных серверах?
AndrewOrsich
23.02.2017 11:57Использую OSX, т.е. локально Docker запускается в виртуальной машине, но это происходит "за ширмой" и не доставляется проблем. Когда использовал Linux, докер запускался непосредственно на физической машине.
Если Вы имели ввиду, как мы запускаем докер в "production" окружениях — то там да, виртуальные машины (Amazon, Google Cloud, Digital Ocean).
cag01
23.02.2017 12:36Немного не в тему, то я плохо представляю как можно проект на Amazon, Google Cloud, Digital Ocean держать с их ценами… Имхо, лишь если крутой инвестор вложился в проект, то это позволительно… Или я ошибаюсь?
AndrewOrsich
23.02.2017 12:45Самый дешевый вариант, который достаточно долго использовал для разных, в том числе и своих проектов — это Digital Ocean дроплет за $5 в месяц. Вполне достаточно для старта большинства приложений. Можно увеличивать размер сервера по необходимости. Amazon, Google Cloud — предлагают хорошую интергацию с их сервисами. Позволяют сместить фокус с работы над инфраструктурой на работу над приложением путем больших затрат. Тут все индивидуально.
Мне кажется, что все же крутой инвестор не самая большая проблема :)
alexkunin
23.02.2017 15:40Если на амазоне разворачивться минимально, то будет одна нода в вашем клауде, и это вроде бы 14 у.е. в месяц на t2.micro, который входит во фри тир (первый год бесплатно). И там еще вроде t2.nano ноды появились, это еще меньше — $5.11 в месяц. Остальные вещи бесплатны, по большей части, в том числе и ССЛ сертификат на балансере.
Ну, я могу ошибаться, Амазон штука сложная…
cag01
23.02.2017 12:33А как вы поступаете в продакше с обшими папками, ну, например, загруженные юзерами файлы и т.п. Просто шарите папку, чтобы файлы на хосте хранились?
AndrewOrsich
23.02.2017 12:47Чтобы избежать мучений с закончившимся местом на диске — используем, в основном, Amazon S3. Для разного рода временных файлов — используем volumes и храним на хосте.
Sergey89
23.02.2017 13:16+1Для этого есть named volumes. Данные в них не удаляются даже после удаления контейнера. По умолчанию данные будут храниться на хосте в папке докера, но можно использовать другие драйверы.
AccessGranted
23.02.2017 13:00+1Постоянно делать docker-compose up --build не обязательно, если уверен что конфигурация не изменялась. На крайний случай можно git хук повесить, который будет пересобирать образы при изменении Dockerfile, но это лишнее на мой взгляд.
У меня сборка всего проекта занимает около 15 минут даже с учетом кеша, так что иногда это может быть проблемой.
Что касается Docker, так мы его уже на боевых серверах используем, выкатываем не сам код а собраные образы вместе с окружением и кодом.
Количество отложенных кирпичей после каждого обновления резко уменшилось. Появилась какая-то увереность что все будет работать так как это работало на тестовом сервере.
AndrewOrsich
23.02.2017 13:28+1Да, абсолютно. Можно жить и без --build, но у нас это часто приводило к станностям в работе приложения. Обновил проект, забыл запустить с --build. Мы измеряли, у нас разница с
--build
и без--build
всего несколько секунд, что не существенно, особенно с учетом того, что проект рестартуется максимум два раза в день. Проект в целом запускается с --build за секунд 5-10 (2 базы данных, 6 сервисов), не более того. Если запускается дольше, есть вероятность, что Dockerfile не совсем корректно построен. Несколько самых популярных ошибки из нашей практики:
- Сторонние зависимости должны идти в начале докера. Они меняются редко и не будут перестраиваться каждый раз, при изменении кода приложения.
- Копирование файлов, в которых описаны сторонние зависимости должно идти отдельным шагом, что бы при каждом запуске они не ставились заново. В примере ниже, сразу происходит копирование package.json (зависимости для Node.js приложения), а лишь потом происходит установка зависимостей. Если package.json не меняется — то и зависимости не будут ставиться заново.
- На рабочих окружениях, код не копируется в Docker, а добавляется как volume.
FROM node:6.3 EXPOSE 8082 COPY package.json /app/ RUN cd /app && npm install --quiet WORKDIR /app
По поводу сборки у нас подход похожий, но немного на стероидах. Поделюсь этим в отдельной статье.
Уменьшение кирпичиков при развертывании — это как глоток свежего воздуха, морозным зимним утром.
cag01
04.03.2017 16:10У вас весь проект умещается на одном сервере или развертываете на нескольких через swarm mode? Получилось ли со swarm mode автоматизировать build и deploy?
cag01
04.03.2017 18:35Сторонние зависимости должны идти в начале докера. Они меняются редко и не будут перестраиваться каждый раз, при изменении кода приложения.
Например, в случае с гитлабом и при использовании рекомендуемого метода docker-in-docker для runner, то, как я понимаю, перестраиваться будут все равно каждый раз.
6ec_uk
23.02.2017 15:32+2при шаринге активно изменяющихся файлов с хостом куча проблем с правами, никогда не угадаешь от какого пользователя коллега будет разворачивать систему, и если читать файлы из контейнера обычно проблем не составляет, то вот записанные в нём переписать или удалить на хосте обычно требует sudo
Данную проблему решил с помощью запуска докера с параметром --userns-remap=user.
В контейнере процессы запускают из под рута, но файлы созданные в volume имеют права вашего локального пользователя и группы, указанного в ремапинге.
hlogeon
23.02.2017 22:04Может кому поможет, но внезапно наткнулся в reddit на ссылочку
https://nanobox.io/app-deployment-tool/
cag01
26.02.2017 11:34Как поступить, если код приложения должен быть доступен в нескольких контейнерах — nginx, php, cron-php (для крона у меня отдельный).
Стоит ли его включать внутрь (как делаете вы) всех контейнеров или же лучше маунтить внешнюю папку?VolCh
26.02.2017 12:19Я пока остановился на volume_from, то есть один контейнер делаем базовым (я обычно php-fpm том делаю, cli внутри него использую при необходимости), выставляющий нужные для других тома. Но вообще считаю костылём, контейнеры должны быть независимыми, но пока не нашел
красивогоудобного способа собрать, например, nginx контейнер со статикой отдельно от php-fpm контейнера.cag01
26.02.2017 14:51Может стоит тогда в nginx контейнер запихивать тоже вообще весь код приложения, а не только статику?
VolCh
27.02.2017 07:25Не имеет значения особого в плане штатной сборки, в nginx контейнер нужно будет установить php со всеми нужными расширениями, собрать, удалить php. Удалять только php или ещё и основной код — особой разницы нет. Мне не нравится сама идея при сборке nginx-контейнера использовать php. Ну и всякие каталоги /uploads всё равно нужно будет шарить между nginx и php контейнерами.
cag01
27.02.2017 14:21Я имел ввиду, что код приложения целиком запихивать как в nginx, так и в php контейнеры. При этом не комбинировать их. Как вам это решение?
Мне не нравится, что при этом придется думать о синхронизации(чтобы в обеих контейнерах был одинаковый код) и пересоздавать также nginx контейнер при обновлении кода.
Иначе получается, что нельзя код включать внутрь контейнера и надо хранить его в отдельном volume. Но как тогда делать деплой чере докер? Ведь не получится просто обновить php-контейнеры, ибо код в volume.VolCh
27.02.2017 16:48Я это понял. Но чтобы его запихнуть нужно, как правило, хотя бы composer install запустить, а это значит установить php и composer в nginx контейнер, если следовать классическому подходу. А поскольку ни php, ни php-код для nginx-контейнера не нужны, то логично их удалить.
В принципе есть много разных вариантов сборки контейнеров только с тем, что им нужно и без шаринга между собой, но вот не нашёл ни одного, который бы работал нормально с docker-compose — он рассчитан только на сборку образов с помощью docker build, который рассчитан только на копирование файлов из контекста :(
cag01
27.02.2017 17:55Может тогда правильно было бы так:
1. На сервере, на котором происходит сборка (или же в CI) установить php, composer
2. clone из репозитори
3. composer install. В итоге мы имеем проект целиком, но еще не в контейнерах
4. Строим контейнеры через build. Индивидуально включая в каждый из них лишь те файлы, которые нужны контейнеру.
5. Делаем ролаут контейнеров nginx(со статикой проекта), php и т.п.
P.S. Единственная проблема, что при ролауде nginx оборвутся соединения из-за перезапуска. Как её решить?VolCh
27.02.2017 18:24Ну совсем правильно тогда так:
- Делаем билд-контейнер с php, composer,… Пользователь и прочее строго синхронизировано с целевыми контейнерами, чтобы не возникло проблем с правами.
Делать грэйсфул стоп контейнеров, обязательно перед ними балансер и минимум два контейнера, из обновление разнести по времени достаточным для стопа и запуска.
hlogeon
26.02.2017 15:36Лично я начинал с volumes_from подхода, но в итоге отказался от него из-за сопутствующих трудностей деплоя в пользу включения внутрь контейнера в качестве volume
samizdam
Спасибо за перевод.
Действительно docker-compose незаменимый инструмент, который очень естественно вписывается в концепции докер и упрощает жизнь.
Если начинаешь докеризировать приложение, то логично разнести сервисы по отдельным образам / контейнерам. И вот тут композ очень просто решает большинство задач по запуску / конфигурации всего зоопарка.