【问题标题】:Need config.ru to Start up a Sinatra App from within a Docker Container?需要 config.ru 从 Docker 容器中启动 Sinatra 应用程序吗?
【发布时间】:2020-12-22 19:00:58
【问题描述】:

为什么简单的命令 ruby my app.rb 不能从 Docker 容器中启动我的 Sinatra 应用程序?

我有一个非常简单的 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 应用程序时,我使用 Sinatra 和以下 Dockerfile 创建了一个 Gemfile。

FROM ruby:2.7.0

WORKDIR /code
COPY . /code
RUN bundle install

CMD ["ruby", "myapp.rb"]

站起来容器,看起来成功了(Docker Desktop 是绿色的,没有终端错误),但是点击建议的链接 http://localhost:4567/ 没有加载(悲伤的 Chrome 脸)。容器内的日志看起来像这样

[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

【问题讨论】:

  • 您确定您的代码正在运行吗?您是否将 Sinatra 和依赖项安装到您的容器中?如果你这样做了,可能 Ruby 不在容器的 PATH 中,你的容器没有 Web 服务器,或者正在使用的 Web 服务器绑定到不同的端口。也许检查容器的进程列表并打开端口。
  • Sinatra 应该通过 Gemfile 安装(为了清楚起见,在上面添加了它)。我在构建时没有看到任何错误,所以我假设应该安装它可能具有的任何依赖项。
  • 我相信代码应该正在运行。容器日志显示[2020-12-22 21:45:21] 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-22 21:45:21] INFO WEBrick::HTTPServer#start: pid=8 port=4567
  • 奇怪的是,当我从 CLI 运行 ps 时,我没有看到 pid=8。我只看到一个“sh”和我自己的 ps 搜索。
  • 您是否尝试过访问“http://localhost:4567”?

标签: ruby docker containers sinatra rack


【解决方案1】:

当您在 Docker 容器中使用 ruby myapp.rb 启动应用程序时,您的应用程序正在侦听 localhost,因为它正在开发模式下运行。如果您的 Docker 服务器在 VM 中运行,您将无法访问您的应用程序。要解决此问题,当您在 Docker 容器中运行应用程序时,请确保它正在侦听 0.0.0.0:ruby myapp.rb -o 0.0.0.0

【讨论】:

  • 有趣!正如预期的那样,该命令在本地和我的 Dockerfile 中都有效。在本地,我的应用程序在 0.0.0.0:4567 和 Docker 桌面 localhost:4567 上打开。指定允许它工作的 -o 0.0.0.0 是什么?容器内的日志看起来与 ruby​​ myapp.rb Docker 案例完全相同,但它会打开。
  • 我不确定我是否理解在 VM 中运行我的 Docker 服务器是什么意思。我只是在这里和本地做 docker 桌面。
  • Docker 在某些系统(如 Mac)上使用虚拟机。绑定到0.0.0.0 意味着您的应用程序将侦听来自任何地方的连接,而不仅仅是来自 VM 内部的本地连接。 0.0.0.0 背后的网络概念在这里解释:superuser.com/questions/949428/…
  • Docker 使用自己的网络堆栈,除非您指定 --net host。除此之外,它还可以在特定平台上的虚拟机中运行。
  • 太棒了!稍后我将对此进行深入探讨。我正在收藏的一些资源(以防其他人正在查看):1 - Docker 连接被拒绝 pythonspeed.com/articles/docker-connection-refused,2 - 容器 localhost 端口映射 - forums.docker.com/t/…,3 - 容器网络 - docs.docker.com/config/containers/container-networking,4 - Sinatra 文档-o sinatrarb.com/configuration.html
【解决方案2】:

注意

以下答案与该问题的先前版本有关。新问题有不同的答案(使用-o 0.0.0.0 CLI 参数修复绑定地址)。


Sinatra 框架基于 Rack,需要与 Rack 兼容的服务器……或者,它也可以回退到包含在 Ruby 语言包中的 WEBrick 服务器上。

WEBrick 是一款不错的服务器,但它的设计目的不是为了满足更重的负载或在生产中运行的实际 Web 应用程序的需求。

因此,您应该使用 Rack 兼容的服务器。

但是,这并不意味着您必须使用 rackup CLI 帮助程序。

一些服务器,如 Puma、iodine 和 Passenger 包含自己的 CLI,因此您可以使用以下方式运行您的应用程序:

CMD ["bundle", "exec", "puma", "-p", "4567"]

键入puma -h(或iodine -h)以获取更多命令行选项。服务器的特定 CLI 可能会提供一些您通过 backup 无法获得的特定于服务器的功能。例如,Iodine 通过其 CLI 公开了一些安全选项(最大文件上传大小、最大总标头长度、Web 套接字消息限制等)。

应该考虑使用服务器的 CLI 界面更好的选择

此外,虽然我不推荐它,但有些服务器还提供 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 并使用一个不错的服务器(我喜欢碘,但 Puma 更受欢迎,除非您需要实时发布/订阅、websockets 或某些特定的安全/性能功能,否则通常很流行更安全)。


编辑(根据评论):

如果您真正需要的是在 Ruby 脚本中嵌入命令 bundle exec(用于使用 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 不起作用并且您似乎需要捆绑exec ... 启动它的方式。
  • @stk1234 bundle exec 从 gem 文件中加载所有 gem,并在运行 exec 之后的命令之前将您锁定到 gemfile.lock 中的版本。您不必使用捆绑程序,但它确实使版本控制更加容易。
  • @stk1234 - 我更新了答案以提供这些额外的变化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-10
  • 1970-01-01
  • 2012-01-10
  • 1970-01-01
相关资源
最近更新 更多