Docker 中的“Hello World”不止于此:在 Docker 中构建 Rails + Sidekiq Web 应用程序

2025-05-26

Docker 中的“Hello World”不止于此:在 Docker 中构建 Rails + Sidekiq Web 应用程序

这是Docker 不止“Hello World”系列的第一篇博文。本系列将帮助您准备应用:从本地设置到在 AWS 上将其部署为生产级工作负载。

关于如何在 Docker 中显示“Hello World”的网页教程层出不穷。问题在于,从让你的应用在 Docker 中显示“Hello World”,到在 AWS 中运行生产级应用程序,这个过程往往并不那么简单。即使你的应用不是 Rails/Ruby 开发的,阅读本系列文章也能帮助你了解在生产环境中使用 Docker 后面临的挑战。

我已经在生产环境中使用 Docker 7 个月了,在此之前也学习了 4 个月。在简单的设置下,使用 Docker 非常简单。然而,在生产环境中使用它会增加复杂性,因为需要考虑很多其他事情。这篇文章将帮助你管理这种复杂性。

对于这篇文章,我们将:

  • 了解 Docker 的基本概念以及使用 Docker 的原因。
  • 将 Rails 应用程序连接到 PostgreSQL 服务器。
  • 运行 Sidekiq 服务器以从 Redis 数据库获取作业​​。
  • 通过在 Redis 中添加作业,向 Sidekiq 服务器提供简单任务。
  • 使用 Docker compose 运行 Rails 应用

这是一篇基础文章。我们这里要构建的 Rails 应用将成为后续文章中使用的示例应用。如果您刚开始使用 Docker,建议您阅读这篇文章。

如果您之前有过使用 Docker 的经验,我建议您阅读本系列的下一篇文章。本教程的源代码可在GitHub上找到,版本号为v0.0.1

0 | 概念

在本节中,我将快速介绍 Docker 背后的基本概念。为了更深入地理解 Docker 的概念,我推荐Chris Noring 的介绍

本质上,Docker 允许我们在主机操作系统(例如 Ubuntu、Mac 或 Windows)上创建容器。我们可以在一个主机操作系统中运行多个容器。

Container- 这是一个运行在 Linux 内核之上的进程。它拥有自己的资源:CPU、内存和文件系统。由于拥有自己的资源,容器可以与实例内运行的其他容器隔离。即使某个容器因任何原因发生故障,也不会影响在同一主机操作系统内运行的其他容器/进程。

Image- 这是一个包含执行代码所需的所有内容的文件:依赖项、二进制文件、源代码等。它们通过执行名为 Dockerfile 的脚本式文件中的命令来构建。镜像用于生成多个相同的容器。这些容器是这些镜像的“实例化”,用于服务于您的客户流量。

Dockerfile- 该文件包含构建镜像所需的指令。您在此处输入的命令应指定如何将应用程序所需的所有内容放入镜像中。

如果这一切看起来不清楚,这篇文章中的实践将会在实践中说明这些概念。

1 | 为什么选择 Docker?

降低成本

传统上,您会预置一个实例,并且只在其中运行一台应用服务器。如果需要扩展应用程序,您可以添加更多实例,每个实例上运行一台应用服务器。通常,您不会通过在一个实例中添加更多应用服务器来实现扩展,因为这很难管理。但这也会导致大多数实例的容量处于闲置状态。

在 Docker 中,您可以在一个实例中运行多个容器。由于您可以将实例的资源分配给容器,因此只要实例上有足够的资源,您就可以在实例中添加任意数量的容器。例如,如果您有一个内存为 8GB 的​​实例,而每个容器只需要 1GB 内存,那么您可以在该单个实例中最多拥有 8 个容器。

生产环境中另一个常见的问题是内存膨胀。当你的应用程序消耗的内存远远超过预期时,就会发生内存膨胀。常见的解决方法是增加实例的大小,但这当然会增加成本。

在 Ruby 中,内存膨胀问题在像Sidekiq这样的多线程应用程序中非常严重。在 Docker 中,你可以为容器设置硬分配内存。如果应用程序消耗的内存超过了硬分配的内存(例如 1GB),Docker 就会终止该容器并启动一个新容器。这可以防止内存膨胀,并帮助你更高效地利用资源。

这样,您就可以充分利用实例的资源。您无需像传统方式那样配置那么多实例。

标准化

您的应用在本地运行的环境将与在生产环境中完全相同。不再出现“在我的机器上可以运行,但在生产环境中却不行”的情况。

新开发者的入职也变得非常简单:你只需要让他们安装 Docker,并在本地机器上运行你的 Docker 镜像即可。无需再写一份长达 7 页的文档来指导如何在他们的机器上安装你的应用及其所有依赖项。

更快的部署

Docker 之前的部署流程如下:AWS 检测到流量激增,它会预置一个新实例,安装所有依赖项,测试实例是否显示流量,然后新实例会接收流量。由于每次都会加载整个操作系统,因此整个过程可能需要几分钟。

Docker 中的部署速度更快。由于容器本身就是一个进程,因此启动它比加载整个操作系统要轻量得多。

其他的

还是不相信?您可以点击此处了解其他优势

2 | 安装

在本教程中,我使用了 Rails 5.2.3 和 Ruby 2.5.1。您可以参考本教程安装 Ruby 和 Rails 。请确保在下拉菜单中选择 Ruby 2.5.x 和 Rails 5.2.x,并选择您使用的操作系统。

我们还将使用 Docker。如果您像我一样是 Mac 用户,请使用本教程。对于 Windows 用户,您可以使用这个

3 | 创建 Rails 应用

(3.0)转到您想要制作 Rails 应用程序的文件夹并执行:

rails new rails_docker_app
cd rails_docker_app
git init && git add .
git commit -m "Initialize Rails project"
Enter fullscreen mode Exit fullscreen mode

然后,让我们创建在主页上显示消息所需的路由、控制器和视图。

(3.1)打开config/routes.rb并创建到主页的路由。我们将在接下来的步骤中创建主页。

  root to: "home#index"
Enter fullscreen mode Exit fullscreen mode

(3.2)home_controller.rb在文件夹中创建文件app/controllers。在变量中写入一条基本消息@message。由于变量名前面带有 @ 符号,因此可以从视图中访问它。

class HomeController < ApplicationController
  def index
    @message = "static page"
  end
end
Enter fullscreen mode Exit fullscreen mode

(3.3)创建app/views/home文件夹。在该文件夹中,创建一个名为 的文件index.html.erb。注意,我们引用了@message上一步中创建的变量。这将显示“静态页面”消息。

Home Page:

<%= @message %>
Enter fullscreen mode Exit fullscreen mode

(3.4)现在,启动 rails 服务器,您应该会看到我们刚刚创建的简单主页。

rails server -p 3000
Enter fullscreen mode Exit fullscreen mode

您应该会看到如下所示的简单页面:

(3.5)当你满意时,提交你的进度

git add .
git commit -m "Add basic routes, views, controller"
Enter fullscreen mode Exit fullscreen mode

4 | 为 Rails 设置 Docker

(4.1)使用命令在应用程序根目录下创建Dockerfile touch Dockerfile

# gets the docker image of ruby 2.5 and lets us build on top of that
FROM ruby:2.5.1-slim

# install rails dependencies
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs libsqlite3-dev

# create a folder /myapp in the docker container and go into that folder
RUN mkdir /myapp
WORKDIR /myapp

# Copy the Gemfile and Gemfile.lock from app root directory into the /myapp/ folder in the docker container
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

# Run bundle install to install gems inside the gemfile
RUN bundle install

# Copy the whole app
COPY . /myapp
Enter fullscreen mode Exit fullscreen mode

本质上,Dockerfile 只是一堆放在一个文件中的人类可读的 shell 命令。第一行从Docker Hub获取一个名为“ruby:2.5.1-slim”的 Docker 镜像。该镜像被称为基础镜像。它包含一个名为“slim”的轻量级操作系统,预装了 ruby​​ 2.5.1。我们将使用下面的命令在此基础镜像上进行构建。

下一个命令安装 Rails 的依赖项。

图像内部有自己的“文件夹结构”。使用下面的命令,我们将创建/myapp文件夹并将其设置为“默认”文件夹。

我们复制GemfileGemfile.lock,然后运行bundle install​​。这会将所有必需的 gem 安装在镜像中。然后,我们继续将整个应用程序复制到镜像中。

(4.2)现在我们了解了它的作用,让我们执行docker build .Docker 来按照指令为我们创建一个 Docker 镜像。

您应该看到 Docker 运行 Dockerfile 中的命令:

(4.3)执行完成后,运行docker images即可看到刚刚构建的docker镜像:

你可以看到,这是一个相当大的文件(约 641MB)。这是因为它包含了运行应用程序所需的一切。

(4.4)从 中docker images查找新创建图像的 image_id。我的图像 ID 是这样的:“2f1a0fabe8c6”。让我们给图像添加一个好记的名字。

# for me
docker tag 2f1a0fabe8c6 rails-docker-app:v0.0.1

# for you: replace --chash-- with your image_id
docker tag --chash-- rails-docker-app:v0.0.1
Enter fullscreen mode Exit fullscreen mode

再次运行docker images,您将看到具有正确名称的图像。

(4.5)现在我们有了 Docker 镜像,让我们运行它。

docker run -it -p 3000:3000 rails-docker-app:v0.0.1 bundle exec rails server -b 0.0.0.0 -p 3000
Enter fullscreen mode Exit fullscreen mode

这些-it标志允许您拥有一个终端,您可以在其中查看来自 Rails 服务器的日志。该-p 3000:3000标志将容器的 3000 端口暴露到本地计算机的 3000 端口。然后,我指定了我们刚刚标记的 Docker 镜像以及运行 Rails 服务器的命令。

您应该再次看到这个简单的页面,这次是在 Docker 上运行的:

(4.6)如果您对输出满意,请提交您的进度。

git add .
git commit -m "Add Dockerfile and run via docker run"
Enter fullscreen mode Exit fullscreen mode

5 | 使用 docker-compose 轻松运行 Docker

我们现在在 Docker 🎉 🎉 🎉 之上有一个可运行的 Ruby on Rails 应用程序。

然而,通常的生产工作负载并非如此简单。我们通常会与 Rails 应用结合运行其他服务:数据库、Redis、Sidekiq 等等。因此,本地设置通常会有 5 个不同的终端窗口,其中包含 5 个单独的 docker run 命令。这需要跟踪的内容非常多。Docker 考虑到了这一点,并推出了docker-compose

docker-compose 服务允许你在一个 YAML 文件中整齐地声明所有这些服务。它还允许你在同一个窗口中启动所有这些服务。

让我们深入了解一下:

(5.1)在应用程序的根目录下创建一个 docker 文件夹,并在其中创建一个 development 文件夹:mkdir docker && mkdir docker/development。然后,创建一个名为 docker-compose.yml 的文件并输入以下内容:

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ../../tmp/db:/var/lib/postgresql/data
  web:
    build: ../../
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ../../:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
Enter fullscreen mode Exit fullscreen mode

这个 docker-compose 文件夹定义了两个服务:db 和 web。“db”服务是一个 PostgreSQL 容器,将用作我们的数据库服务。“web”服务是 Rails 应用的应用服务器。

“web” 服务中的内容是我们通常会放在docker run命令中的内容 -但这样阅读起来更容易! 📖

(5.2)现在,我们使用docker-compose build命令构建运行此设置所需的 Docker 镜像。然后,我们使用这些docker-compose up镜像同时运行这些服务。

cd docker/development
docker-compose build
docker-compose up
Enter fullscreen mode Exit fullscreen mode

您将看到来自这两个服务的日志:

(5.3)如果您对输出满意,请提交您的进度。

git add .
git commit -m "Add docker-compose.yml for easier docker runs"
Enter fullscreen mode Exit fullscreen mode

6 | 更进一步:集成 PostgreSQL

现在,大多数网络教程都停留在第 5 部分就结束了。这篇文章旨在尽可能地满足您的生产需求。连接 PostgreSQL 数据库和通过 Redis 集成 Sidekiq 是两个常见的生产任务,但在其他博客文章中却很少涉及。

(6.1)现在是时候创建我们的模型了。运行下面两个命令来生成 Author 和 Post 的模型和迁移。

rails generate model Author name:string kind:string
rails generate model Post title:string body:text author:references likes_count:integer
Enter fullscreen mode Exit fullscreen mode

(6.2)打开你的 Gemfile 文件,并gem 'sqlite3'用下面这行代码替换。我们将使用 PostgreSQL 作为数据库,并删除对 SQLite3 的引用。

gem 'pg', '>= 0.18', '< 2.0'
Enter fullscreen mode Exit fullscreen mode

然后做bundle install

(6.3)为了能够连接到我们的数据库,请config/database.yml用下面的内容替换的内容(如果文件不存在,请自行创建)。

development:
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5
  database: sample_app_docker_development
Enter fullscreen mode Exit fullscreen mode

然后,将种子添加到db/seeds.rb文件,以便我们可以在数据库中获得样本数据。

author = Author.create(name: "Raphael Jambalos", kind: "Programmer")
post = Post.create(title: "Redis", body: "This is a in-memory database often used for caching.", author_id: author.id)
post = Post.create(title: "PostgreSQL", body: "This is a transactional database used for transactions", author_id: author.id)
post = Post.create(title: "DynamoDB", body: "This is a NoSQL database used for concurrent workloads.", author_id: author.id)
Enter fullscreen mode Exit fullscreen mode

(6.4)我们想将示例数据显示到视图中。为此,我们将文件内容更改app/controllers/home_controller.rb为:

class HomeController < ApplicationController
  def index
    @message = "Dynamic"

    @posts = Post.all
  end
end
Enter fullscreen mode Exit fullscreen mode

然后,让我们改变内容,app/views/home/index.html.erb以便我们可以将帖子整齐地呈现到视图中。

Home Page:

<%= @message %>

<% @posts.each do |post| %>
  <h1> <%= post.title %> </h1>
  <h2> <%= post.author.name %> </h2>
  <p> <%= post.body %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

(6.5)现在,我们docker-compose再次运行。

cd docker/development/
docker-compose build
docker-compose up
Enter fullscreen mode Exit fullscreen mode

在单独的窗口中,按顺序运行这些命令

docker-compose run web rake db:create
docker-compose run web rake db:migrate
docker-compose run web rake db:seed
Enter fullscreen mode Exit fullscreen mode

(6.6)如果您对自己的进度感到满意,请提交:

git add .
git commit -m "Add models, migration, seeds; Modify views to show seeded data."
Enter fullscreen mode Exit fullscreen mode

7 | 更进一步:集成 Sidekiq

(7.1)添加gem 'sidekiq'到Gemfile并运行bundle

Sidekiq 用于后台处理。想象一下,一个网站上有一个表单。填写完表单中所有必需的信息后,点击“提交”。浏览器会将你填写的所有信息发送到 Web 服务器。默认情况下,Web 服务器有 60 秒的时间来处理请求;否则,将导致请求超时。这种处理方式称为前台处理。

现在,我们不想让客户在信息处理过程中等待(更不用说显示超时页面了)。所以我们将处理信息的任务交给了后台处理器(“Sidekiq”)。一旦我们将任务分配给 Sidekiq,客户就会看到一个页面,上面写着“请稍等片刻”。Sidekiq 处理请求,几分钟后,客户刷新页面就能看到结果。

(7.2)运行rails g sidekiq:worker IncrementCount以创建工作文件。我们转到刚刚创建的 Sidekiq 文件app/workers/increment_count_worker.rb,并将其内容替换为下面的代码片段。

本质上,我们获取一篇帖子并将 1 添加到其 like_count 属性中。

class IncrementCountWorker
  include Sidekiq::Worker

  def perform(post_id)
    post = Post.find(post_id)

    post.likes_count ||= 0
    post.likes_count += 1
    post.save
  end
end
Enter fullscreen mode Exit fullscreen mode

(7.3)默认情况下,Rails 不会读取文件夹中的文件app/workers/。您必须转到config/application.rb并将此行插入到class Application代码块中。

  config.autoload_paths += %W(#{config.root}/app/workers)
Enter fullscreen mode Exit fullscreen mode

(7.4)为了显示每篇帖子的点赞数,并为每个帖子添加一个点赞按钮,让我们将app/views/home/index.html.erb

Home Page:

<%= @message %>

<% @posts.each do |post| %>
  <h1> <%= post.title %> </h1>
  <h2> <%= post.author.name %> </h2>
  <p> <%= post.body %>

  <br>

  <p> 
    <%= link_to "Like", increment_async_path(post_id: post.id), method: :post %>

    Likes:
    <%= post.likes_count %> 
  </p>
<% end %>
Enter fullscreen mode Exit fullscreen mode

(7.5)然后,我们添加一个路由config/routes.rb和一个控制器方法来app/controllers/home_controller.rb适应这个请求。

  post "/increment_async", to: "home#increment_async"
Enter fullscreen mode Exit fullscreen mode

控制器方法调用我们的 Sidekiq 服务并调用工作器“IncrementCountWorker”。

class HomeController < ApplicationController
  def index
    @message = "Dynamic"

    @posts = Post.all
  end

  def increment_async
    ::IncrementCountWorker.perform_async(params[:post_id])
    redirect_to root_path
  end
end
Enter fullscreen mode Exit fullscreen mode

(7.6)我们的新设置需要 Sidekiq 服务和 Redis 服务。

Redis 是一个内存数据库。每当客户按下我们在 7.4 版本中创建的按钮时,Redis 数据库中就会添加一条记录。Sidekiq 服务会轮询 Redis 服务,查看其数据库中是否有新的记录。如果有,它会执行该记录。

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ../../tmp/db:/var/lib/postgresql/data
  web:
    build: ../../
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ../../:/myapp
    environment:
      RAILS_ENV: "development"
      REDIS_URL: "redis://redis:6379/12"
    ports:
      - "3000:3000"
    depends_on:
      - db
  redis:
    image: redis
    volumes:
      - ../../tmp/db:/var/lib/redis/data
  sidekiq:
    build: ../../
    command: 'bundle exec sidekiq'
    volumes:
      - ../../:/myapp
    environment:
      RAILS_ENV: "development"
      REDIS_URL: "redis://redis:6379/12"
    depends_on:
      - redis

Enter fullscreen mode Exit fullscreen mode

(7.7)现在,让我们再次运行docker-compose。

cd docker/development/
docker-compose build
docker-compose up
Enter fullscreen mode Exit fullscreen mode

回到localhost:3000浏览器,您应该能够看到以下内容:

点击链接按钮,你就可以增加每篇文章的点赞数。Rails 收到请求后,会在 Redis 上放置一个任务,Sidekiq 会获取该任务,将指定的文章加 1 并保存。

8 | 下一步是什么?

现在,您已经拥有一个在 Docker 上运行的成熟的 Rails 5 应用程序。虽然不多,但 Sidekiq 和 PostgreSQL 已经为您构建应用程序奠定了良好的基础。您甚至可以使用此模板将现有的 Rails 应用程序迁移到 Docker。

开发完 Rails 应用程序后,下一步就是将其部署到云提供商,以便您的客户能够看到您的应用。下一篇文章将介绍如何将这个项目部署到AWS Elastic Container Service。敬请期待!

致谢

特别感谢我的编辑 Allen,他帮助我使这篇文章更加连贯。

封面照片由 Patrick Brinksma 提供

我很高兴收到您对这篇文章的评论/反馈。请在下方评论或给我留言!

文章来源:https://dev.to/raphael_jambalos/more-than-hello-world-in-docker-run-rails-sidekiq-web-apps-in-docker-1b37
PREV
Web 开发的未来:每个开发人员都应该知道的新兴趋势和技术
NEXT
给初学者的 SQL 技巧;作为一名 Web 开发人员,我偶尔编写查询和模式十多年,从中我了解到 SQL 语法不区分大小写