Глава 10. Некое незначительное брюзжание
Содержание
Ну, это не должно вас смущать.
К сожалению, у Compose имеется пара смущающих проблем. Поскольку вы, вероятно, уже сталкивались с ними в предыдущих главах, либо по мере того как применяли Compose самостоятельно, будет безответственным проигнорировать их. Здесь мы рассмотрим каждую из них по очереди.
К счастью, мы можем обойти первую проблему без особых усилий. Тем не менее, вторая остаётся неразрешённой.
По неким причинам, порой случается так,что в процессе прекращения определённого приложения Compose (нажатием
Ctrl-C
), ваш сервер Rails, похоже, не отключается аккуратно и его
файл server.pid
- который хранится в tmp/pids/
-
не удаляется. Это означает, что когда вы запустите это приложение снова при помощи:
$ docker-compose up
вы можете обнаружить себя в противостоянии со следующей ошибкой в своём выводе:
...
A server is already running. Check /usr/src/app/tmp/pids/server.pid
...
Наличие данного файла pid
заставляет запускаемый сервер предполагать, что
уже имеется некий запущенный сервер, поэтому он не запустится.
Rails сохраняет такой файл server.pid
в
tmp/pids
. Так как мы монтируем свой локальный каталог приложения в самом контейнере,
этот файл попадает в соответствующий каталог tmp/pids/
в нашей локальной машине и присутствует там пока мы не удалим его.
Как решить эту проблему?
Так как мы монтируем свой каталог приложения в запускаемом контейнере, достаточно просто удалить этот файл
server.pid
вручную:
$ rm tmp/pids/server.pid
После выполнения этого наш сервер Rails теперь должен запускаться:
$ docker-compose up
В своём выводе вы должны обнаружить что Rails теперь поднят и запущен. Тем не менее, это не является в действительности решением проблемы, если оно продолжает происходить. К счастью, мы можем найти обходной путь.
Давайте рассмотрим этот обходной путь, а потом обсудим его:
-
Созлайе в своём корне Rails файл
docker-entrypoint.sh
со следующим содержимым: #!/bin/sh set -e if [ -f tmp/pids/server.pid ]; then rm tmp/pids/server.pid fi exec "$@"
-
Сделайте этот файл исполняемым:
$ chmod +x docker-entrypoint.sh
-
В нашем файле
Dockerfile
определите инструкциюENTRYPOINT
добавив следующую строку прямо перед самой последней инструкциейCMD
: ENTRYPOINT ["./docker-entrypoint.sh"]
-
Остановите, соберите заново и перезапустите свою службу
web
: $ docker-compose stop web $ docker-compose build web $ docker-compose up -d web
Итак, что это всё означает?
Некая entrypoint
присоединяется спереди
к нашей команде запуска при старте некого контейнера. В нашем случае мы установили
./docker-entrypoint.sh
в качестве такой ENTRYPOINT
для нашей службы web
. Это означает, что когда мы запускаем некий новый контейнер
web
, вместо того чтобы просто запускать нашу команду по умолчанию:
bin/rails s -b 0.0.0.0
мы в действительности выполним это благодаря своей новой инструкции ENTRYPOINT
:
./docker-entrypoint.sh bin/rails s -b 0.0.0.0
Так как этот сценарий оболочки будет запускаться, нам приходится давать его файлу полномочия
execute
, что мы и сделали на шаге 2.
Что в действительности делает наш сценарий docker-entrypoint.sh
?
На тот сдучай если вы не знакомы с Bash, давайте быстренько пробежимся по нему шаг за шагом (не стесняйтесь перейти далее,
если вам всё понятно).
1: #!/bin/sh
2: set -e
3:
4: if [ -f tmp/pids/server.pid ]; then
5: rm tmp/pids/server.pid
6: fi
7:
8: exec "$@"
Сценарии Bash рекомендуется начинать с set -e
(строка 2) - это приводит к
быстрому отказу самого сценария в случае завершения последующих команд с некой ошибкой (не нулевым состоянием выхода).
Оператор if
в строке 4 выполняет проверку на наличие файла
tmp/pids/server.pid
; если это так, мы удаляем его в строке 5. Это порция очистки в данном
сценарии гарантирует что наш сервер всегда запускается, даже если был оставлен нетронутым файл
server.pid
.
Однако в конечном счёте мы желаем чтобы этот контейнер запускал наш сервер Rails не в данном сценарии Bash. Именно
здесь и приходит на помощь соответствующая команда exec
в строке 8. Она сообщает:
"Замените исполняемый в настоящее время процесс (данный сценарий Bash) исполнением следующей команды" - почти
так, как если бы данный сценарий никогда не существовал. Но какую программу запускает exec
?
Значение "$@"
означает "все аргументы, предоставленные данному
сценарию Bash, что в нашем случае является bin/rails s -b 0.0.0.0
. Поэтому на
самом деле мы велим "Заменить данный исполняемый сценарий Bash неким сервером Rails".
Суммируя, docker-entrypoint.sh
действует как некий обёртывающий сценарий,
предоставляющий нам возможность осуществить нашу небольшую очистку имеющегося файла
pid
и затем запустить наш сервер Rails как ни в чём не бывало. Теперь вы можете
запускать docker-compose up
как вашей душе угодно, зная что надоедливая ошибка не
помешает вам.
Точки входа, в особенности при следовании обсуждённым шаблоном, являются хорошим инструментом для присутствия в вашем
арсенале; вы может найти и иные творчекие варианты применения для них. Будет полезно также знать, что вы можете также
определять также некую точку входа напрямую в самом файле Dockerfile
, применяя
соответствующую инструкцию ENTRYPOINT
. Для получения дополнительных подробностей
ознакомьтесь с документацией Docker.
Когда мы запускаем ваше приложение при помощи Compose в определяемом по умолчанию, присоединённом
режиме, иными словами, без соответствующего параметра
-d
- Compose подключает к каждому контейнеру stdout
,
цепляя в хвост его вывод.
Когда вы нажимаете Ctrl-C
, полагается что Compose выдаёт инструкцию своим
контейнерам на прерывание, отправляя соответствующий сигнал SIGTERM
своего основного
процесса. Данный процесс должен выполнить аккуратный выход, после чего должен завершиться и сам контейнер. Когда это происходит
верно, соответствующий вывод Compose на нажатие Ctrl-C
таков:
Killing myapp_web_1 ... done
Gracefully stopping... (press Ctrl+C again to force)
Однако в моей практике, пожалуй, в 10- 50 процентов случаев вместо аккуратного завершения контейнеров мы получаем следующее:
^CERROR: Aborting.
И прекращение завершается неудачей, оставляя эти контейнеры всё ещё исполняющимися. Это не есть хорошо.
К сожалению, это выглядит как давно присутствующая, известная, проблема. По всей видимости, она вызывается проблемой в PyInstaller, неком инструменте с открытым исходным кодом для создания исполняемых файлов из сценариев Python, на который полагается Compose.
Основная проблема скорее в досаде, чем в самом представлении. Мы способны и вручную останавливать свои контейнеры
вызывая команду docker-compose stop
(или kill
).
Однако, хотя это и кажется проблемой сторонней зависимости, это не может не подрывать нашей уверенности в самом
Compose, что является ужасной стыдобой.
Несмотря на то, что я исследовал данную проблему и пытался найти различные предлагаемые исправления, я не смог найти
никакого обходного пути её решения. Если вы столкнулись с данной проблемой, мой совет состоит в том, чтобы просто не
запускать ваши приложения в присоединённом режиме, а вместо этого всегда применять отсоединённый
режим при помощи соответствующего параметра -d
(detached). На сегодняшний день я не
испытывал при этом данной проблемы при таком подходе.
Никакое программное обеспечение не бывает идеальным, но прискорбно когда ваша практика применения некого инструментария преуменьшается за счёт ошибок. Я должен признаться: мне было больно писать эту главу. Я бы хотел чтобы ваш опыт использования Docker был неизменно положительным.
В конце концов, я ощущал достаточную важность этих проблем чтобы привлечь к ним ваше внимание. Надеюсь, теперь вы осведомлены об основных существующих проблемах и готовы к столкновению с ними.
Давайте повторим что мы охватили в этой главе:
-
Мы изучили некую проблему при которой файл
tmp/pids/server.pid
Rails не всегда удаляется в процессе прекращения своих контейнеров. -
Мы знакомились с точками входа, которые предваряют запуск соответствующей команды при старте некого нового контейнера.
-
Мы воспользовались некой точкой входа для создания обёртывающего сценария, который удаляет имеющийся
tmp/pids/server.pid
при запуске контейнера, обходя данную проблему. -
Мы обсудили некую проблему, при которой Compose прерывается внутри себя вместо аккуратного прекращения контейнеров, придя к заключению, что наилучшим подходом для избегания этого будет запуск Compose в отсоединённом режиме (
-d
).
Ладно, хватит о грустном. Самое время подумать о положительных сторонах.
Завершая данный посвящённый разработке раздел, давайте потратим немного времени на то, чтобы задуматься чего мы достигли. На первый взгляд легко допустить ошибку и посчитать что мы приложили немало усили чтобы вернуться туда, откуда мы начинали - к стандартному, работающему приложению Rails.
Тем не менее, на деле мы достигли некоторых основных преимуществ:
-
Наши файлы
Dockerfile
иdocker-compose.yml
снабдили нас декларативным описанием всего нашего приложения целиком - со всеми требующимися для него частями, такими как база данных - помогая нам представить ясную картину того что же делает наше приложение. -
Мы способны раскрутить своё приложение единственной командой - даже если ничего перед этим не установлено. Docker выполняет выгрузку и установку всего что нам потребуется.
-
Мы прекратили необходимость вручную устанавливать зависимости программного обеспечения своего основного приложения в нашей локальной машине. Больше никаких пустяков с получением Redis, Postgres или даже Ruby установленными и исполняемыми с совместимыми версиями по всей своей команде целиком. За нас обо всём этом заботится Docker.
-
Этот последний момент сам по себе значим. Он также означает, что наше прикладное приложение может исполняться в любой машине с установленным Docker. Это предоставляет свободу и переносимость.
-
Обновление частей нашего приложения настолько же простое, как и обновление значения номера версии того образа, на который мы ссылаемся в своём файле Compose. Например, запросто обнаружить, например, как наше приложение работает в более новой версии Ruby.
По всем этим причинам использование Docker в разработке является полезным и самим по себе - вы должны ощущать гордость достигнув этого промежуточного камня. Однако наше путешествие не заканчивается здесь. Docker может привнести ещё больше преимуществ для дальнейшего продвижения в промышленность.
{Прим. пер.: к сожалению, здесь наши интересы расходятся. Нас зачаровало данное описание практики работы с Docker. Тем не менее, наше видение дальнейшего внедрения получаемых приложений Docker связано с применением Kubernetes, для которого мы пока не встречали руководство по практике работы с контейнерами подобного уровня. Если же вы, читатели, полагаете что данный перевод следует продолжить, обращайтесь к нам.}