Согласно данным, которые представил на Dockercon 2016 CEO компании Docker Бен Го?луб (Ben Golub), количество работающих в контейнерах Docker приложений за последние два года выросло на 3100%. Docker обеспечивает функционирование 460 тысяч приложений по всему миру. Это невероятно!
Если вы еще не начали использовать Docker, прочтите этот впечатляющий документ о его внедрении. Docker изменил подход к созданию приложений и стал крайне важным инструментом для разработчиков и DevOps-специалистов. Эта статья рассчитана на тех, кто уже использует Docker, и призвана открыть еще одну причину, по которой стоит продолжать это делать.
Мы бы хотели поделиться своим опытом использования docker-compose в больших проектах. Применив этот инструмент для автоматизации задач, связанных с разработкой, тестированием и конфигурированием, мы за несколько простых шагов смогли сделать нашу команду более эффективной и сфокусироваться непосредственно на разработке продукта.
Проблема
В начале карьеры, когда я был еще молодым разработчиком на c# и asp.net, развертывание окружения для разработки было непростой задачей. Требовалось установить базы данных и необходимые для работы приложения инструменты. При этом конфигурационные файлы должны были быть изменены таким образом, чтобы соответствовать настройкам локальной машины. Приходилось прописывать порты, пути к локальным директориям с обновлениями и так далее. Эти шаги обычно были плохо документированы, поэтому на запуск среды разработки уходило огромное количество времени.
Многие продукты в начале своего развития не отличаются сложностью, но по мере реализации новых функций разобраться с ними становится все труднее. В них добавляются новые инструменты и подсистемы, такие как дополнительные базы данных и очереди сообщений. Из-за растущей популярности микросервисов монолитные монстры больших приложений все чаще дробятся на много частей. Подобные изменения обычно требуют участия всей команды, работающей над проектом. Разработчик, который вносит изменения, ломающие локальные окружения, обычно пишет длинные письма со списком необходимых для настройки шагов. Вспоминается случай, когда один работающий за океаном специалист провел серьезное изменение структуры продукта, написал письмо с инструкциями по восстановлению работоспособности локальных окружений и пошел спать. Думаю, вы догадались, что случилось дальше. Правильно: он забыл упомянуть несколько важных моментов. В итоге бо?льшая часть команды потеряла следующий рабочий день в попытках заставить работать обновленный код в своих локальных рабочих средах.
Разработчики очень (не) любят писать документацию, и некоторые шаги по запуску проекта часто хранятся исключительно у них в голове. В итоге настройка рабочего окружения с нуля становится нетривиальной задачей, особенно для новичков.
Как и любой инженер, я стремлюсь автоматизировать все вокруг. Я убежден, что запуск, тестирование и развертывание приложения должны выполняться за один шаг. Это позволяет команде фокусироваться на действительно важных вещах: разработке и улучшении продукта. Десять лет назад автоматизировать эти задачи было намного сложнее, чем сейчас. Теперь это может и должен делать каждый, и чем раньше начать, тем лучше.
Быстрый старт с docker-compose
Docker-compose — это простой инструмент, который позволяет запустить несколько докер-контейнеров одной командой. Перед тем как окунуться в детали, я должен рассказать о структуре проекта. Мы используем monorepo, и кодовая база каждого сервиса (веб-приложение, API, фоновые обработчики) хранится в своей корневой директории. У каждого сервиса есть описывающий его зависимости Docker-файл. Пример такой структуры можно увидеть в нашем демонстрационном проекте.
Давайте начнем с автоматизации простого приложения, которое зависит от MongoDB и небольшого сервиса на Node.JS. Конфигурация для docker-compose находится в файле docker-compose.yml
, который обычно помещается в корневую директорию проекта.
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, но все же давайте обсудим некоторые детали:
context: ./web
— таким образом указывается путь к исходному коду сервиса в рамках monorepo.dockerfile: Dockerfile.dev
— для окружений разработки мы используем отдельный Dockerfile.dev. В production исходный код копируется прямо в контейнер, а для разработки подключается в виде тома. Поэтому нет необходимости заново создавать контейнер при каждом изменении кода.volumes: - "./web/src:/web/src"
— таким образом каталог с кодом добавляется в docker в виде тома.- Docker-compose автоматически связывает контейнеры друг с другом, поэтому, например, веб-сервис может получить доступ к mongodb по имени:
mongodb://mongo:27017
Всегда используйте аргумент --build
По умолчанию, если контейнеры уже есть на хосте, docker-compose up
их не пересоздает. Для принудительного выполнения этой операции используется аргумент --build
. Это необходимо, когда меняются сторонние зависимости или сам Docker-файл. Мы приняли за правило всегда выполнять 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-страницей, не нужен проект целиком, достаточно лишь самой landing-страницы.
Ненужные логи в >/dev/null
Некоторые программы генерируют слишком много логов. Эта информация в большинстве случаев бесполезна и только отвлекает внимание. В нашем демонстрационном репозитории мы выключили логи MongoDB, установив драйвер журнала в 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.yml
в текущей директории.
В некоторых случаях (мы поговорим об этом чуть позже) может понадобиться несколько файлов docker-compose.yml
. Для подключения другого файла конфигурации может быть использован аргумент --file
:
docker-compose --file docker-compose.local-tests.yml up
Так зачем же нужны несколько файлов конфигурации? В первую очередь для разбиения составного проекта на несколько подпроектов. Радует, что сервисы из разных compose-файлов все равно могут быть связаны. Например, вы можете поместить в один файл docker-compose контейнеры, связанные с инфраструктурой (базы данных, очереди и т. д.), а в другой — контейнеры, связанные с приложениями.
Тестирование
Мы используем различные виды тестирования: unit, integrational, ui, linting. Для каждого сервиса разработан отдельный набор тестов. Например, интеграционные и UI-тесты требуют для запуска api- и web-сервисы.
Сначала мы думали, что лучше выполнять тесты каждый раз, когда запускается основной compose-файл, но вскоре выяснили, что это отнимает много времени. В некоторых случаях нам нужно было иметь возможность запускать конкретные тесты. Для этого был создан отдельный 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"
Наш compose-файл с тестами зависит от основного docker-compose-файла. Интеграционные тесты подключаются к development-версии api
, UI-тесты — к web frontend
. Тестовый compose-файл лишь запускает контейнеры, созданные в основном docker-compose-файле. Если нужно запустить тесты только для одного сервиса, можно использовать частичный запуск:
docker-compose --file docker-compose.local-tests.yml up api-tests
Эта команда запустит только тесты для api
.
Префиксы имен контейнеров
По умолчанию всем контейнерам, запущенным с помощью docker-compose, присваивается префикс в виде имени родительской директории. Имя директории в различных средах разработки может меняться. Из-за этого Docker-compose-файлы с тестами, о которых мы говорили ранее, могут перестать работать. Мы используем префикс (app_
) для контейнеров в основном файле docker-compose. Для согласованной работы конфигурации в различных средах мы создали специальный .env-файл в директории, в которой запускаем docker-compose:
COMPOSE_PROJECT_NAME=app
Таким образом можно добиться того, что контейнерам будут присваиваться одинаковые префиксы во всех окружениях вне зависимости от имени родительской директории.
Заключение
Docker-compose — это полезный и гибкий инструмент для запуска ПО, используемого для работы над проектами.
Когда к нам приходят новые разработчики, мы обычно в первый же день даем им задачу на внедрение в production простой функции или исправления ошибки. Наше руководство по началу работы выглядит примерно так:
1) установить Docker и Docker-compose,
2) скопировать репозиторий GitHub,
3) выполнить в терминале команду ./bin/start.sh
.
Чтобы лучше понять изложенные в этой статье концепции, рекомендуем посмотреть демонстрационный проект, размещенный на GitHub. Делитесь своим опытом и задавайте вопросы.
Надеемся, вы нашли эту статью полезной и полученная информация поможет сделать ваши проекты лучше :)
Оригинал: Fully automated development environment with docker-compose
Комментарии (31)
ideological
18.04.2017 13:00Посоветуйте пожалуйста.
Я делаю сайты на nginx+php7-fpm, mariadb, sphinx. Мне docker как-то может помочь?
Как перенести список пакетов с VPS на Debian на другой такой же сервер. Ну чтобы на другом сервере поставились такие же пакеты с такой же конфигурацией, такой софт есть?de1m
18.04.2017 13:40Докер для этого идеален, сделали образ и потом с него запускаете контейнер на любом сервере, где установлен докер.
VolCh
18.04.2017 13:54Вот так сходу советовать докер на продакшене я бы не стал. Больше года использую для разработки, но на продакшен без админов, готовых взять на себя администрирование, выкладывать не хочется.
de1m
18.04.2017 14:09Ну так я и есть админ, причём я «ленивый» админ, поэтому уже больше двух лет использую докер где только можно. Вот небольшой список, что у нас крутится в контейнерах:
bind
icinga2
icinga2 docker monitoring — я тут недавно писал про это статью
git
horde
keybox
mariadb
forum
backup(nodebackup) — это для бэкапа контейнеров.
postgrel
proxy
vpn
Cписко несколько больше, но я его не могу публиковать плюс некоторые вещи повторяются для разных серверов
VolCh
18.04.2017 13:44Для локальной разработки точно поможет, не надо будет ничего, даже пхп, устанавливать локально (кроме самого докера), любые целевые версии можно использовать одновременно дл янескольких проектов без шаманста, конфиги править без sudo и прочие плюшки.
Если на продакшене докер запускать не хочется или не можется, то крайне желательно иметь скрипты разворачивания продакшена с нуля. Это могут быть кофиги утилит систем типа ansible или puppet, а могут быть простые bash-скрипты (как у нас). Главное, держать их в синхронизации с файлами докера.
de1m
18.04.2017 13:46Мы похоже делаем.
У нас есть гит репозиторий, где находятся все конфигурации для контейнеров. Для каждого контейнера своя папка, в ней лежат два файла — docker-compose.yml и env. Во втором (env) написано на каком сервере должен разворачиватся контейнер. Когда делается комит, то git-runner раскладывает папки по серверам.
Также в этом репозитории лежит Vagrantfile с которого можно запустить виртуальную машину и уже в ней что-то потестировать.VolCh
18.04.2017 13:56А в первом что? Как контейнеры друг с другом связываете?
de1m
18.04.2017 14:05В смысле что? Там конфигурация контейнера/ов. Они (папки) больше логически поделены, чем на контейнеры.
К примеру docker-compose.yml для redmine.
redmine: image: redmine:3.2 hostname: redmine.example.local links: - mysql:mysql ports: - "192.168.40.203:3000:3000" # http # - "192.168.33.10:3000:3000" # vagrant-http volumes: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro - /a/data/redmine.intern/redmine:/usr/src/redmine/files - ./config/configuration.yml:/usr/src/redmine/config/configuration.yml restart: unless-stopped labels: nextfullbackup: "1M" noffullbackup: "2" backup: "/a/data/redmine.intern" strategy: "off" mysql: image: mysql:5.7 ports: - "192.168.40.203:3306:3306" # mysql-database # - "192.168.33.10:3306:3306" # vagrant-mysql-database volumes: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro - /a/data/redmine.intern/db:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=PASS - MYSQL_DATABASE=redmine command: mysqld --lower_case_table_names="1" restart: unless-stopped
«Labels» нужны для бэкапа, через них моя программа находит, какие контейнеры надо сохранять.VolCh
18.04.2017 14:30Они (папки) больше логически поделены, чем на контейнеры.
Удивило как контейнеры, у каждого из которого свой докер-композ, общаются друг с другом. Вроде без хардкода в рамках только одного (без учета каскадов) докер-композ можно общаться.
de1m
18.04.2017 14:36Можно ещё сделать сеть и к этой сети подключить два контейнера из разных docker-compose файлов.
Это будет в первом:
networks: - dbnet external_links: - mariadb
А это во втором:
networks: default: aliases: - mariadb
Для этого нужна вторая версия docker-compose файла
version: "2"
Amet13
18.04.2017 16:15Есть смысл еще использовать параметр -d, для запуска в background-режиме, bash-сессия может прерваться, а с ней и запущенный docker-compose
flastir
18.04.2017 21:01+1Docker в продакшене с проброской портов «27100:27017»? Попробуйте фаерволом закрыть все порты, кроме нужных, и подключиться извне к порту 27100. Будете удивлены. Чтобы закрыть порт от посторонних глаз, я делал проброску портов так: «172.17.0.1:27017:27017», где 172.17.0.1 — интерфейс docker0.
VolCh
18.04.2017 21:29Название поста: "Полная автоматизация среды разработки с помощью docker-compose". Ни слова о продакшене, чисто для локального запуска всё.
ALexhha
19.04.2017 11:02Вот небольшой список, что у нас крутится в контейнерах:
bind
git
а какой глубокий смысл запускать git/bind в докере? Вы их часто обновляете или вам часто приходится разворачивать соотв окружения на других серверах? Можно use-case так сказать, преимуществ использования докер конкретно в этих случаях?
Вообще заметил в последняя время плохую тенденцию — запускаем все в докере ибо это стильно/модно/молодежно. Как в свое время была тенденция — переписываем все gnu утилиты на go.ggo
19.04.2017 11:59Докер — это не только способ доставки приложений до железа, но и способ документирования где и что подкручено (есть, конечно, исключения, когда это непрозрачно, например, образ выкатывается из tar-архива).
Это не отменяет того факта, что прежде чем что-то (хоть докер, хоть не докер) использовать нужно оценивать потенциальную выгоду и затраты.ALexhha
19.04.2017 13:12Докер — это не только способ доставки приложений до железа, но и способ документирования где и что подкручено (есть, конечно, исключения, когда это непрозрачно, например, образ выкатывается из tar-архива).
для этого вроде как есть системы управления конфигурациями — chef, puppet, ansible,…
Просто стало интересно, зачем запускать тот же bind и уж тем более git в докере, какие преимущества мы получаем?de1m
19.04.2017 14:04Я напишу только три плюса, но наверное их больше(это всё со стороны администратора).
1. В обоих случаях у меня получается чёткое разделение на систему и данные, то есть, когда я делаю бэкапы, то я знаю, что мне нужна только одна, ну или несколько определённых папок и всё.
Когда у меня к примеру сгорит сервер, то я просто беру новый ставлю на него линукс и докер, заливаю бэкап (один только) и всё, у меня снова всё работает, вообще всё работает что было.
2. Здесь только про гит — это обновления. Когда выходит новая версия, я просто скачиваю новый образ, запускаю с него контейнер и у меня уже актуальная версия.
3. Касается всех — это мониторинг. То есть у меня сделано так, что я вижу статус каждого контейнера, а так как каждый контейнер это один процес. То я соответственно вижу, если не работает контейнер, то это значит, что не работает какой-то сервис.
Причём каждый контейнер автоматически регестрируется в мониторинге и мне ничего не надо делать. Раньше без докера было по другому и было сложнее, в винде всё ещё приходится по старинке делать.
Вообще должен сказать, что я уже больше двух лет работаю с докером и пока единственное место, где я бы не стал использовать докер, это работа со сложными настройками сетей (типа vlan, vxlan итд.)
Я себе очень мало случаев могу представить, где докер что-то сделает хуже.
Мы на фирме у нас стараемся по возможности всё на докер и линукс перевести, но из-за виндовсы не очень получается, хотя его у нас мало.VolCh
19.04.2017 14:16Я себе очень мало случаев могу представить, где докер что-то сделает хуже.
В последнее время реже, но раньше, год-полтора назад, очень часто попадалась на глаза рекомендация не использовать докер для важных стейтфулл сервисов, в частности для операционных СУБД, особенно кластеризованных, пускай и мастер-слэйв режимах. В дев- и тест-средах поднималась в контейнере, а для прода использовался обычный инстанс. Просто болезни молодости были или всё же шансы, что что-то пойдёт не так и данные пострадают выше?
ALexhha
19.04.2017 14:371. Опять таки — для этого есть SCM. Ведь все равно вам где то надо хранить персистентные данные при использовании докера.
2. А в случае не Docker вам достаточно yum update/apt-get update или что вы там используете. Единственный плюс который я вижу — ваш дистрибутив не поддерживает нужной вам версии ПО, а собирать свой rpm/deb накладно и/или невозможно. Но это довольно таки редкий случай.
3. Что касается регистрации возможно, но в том же Zabbix отлично работает auto discovery и без всяких докеров
То есть у меня сделано так, что я вижу статус каждого контейнера, а так как каждый контейнер это один процес. То я соответственно вижу, если не работает контейнер, то это значит, что не работает какой-то сервис.
я тоже самое вижу и без докера. Если я мониторю smtp, pop3, imap4, mysql на одном сервере, то когда перестает работать определенный сервис я это вижу. Или я не понял, что вы хотели сказать.de1m
19.04.2017 18:30мне не охота спорить, поэтому вы правы, а я, соответственно, нет.
Поэтому я пойду посыплю голову пеплом и верну все мои серверы на четыре года назад.ALexhha
19.04.2017 18:38да никто не спорит, просто хотел понять в чем профит, мб я что то упускаю или чего то не знаю о докере ;)
VolCh
20.04.2017 08:59Докер из коробки показывает состояние запущенных процессов. Если какой-то процесс даже сегфолтится, то он не пропадает из списка, а переходит в состояние exited (по памяти). То есть грубый мониторинг можно сделать по простой формуле "все процессы должны быть в состоянии running" ничего не зная о том, что это за процессы.
de1m
20.04.2017 13:43Я исходил из того, что если к примеру процес «nginx» в контейнере остановится, то соответственно остановится и сам контейнер. Соответственно, если я вижу, что контейнер не работает, то тогда и моя страница или прокси тоже не работает.
Потом чтобы каждый контейнер не прописывать в мониторинге, я сделал программу (nodejs) которая во-первых сравнивает наличее контейнеров на докер хосте и в мониторинге и удаляет, либо добавляет лишнее. Во-вторых смотрит все ли они работают.VolCh
20.04.2017 14:01У нас мониторинг для дев-сервера на баше с грепом написан и крону запускается, тупо если больше минуты один и тот же контейнер не Up… то письмо шлется. Даже нет списка мониторинга
gaf
19.04.2017 20:52Позвольте вопрос. Какие данные и как вы бэкапите? Ведь простое копирование файлов в сторону, как правило, плохая идея из-за различных кешей-буферов и прочего.
de1m
19.04.2017 22:26Я использую для этого nodebackup, специально писал для удобного сохранения данных из контейнеров, а также нормальных серверов. Есть также готовый докер образ, которому надо только подсунуть конфигурацию, crontab файл и ssh key. Там под капотом используется duplicity.
Пример (посмотрите выше, там где пример для redmine):
У меня для redmine есть один docker-compose.yml файл в нем описаны два контейнера, причём один из них это СУБД, то есть когда это всё работает, сохранять не желательно. Надо выключить контейнер, также контейнеры связаны между собой.
Вот эта часть в docker-compose.yaml говорит, что нужет бэкап
labels: nextfullbackup: "1M" noffullbackup: "2" backup: "/a/data/redmine.intern" strategy: "off"
Nodebackup может через docker.sock посмотреть какой контейнер надо сохранять. Если в «strategy:off», то это означает, что перед тем, как начать делать бэкап, то нужно выключить контейнеры, также тот, который слинкован. После этого он смотрет на путь в «backup» и тут уже включается duplicity. После этого контейнер снова стартует.
Если посмотрите выше, то видно, что эта папка примонтирована в оба контейнера.
Есть варианты, где контейнер не надо выключать, к примеру bind. Тогда соответственно «strategy: on». Там на самом деле ещё много можно опций писать.
Есть ещё один вариант, когда к примеру большая СУБД, но на долго контейнер нельзя выключать. Тогда используются возможности файловой системы (btrfs или сephfs) и делается снапшот.
То есть nodebackup выключает контейнер, потом снапшот, потом контейнер снова включается, а дальше nodebackup спокойно делает бекап со снапшота, после чего его стирает.
PS Извеняюсь, что так растёкся мыслью, просто я вроде как написал хорошую програму для докер контейнеров, а никто не знает. ))gaf
19.04.2017 23:12Спасибо за развернутый ответ. Я интересовался, потому что сам не смог найти ответ на достаточно простой вопрос: как сложить данные в архив, чтоб при этом целостность данных не была нарушена. В итоге, для файлов, написал свой велосипед, который делает снапшот и запаковывает данные в tar-архив. Плюсом к этому logrotate, который занимается ротацией архивов.
duplocity — вещь хорошая, но сама делать снапшоты не умеет.
Велосипед, если кому интересно
ggo
19.04.2017 14:21Тут наверно правильнее будет сказать, что делает docker лучше чем условный chef.
Строго говоря, критерий лучшести каждый определяет самостоятельно. И тогда кому-то будет удобнее docker, кому-то условный chef.
Но в чем безусловно плюс, это более формальное разделение ответственности между артефактом, отвечающим за бизнес-логику, и артефактом, отвечающим за обслуживание.
Например за условный bind отвечает одна команда.
За железо — другая.
За сбор логов — третья.
У каждой свои инструменты — docker, chef, etc. У каждой — свои репозитарии (vcs). Взаимодействие осуществляется по декларативно прописанным интерфейсам: файлы, порты, и т.д.
dfuse
21.04.2017 03:23Предположим есть builder контейнер, который строится и потом запускается через run, а результат его работы надо положить в другой pro контейнер. Ведь не будешь же класть весь NodeJS со всеми модулями из билдера в продакшен образ, где только Nginx нужен. Что то типа этого тикета и схожий кейс там же.
Может ли compose помочь с этим как-то?
VolCh
Для тех, кто, как мы, не планирует в продакшен докер запускать, но в разработке хочется попробовать: оно круто, но нужно сразу заботиться о соответствии файлов Docker и Docker-compose сценариям развёртывания на приемочной(препрод) и промышленной(прод) средах, чтобы не тратить потом кучу времени на поиски причин "ничего не знаю, у меня же работает". Сразу надо взять за правило, что каждое закомиченное изменение файлов докера должно сопровождаться соответствующим изменением сценариев развёртывания, причём препрод и прод сценарии должны отличаться только именами/адресами хостов.
К хорошему (докер для дев- и тест- сред) быстро привыкаешь, но если полного соответствия нет, если скрипты развёртывания на "железо" без контейнеров отличаются от файлов докера и ко, то после пары-тройки больших коммитов в них разработка под докером приостанавливается на неопределенное время.