问题描述
为什么简单的命令 ruby my app.rb
不能从 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 应用程序时,我使用 Sinatra 和以下 Dockerfile 创建了一个 Gemfile。
FROM ruby:2.7.0
workdir /code
copY . /code
RUN bundle install
CMD ["ruby","myapp.rb"]
立起容器,看起来成功了(Docker 桌面是绿色的,没有终端错误),但是点击建议的链接 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
解决方法
当您在 Docker 容器中使用 ruby myapp.rb
启动您的应用程序时,您的应用程序正在侦听 localhost
,因为它在开发模式下运行。如果您的 Docker 服务器在 VM 中运行,您将无法访问您的应用程序。要解决此问题,当您在 Docker 容器中运行您的应用时,请确保它正在侦听 0.0.0.0:ruby myapp.rb -o 0.0.0.0
注意:
以下答案与问题的先前版本有关。新问题有不同的答案(使用 -o 0.0.0.0
CLI 参数修复绑定地址)。
Sinatra 框架基于 Rack,需要一个与 Rack 兼容的服务器......或者它也可以回退到 Ruby 语言包中包含的 WEBrick 服务器。
WEBrick 是一款不错的服务器,但它并不是为较重的负载或在生产中运行的实际 Web 应用程序的需求而设计的。
因此,您应该使用与机架兼容的服务器。
然而,这并不意味着您必须使用 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 或某些特定的安全/性能功能,流行通常更安全)。
编辑(根据评论):
如果您真正想要的是将命令 bundle exec
嵌入到 Ruby 脚本中(使用 gemfile
进行版本控制),您可以使用以下行启动脚本
#!/usr/bin/env ruby
require 'bundler'
Bundler.require
或者,如果您根本不想使用 gemfile
(或不需要版本控制),您可以在第一行开始:
#!/usr/bin/env ruby
然后你就可以直接启动你的服务器了:
CMD ["puma","4567"]
或者,不使用服务器的 CLI,使用上面的示例脚本,运行:
CMD ["my_script.rb"]