Почему простая команда ruby my app.rb
не работает для загрузки моего приложения Sinatra из контейнера Docker?
У меня есть очень простое приложение Sinatra:
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
Я запускаю это локально с помощью ruby myapp.rb
и получаю следующий вывод
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 5.1.1 (ruby 2.7.0-p0) ("At Your Service")
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 49242
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop
Открывается на http://127.0.0.1:4567 без проблем. При переходе на Dockerize приложение я создаю Gemfile с помощью Sinatra и следующий Dockerfile.
FROM ruby:2.7.0
WORKDIR /code
COPY . /code
RUN bundle install
CMD ["ruby", "myapp.rb"]
Подняв контейнер, вроде успешно (Docker Desktop зеленый, ошибок терминала нет), но переход по предложенной ссылке http://localhost:4567/ не загружается (грустное лицо Хрома). Логи изнутри контейнера выглядят так
[2020-12-27 18:04:52] INFO WEBrick 1.6.0
[2020-12-27 18:04:52] INFO ruby 2.7.0 (2019-12-25) [x86_64-linux]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2020-12-27 18:04:52] INFO WEBrick::HTTPServer#start: pid=1 port=4567
Однако, когда я добавляю приведенный ниже файл config.ru и меняю последнюю строку моего Dockerfile на CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]
, http://localhost:4567/ открывается без проблем.
# config.ru
require './myapp'
run Sinatra::Application
Почему эти настройки необходимы для работы приложения? Журналы из контейнера выглядят почти одинаково.
[2020-12-27 18:01:49] INFO WEBrick 1.6.0
[2020-12-27 18:01:49] INFO ruby 2.7.0 (2019-12-25) [x86_64-linux]
[2020-12-27 18:01:49] INFO WEBrick::HTTPServer#start: pid=1 port=4567
172.17.0.1 - - [27/Dec/2020:18:02:44 +0000] "GET / HTTP/1.1" 200 12 0.0420
Я не обязательно интересуюсь здесь «лучшими практиками» (это побочный проект). Я больше просто пытаюсь понять, что мне может не хватать в работе приложений Dockerizing.
Команды Docker для обоих случаев (и я очищаю образы/контейнеры между запусками):
docker build --tag sinatra-img .
docker run --name sinatra-app -dp 4567:4567 sinatra-img
🤔 А знаете ли вы, что...
Ruby предоставляет инструменты для создания RESTful веб-сервисов.
ПРИМЕЧАНИЕ:
Следующий ответ относится к предыдущей версии вопроса. Новый вопрос имеет другой ответ (исправление адреса привязки с помощью аргумента -o 0.0.0.0
CLI).
Фреймворк Sinatra основан на Rack и требует сервера, совместимого со Rack... либо так, либо он также может использовать резервный сервер WEBrick, включенный в языковой пакет Ruby.
WEBrick — достойный сервер, но он не был разработан для более высоких нагрузок или потребностей реального веб-приложения, работающего в производственной среде.
По этой причине вам СЛЕДУЕТ использовать сервер, совместимый со стойкой.
Однако это не означает, что вы должны использовать помощник rackup
CLI.
Некоторые серверы, такие как Puma, iodine и пассажирские, имеют собственный интерфейс командной строки, поэтому вы можете запустить свое приложение, используя:
CMD ["bundle", "exec", "puma", "-p", "4567"]
Введите puma -h
(или iodine -h
), чтобы получить дополнительные параметры командной строки. Конкретный интерфейс командной строки сервера может предлагать некоторые специфичные для сервера функции, которые вы не получаете с backup
. Например, Iodine предоставляет некоторые параметры безопасности через интерфейс командной строки (максимальный размер загружаемого файла, максимальную общую длину заголовка, ограничения на сообщения веб-сокетов и т. д.).
Использование интерфейса командной строки сервера следует считать лучшим вариантом.
Кроме того, хотя я бы этого не рекомендовал, некоторые серверы также предоставляют Ruby API, который позволяет запускать сервер из скрипта Ruby (вместо файла config.ru
). то есть с йодом (я необъективен):
ENV['PORT'] ||= "4567"
require 'iodine' # will test the `ENV['PORT']` value
require 'sinatra'
get '/' do
'Hello world!'
end
Iodine.listen service: :http, public: './public', handler: Sinatra::Application
# Iodine.threads = 16 # or whatever.
# Iodine.workers = -2 # half the core count (negative value).
Iodine.start
Я бы не стал использовать этот подход. Он имеет тенденцию быть более хрупким, а также жестко кодирует как среду, так и настройки сервера в приложении.
Я бы просто добавил config.ru
и использовал приличный сервер (мне нравится iodine, но Puma гораздо популярнее, и если вам не нужны публикации/подписки в реальном времени, веб-сокеты или какие-то специальные функции безопасности/производительности, популярный часто безопаснее).
РЕДАКТИРОВАТЬ (согласно комментарию):
Если вам действительно нужно встроить команду bundle exec
в скрипт Ruby (для контроля версий с помощью gemfile
), вы можете запустить скрипт со строк
#!/usr/bin/env ruby
require 'bundler'
Bundler.require
Или, если вы вообще не хотите использовать gemfile
(или вам не нужен контроль версий), вы можете просто начать первую строку с:
#!/usr/bin/env ruby
Затем вы можете запустить свой сервер напрямую:
CMD ["puma", "-p", "4567"]
Или, не используя CLI сервера, используя приведенный выше пример скрипта, запустите:
CMD ["my_script.rb"]
Когда вы запускаете свое приложение с помощью ruby myapp.rb
в контейнере Docker, ваше приложение прослушивает localhost
, потому что оно работает в режиме разработки. Если ваш сервер Docker работает на виртуальной машине, вы не сможете получить доступ к своему приложению. Чтобы исправить это, когда вы запускаете свое приложение в контейнере Docker, убедитесь, что оно прослушивает 0.0.0.0: ruby myapp.rb -o 0.0.0.0