Глава 4. Декларативное описание нашего прикладного приложения с помощью Docker Compose
Мы уже видели как запускать контейнеры при помощи соответствующей команды docker run
.
Однако это ограничивает нас запуском одного отдельного контейнера за раз. Хотя это и великолепно для единичных
задач, у нас, разработчиков, наши приложения обычно создаются из множества компонентов, или, в терминологии Docker,
служб. Например, дополнительно к нашему серверу Rails нам обычно требуется по
крайней мере некая база данных.
Раз уж мы думаем о неком приложении в целом, запуск контейнеров при помощи docker run
становится слишком громоздким. Нам понадобится некий иной инструмент верхнего уровня, который позволит нам координировать
контейнеры и управлять ими для различных имеющихся служб, составляющих наше приложение.
Введём Docker Compose.
Docker Compose - или для краткости просто Compose (Компоновщик) - является инструментом для управления неким приложением, которому требуется какое- то количество работающих совместно различных контейнеров. Compose является объявлениями: вы определяете каждую часть своего приложения - именуемую как служба - и Compose обрабатывает работу по предоставлению гарантий того что правильные контейнеры исполняются когда и как вам это требуется. Он также управляет созданием и удалением ресурсов, необходимых данному прикладному приложению. Например, он создаёт некую обособленную частную сетевую среду для вашего приложения, снабжая вас некоторой предсказуемой изолированной средой. Как мы увидим в Части 2, Вперёд в производство, он также играет некую ключевую роль в том как мы развёртываем и масштабируем приложения с помощью Docker.
Compose разработан с целью вооружения разработчиков на уме. Он позволяет нам масштабировать свои приложения в терминах
знакомых нам понятий - например "запускаем свою веб службу" или "останавливаем базу данных".
Всё это сильно контрастирует с командами docker run
нижнего уровня, которые мы уже
видели, где сложнее обнаружить тот контекст, который мы пытаемся достичь.
Однако, прежде чем мы сможем заставить Compose делать наши ставки, нам вначале требуется описать своё приложение, создав некий
файл docker-compose.yml
. Он не подменяет необходимость в
Dockerfile
- чертежах для создания контейнеров - но описывает какие образы требуются
нашему приложению и как они согласованно запускаются.
Вот некий первоначальный docker-compose.yml
:
1: version: '3'
2:
3: services:
4:
5: web:
6: build: .
7: ports:
8: - "3000:3000"
Некий файл Компоновки всегда начинается с какого- то номера версии (строка 1), которая определяет тот формат файла, который будет применяться. Это помогает гарантировать что данное прикладное приложение продолжит исполняться ожидаемым образом по мере добавления в Compose новых свойств, или же при прерывающих изменениях. Мы используем версию 3 - самую последнюю основную версию на момент написания.
Далее у нас имеется некая коллекция с названием services
(строка 3),
которая применяется для группирования составных частей некоторой отдельной службы - которую мы выбрали именуемой как
web
- для её представления (строка 5). Мы увидим добавление иной службы в
Главе 5, За рамками прикладного приложения: добавляем Redis.
Под web
встраиваются его различные параметры настройки (строки 6-8).
Самая первая из них:
build: .
сообщает Compose где обнаруживать тот Dockerfile
, который следует применять
для построения нашего образа. Определяемый нами путь является относительным для самого файла
docker-compose.yml
. В данном случае он расположен в том же самом каталоге.
Далее мы переходим к строкам 7-8:
ports:
- "3000:3000"
Это эквивалентно значению параметра -p 3000:3000
, который мы определяли в
своей команде docker run
. Если вы вспомните, он применялся для установления
соответствия порта 3000 порту 3000 в нашей локальной машине. Он требуется для того чтобы сделать наше приложение
Rails доступным из нашей локальной машины.
Вооружившись docker-compose.yml
, Compose теперь настроен на управление
нашим приложением. Однако прежде чем мы запустим своё приложение, вначале слегка слегка настроим своё домашнее
хозяйство. По умолчанию Ruby выполняет буферизацию вывода в stdout
, что
недостаточно хорошо играет с Compose.
Давайте исправим это отключив буферизацию вывода Ruby.
В самом верху вашего файла config/boot.rb
добавьте следующую строку:
$stdout.sync = true
Теперь мы готовы к запуску нашего приложения. Вместо длинной команды docker run
теперь мы можем использовать:
$ docker-compose up
Прежде чем мы пройдёмчя по получаемому выводу, давайте обсудим что делает эта команда.
Когда вы запускаете docker-compose up
, Compose проверяет что все необходимые
ресурсы были настроены, создавая те, которые отсутствуют, перед тем как запустить контейнер для каждой службы.
В частности, он:
-
Создаёт некую обособленную сеть только для данного приложения
-
Создаёт все не локальные монтируемые тома определённые для данного приложения (у нас их пока нет - дополнительно об этом в Главе 6, Глава 6. Добавляем базу данных: Postgres)
-
Собирает некий образ для всех служб при помощи директивы
build
-
Создаёт некий контейнер для каждой службы
-
Запускает по контейнеру для сдужбы
Достаточно впечатляюще для отдельной команды.
Если мы вернёмся к выводу данной команды, то мы увидим многое из того что происходит. Сначала создаётся сеть:
Creating network "myapp_default" with the default driver
По соглашению Compose устанавливает название для данной сети
<appname>_default
, где appname
где имя приложения по умолчанию берётся из того каталога, в котором он находится.
Далее мы строим необходимый образ для своей службы web
:
Building web
Step 1/8 : FROM ruby:2.6
---> f28a9e1d0449
Step 2/8 : LABEL maintainer="rob@DockerForRailsDevelopers.com"
---> Using cache
---> d69ea7d90f89
Step 3/8 : RUN apt-get update -yqq && apt-get install -yqq --no-install-
recommends nodejs
---> Using cache
---> 463750079bef
...
Step 8/8 : CMD ["bin/rails", "s", "-b", "0.0.0.0"]
---> Running in b11e989011fc
Removing intermediate container b11e989011fc
---> a18b3079c84b
Successfully built a18b3079c84b
Compose определяет название и версию для данного образа устанавливая его тег:
Successfully tagged myapp_web:latest
применяя соглашение <appname>_<service_name>:latest
, вновь
ссылаясь на appname
из содержащего её каталога. В нашем случае это
превращается в myapp_web:latest
.
Вы можете убедиться что был создан образ myapp_web
исполнив следующее в
отдельном терминальном окне:
$ docker images
Вы должны квидеть его в одной из строк получаемого вывода:
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp_web latest a18b3079c84b About a minute ago 1.01GB
...
Compose будет строить образы только если их нет, что может быть по причине того что это первый раз когда вы запускаете
docker-compose up
, либо потому что вы удалили его. Это важный момент: именно
вы отвечает за построение своих образов по мере необходимости (смотрите Повторное
построение наших образов); на самом деле, Compose напоминает нам об этом в своём выводе:
WARNING: Image for service web was built because it did not already exist.
To rebuild this image you must use `docker-compose build` or `docker-compose
up --build`.
Предупреждение: Образ для веб службы был построен потому что его ещё не было.
Для повторного его построения вам следует применить `docker-compose build` или `docker-compose up --build`.
Затем Compose создаёт и запускает некий обособленный контейнер для нашей службы web
на основе того образа, что мы только что создали. Он будет именовать контейнеры применяя формат
<appname>_<service name>_<n>
:
Creating myapp_web_1 ... done
В процессе, называемом присоединением (attaching), Compose далее подключает наши
локальные потоки ввода/ вывода (stdin
,
stdout
и stderr
) к данному запускаемому
контейнеру, поэтому мы можем видеть его вывод:
Attaching to myapp_web_1
В качестве результата мы наблюдаем запуск своего сервера Rails внутри данного контейнера:
web_1 | => Booting Puma
web_1 | => Rails 5.2.2 application starting in development
web_1 | => Run `rails server -h` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
web_1 | * Min threads: 5, max threads: 5
web_1 | * Environment: development
web_1 | * Listening on tcp://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
Наш сервер Rails запускается благодаря той инструкции CMD
по умолчанию, которую
мы установили в разделе Команда по умолчанию. Мы можем
иметь установленной соответствующую команду непосредственно в docker-compose.yml
за счёт настройки параметра command
для web
- это приведёт к перекрытию действия той инструкции CMD
, которая определена в
его Dockerfile
.
Проследуте вперёд и убедитесь что ваше приложение запущено: посетите в своём браузере
http://localhost:3000
. Вы должны увидеть вновь знакомую вам страницу
приветствия Rails.
Великолепно! Наш docker-compose.yml
работает и всё первоклассно.
После этого вы можете прекратить Compose нажав Ctrl-C
; вы должны увидеть
что ваши контейнеры будут остановлены.
Контейнеры не останавливаются аккуратно | |
---|---|
К сожалению у Compose периодически возникает проблема, за проявлением которой вам следует наблюдать. Порой при
прекращении Compose с помощью |
В исполнении этого сейчас нет необходимости, но если вы не желаете заботиться о просмотре вывода своего контейнера,
мы можем запускать свои контейнеры в режиме отключённых (detached) определяя
соответствующий параметр -d
. Это запускает данное приложение в фоновом режиме
{Прим. пер.: в качестве демона} и возвращает вам приглашение вашей оболочки.
$ docker-compose up -d
Starting myapp_web_46768de21d89 ... done
Следует заметить, тем не менее, что Rails может потребоваться некоторое время для запуска вашего приложения и установки его доступности.
Проблемы с запуском Rails? | |
---|---|
Если вы столкнулись с некой ошибкой своего сревера, который сообщает нечто подобное "A server is already
running" (Сервер уже запущен), вы столкнулись с некой ошибкой в Compose. На данный момент просто удалите имеющийся
|
Прежде чем мы покинем свой docker-compose.yml
, давайте выполним некое
небольшое добавление.
Мы уже видели как монтировать некий локальный каталог внутри некого контейнера при помощи
docker run
с применением параметра -v
-
мы это делали в разделе Генерация нового прикладного приложения Rails
без установленного Ruby с тем, чтобы необходимые вырабатываемые внутри этого контейнера файлы проекта Rails были
доступными в нашей локальной машине.
Некий монтируемый том представляет какую- то файловую систему, которая совместно используется вашей локальной машиной и самим контейнером. Файлы в таком монтируемом томе синхронизуются в обоих направлениях между вашей локальной машиной и самим контейнером. По этой причине некое монтирование локального тома может делать для вас возможной локальную разработку и при этом иметь данный сервер Rails исполняемым в самом контейнере и автоматически подхватывающим такие изменения файлов без перезапуска - в точности как мы и привыкли это делать.
На этот раз мы собираемся настроить такой локально монтироуемый том при помощи Compose без применения
docker run
. В своей команде docker run
для монтирования своего текущего локального каталога внутри соответствующего контейнера по
/usr/src/app
, мы применяли -v $PWD:/usr/src/app
.
Того же самомого с помощью Compose мы можем достичь добавляя в свой
docker-compose.yml
следующее:
version: '3'
services:
web:
build: .
ports:
- "3000:3000"
» volumes:
» - .:/usr/src/app
Здесь мы определили соответствие своего тома в том свойстве volumes
, которое мы
добавили для своей службы web
. Хотя такая установка соответствия тома очень похожа на
применяемый нами параметр docker run
, имеется одно небольшое отличие. Мы имеем
возможность ссылаться на текущий каталог просто применяя некую точку (.
) вместо
применявшейся ранее переменной среды $PWD
. Compose делает возможной
относительную адресацию подобную указанной на основе того где находится соответствующий
docker-compose.yml
- небольшой чудесный бонус.
Перезапуская своё приложение на этот раз с помощью этого изменения:
$ docker-compose up -d
мы теперь имеем возможность следовать обычному потоку разработки по изменению файлов локально и немедленно наблюдать изменения просто перегружая страницу браузера.
Проблемы с запуском Rails? | |
---|---|
Если вы столкнулись с некой ошибкой своего сревера, который сообщает нечто подобное "A server is already
running" (Сервер уже запущен), вы столкнулись с некой ошибкой в Compose. На данный момент просто удалите имеющийся
|
Достаточно распространённой вещью, которую мы вынуждены делать пока разрабатываем своё приложение, являются останов или запуск различных служб, которые мы создаём. На ммоент мы окунёмся в тонкости контроля, которые Compose предоставляет нам для этого. Прежде чем мы следаем это, однако, будет полезно иметь представление того путешествия, которое совершают контейнеры, от момента создания до того момента когда они более не требуются.
Следующая схема отображает нам некую упрощённую версию жизненного цикла контейнера:
Контейнер вступает в строй с соответствующим состоянием created (созданный).
Он не исполняет никакого кода; он просто сидит и дожидается своего вызова. Когда этот контейнер запускается, он переходит
в состояние running (исполняемого), где он активно выполняет код. Та команда
docker run
, которую мы уже наблюдали, создаёт некий новый контейнер, а заетм
запускает его.
Пребывая в состоянии исполняемого некий контейнер может быть restarted (перезапущен), stopped (остановлен), killed
(уничтожен) или paused (приостановлен). Приостановка контейнера подвешивает все исполняемые процессы с тем, чтобы они
смогли продолжить исполнение (resumed) через какое- то время позднее. Останов контейнера пытается аккуратно остановить
посредством отправки сигнала прекращения (SIGTERM
) в основной процесс внутри этого
контейнера - в случае отказа возвращаясь назад к некому принудительному завершению при помощи сигнала kill
(уничтожения, SIGKILL
). Уничтожение контейнера сразу перескакивает к принудительному
прекращению.
В случае своего останова контейнер переходит в состояние stopped (остановленного) если он останавливается или уничтожается, либо если прекращается сам исполняемый внутри основной процесс. Данное состояние остановленного аналогично состоянию созданного: такой контейнер сидит в нём до тех пор пока не будет вызван снова. Отсюда данный контейнер может быть либо перезапущен, либо если вам более не придётся его применять, удалён из вашей системы. Имея всё это ввиду, давайте рассмотрим как это работает на практике при помощи Compose.
Прежде всего давайте проверим какие контейнеры исполняются в данный момент. Для этого мы воспользуемся командой
ps
:
$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------
myapp_web_1 bin/rails s -b 0.0.0.0 Up 0.0.0.0:3000->3000/tcp
Данное перечисление содержит соответствующее название контейнера, ту команду, которая была применена для его запуска,
его текущее состояние и соответствие его портов. Здесь вы можете видеть контейнер нашего сервера Rails; он всё ещё
запущен с того момента когда мы его исполнили в предыдущий раз docker-compose up -d
(Up
означает что он исполняется).
Если мы теперь намерены остановить свой сервер Rails, нам придётся сделать это при помощи команды
stop
. По умолчанию, эта команда будет применена ко
всем процессам, перечисленным в нашем файле
docker-compose.yml
. Например, для останова всех контейнеров во всём данном приложении
нам бы пришлось исполнить:
$ docker-compose stop
Чтобы указать целью некий определённый процесс, нам придётся определить соответствующее название процесса после предпринимаемого нами действия:
$ docker-compose stop <service_name>
Это может казаться неким спорным вопросом, поскольку в настоящее время web
является единственной определённой нами службой. Однако вскорости мы добавим ещё службы, начиная с
Главе 5, За рамками прикладного приложения: добавляем Redis.
Это обычное явление когда вы желаете указывать целью команды конкретную службу, следовательно будет очень полезно запомнить
данный шаблон - в частности потому что все имеющиеся команды docker-compose
следуют ему.
Давайте двинемся далее и остановим свою службу web
:
$ docker-compose stop web
Stopping myapp_web_1 ... done
Загрузка localhost:3000
в вашем браузере теперь завершится неудачей, а перечисление
всех контейнеров сообщит что ваш сервер Rails был прекращён:
$ docker-compose ps
Name Command State Ports
-----------------------------------------------------
myapp_web_1 bin/rails s -b 0.0.0.0 Exit 1
Имея некий контейнер остановленным, мы имеем возможность запустить его снова при помощи команды
start
:
$ docker-compose start web
Starting web ... done
Имеется также команда restart
, которая будет удобной когда, например, вы
провели некие изменения настроек и желаете чтобы ваш сервер Rails подхватил бы их:
$ docker-compose restart web
Restarting myapp_web_1 ... done
Все рассмотренные нами команды Compose полагаются на лежащие в основе
команды
docker
. Однако мы бы не хотели рассматривать их подробно, так как теперь мы будем
применять Compose. Compose предоставляет нам всю мощность команд нижнего уровня docker
,
но в более простых, сосредоточенных на приложении командах.
Помимо запуска и останова наших служб, имеется ряд дополнительных моментов, которые часто выступают частью нашей повседневной разработки. Мы намерены предпринять тур с небольшими остановками для их отображения, так как они потребуются нам в последующих главах.
Мы уже видели, что docker-compose up
без параметра
-d
подключается к запускаемому им контейнеру и впоследствии отображает его
вывод.Однако существует также некая выделенная команда для просмотра выода контейнера, которая намного удобнее.
Давайте просмотрим журнал регистраций определённого контейнера:
$ docker-compose logs -f web
Вы должны обнаружить некие записи, показывающие что наш сервер Rails был запущен, аналогичные таким:
web_1 | => Booting Puma
web_1 | => Rails 5.2.2 application starting in development
web_1 | => Run `rails server -h` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
web_1 | * Min threads: 5, max threads: 5
web_1 | * Environment: development
web_1 | * Listening on tcp://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
Флаг -f
указывает данной команде следовать имеющемуся выводу - то есть оставлять
соединение подключённым и продолжать добавлять в конец вывода экрана всего что поступает в данный журнал аналогично
команде Unix
tail
.
Для прекращения потока вывода регистрационных записей нажмите Ctrl - C
.
Важно сознавать, что данная команда отображает регистрационный записи самого вывода
контейнера вместо регистрационных записей вашего сервера Rails, которые, по умолчанию, сохраняются в
соответствующем каталоге log/
. Однако, как мы позднее увидим в Части 2, Вперёд в производство,
при использовании Docker принято настраивать Rails на вывод регистрационных записей в
stdout
.
Относительно дополнительных параметров
docker logs
отсылаем вас к
документации Docker.
До сих пор мы запускали свой контейнер web
только применяя установленный
в образе по умолчанию CMD
. Что если нам требуется запустить некую иную команду?
Например, часто мы желаем выполнять такие вещи как миграцию своей базы данных с помощью
db:migrate
, запускать свои проверки, обновлять наши gems или выполнять те
многие команду Rails, которые мы применяем как часть своей разработки. Как нам это делать?
В действительности имеются два различных способа достижения этого, которые мы покажем на неком тривиальном примере:
вывод echo
чего бы то ни было на ваш экран. Не дайте обмануть вас этому простому
примеру; данный подход использует тот же самый механизм, кторый мы будем применять для своих любимых команд
в последующих главах.
Прежде всего мы можем воспользоваться docker run
для запуска некого нового
контейнера для своих одноразовых команд. Мы предоставляем такую команду после названия соответствующей службы, как это
показано ниже, что перекрывает все определённые по умолчанию команды как в нашем файле
docker-compose.yml
, так и в самом Dockerfile
:
$ docker-compose run --rm web echo 'ran a different command'
ran a different command
Данная команда echo
исполнена успешно. Стоит отметить, что когда мы запускаем свой сервер
Rails, наш контейнер прекращается немедленно после исполнения данной команды. Это происходит потому, что данная команда
echo
завершается и возвращает своё состояние выхода, в то время как наш сервер
Rails выполняется в цикле сохраняя своё исполнение пока вы не попросите его остановиться (или если он рухнет). Кроме того,
так как это одноразовая команда, мы воспользовались соответствующим параметром --rm
для удаления этого контейнера по его завершению - в противном случае мы бы заканчивали с кучей дополнительных контейнеров
лежащими там.
Второй способ исполнения одноразовых команд избегает вовсе запуска некого нового контейнера. Вместо этого он полагается
на уже исполняемый контейнер и выполняет соответствующую команду в нём. Это выполняется при помощи соответствующей
команды docker-compose exec
.
Допустим, что наш сервер Rails исполняется, тогда мы можем запустить свой пример
echo
следующим манером:
$ docker-compose exec web echo 'ran a different command'
ran a different command
Хотя это работает только при уже исполняемом контейнере, поскольку новый контейнер не запускается, нам не приходится
помнить об очистке от дополнительных контейнеров или применении соответствующего параметра
--rm
.
Мы можем запросить для себя Compose собрать наши образы вместо применения лежащих в основе команд
docker build
. Это полезно для того чтобы избегать переключения между командами
docker
и docker-compose
, но также и происходит
по той причине, что ваше приложение может содержать множество Dockerfile
для более
чем одной службы; наш файл docker-compose.yml
будет отслеживать какой
Dockerfile
для какой службы применяется.
Для перестроения образа сервера своего приложения Rails, известного Compose в качестве службы
web
вам следует вызвать такую команду:
$ docker-compose build web
Имеется несколько различных причин почему вам может понадобиться выполнить перестроение своего образа. Зачастую это
обусловлено тем что вы обновляете свой Gemfile
и вам требуется переустановить свои
gems (сам Dockerfile
содержит соответствующую командуbundle install
).
Более редко это происходит потому что вам приходится изменять свой Dockerfile
для
установки дополнительных зависимостей. А ещё порой вы пожелаете совместно использовать свой образ и вам потребуется включать
самые последние изменения кода (благодаря соответствующей инструкции Dockerfile COPY
),
как мы это увидим в Части 2, Вперёд в производство.
Как вы можете помнить, когда мы впервые вызвали для своего проекта docker-compose up
,
она создала некую сетевую среду, необходимые тома и все контейнеры, требующиеся для данного приложения. Соответствующая ей
команда docker-compose down
останавливает все запущенные контейнеры и удаляет их
вместе с соответствующими выделенными сетями и томами этого приложения.
Это полезно когда вы работает с неким проектом и желает высвободить пространство, применяемое его ресурсами. Если вы желаете
удалить соответствующие контейнеры этого приложения, также имеется соответствующая этой цели команда
docker-compose up
.
Прдрезка: освобождение от неиспользуемых ресурсов | |
---|---|
Поскольку мы говорим о высвобождаемых ресурсах, имеются также некоторые другие команды, которые помогут нам с этой целью. Раз мы изменили свой Существует целое семейство команд |
Мы представили мощный инструментарий для своего арсенала: Docker Compose. Это действительно команда всё- в- одном для разработки наших прикладных приложений с помощью Docker.
Давайте напомним что мы изучили. В этой главе:
-
Мы представили
docker-compose.yml
и его формат. -
Мы создали свой собственный
docker-compose.yml
для нашего приложения Rails, включая локально монтируемый том, который позволяет нам вживую изменять локальные файлы. -
Мы увидели как раскрутить наше приложение целиком и запустить свой сервер Rails при помощи следующей команды:
$ docker-compose up
-
Мы изучили различные команды для управления своим приложением с помощью Compose:
-
Перечисление исполняемых контейнеров
$ docker-compose ps
-
Управление жизненным циклом контейнера
$ docker-compose [start|stop|kill|restart|pause|unpause|rm] \ <service name>
-
Просмотр регистрационных записей
$ docker-compose logs [-f] <service name>
-
Запуск одноразовых команд в неком новом проходящем контейнере
$ docker-compose run --rm <service name> <some command>
-
Исполнение одноразовых команд в неком имеющемся контейнере
$ docker-compose exec <service name> <some command>
-
Перестроение некого образа
$ docker-compose build <service name>
-
Применяя все эти чудеса Compose мы заменяем большинство своих сложных команд docker run
чем- то более чистым, ясным для понимания и управляемым. Теперь мы можем запустить своё приложение целиком с нуля всего
лишь одной командой:
$ docker-compose up
Ура!
Теперь самое время начать применять Compose для расширения имеющихся возможностей нашего прикладного приложения путём добавления служб.