Нужен config.ru для запуска приложения Sinatra из контейнера Docker?

Почему простая команда 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 веб-сервисов.


1
920
2

Ответы:

ПРИМЕЧАНИЕ:

Следующий ответ относится к предыдущей версии вопроса. Новый вопрос имеет другой ответ (исправление адреса привязки с помощью аргумента -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