Глава 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.pidRails не всегда удаляется в процессе прекращения своих контейнеров. -
Мы знакомились с точками входа, которые предваряют запуск соответствующей команды при старте некого нового контейнера.
-
Мы воспользовались некой точкой входа для создания обёртывающего сценария, который удаляет имеющийся
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, для которого мы пока не встречали руководство по практике работы с контейнерами подобного уровня. Если же вы, читатели, полагаете что данный перевод следует продолжить, обращайтесь к нам.}