В этой статье мы поделимся опытом автоматизации запуска, тестирования и конфигурации больших проектов с использованием 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:
COMPOSE_PROJECT_NAME=app
Таким образом, префикс будет одинаковым во всех рабочих окружениях.
Заключение
Docker-compose это очень полезный и гибкий способ автоматизации запуска проектов.
Когда новые разработчики добавляются к нам в команду, мы даем им небольшую задачу, которую они должны закончить к концу первого рабочего дня. Каждый, кто присоединялся к нашей команде, справился с этим и был самым счастливым человеком на Земле. Уже с первых минут новые разработчики могут сфокусироваться на важных задачах и не тратить времени на то, чтобы запустить проект. Наша документация для старта проекта состоит из трех пунктов:
- Установить Docker и Docker-compose
- Склонировать репозиторий
- Запустить в терминале
./bin/start.sh
Для того, чтобы Вам было проще понять эту статью, у нас есть пример проекта на Github. Делитесь вашим опытом и задавайте вопросы.
Надеемся, что статья была полезной и поможет сделать Ваш проект лучше :)
Версию на английском, можно почитать здесь.
Автор: AndrewOrsich