容器化现有的Rails应用程序

360 阅读10分钟

容器化软件是将其打包成标准化的单元,以方便开发和部署。容器将你的应用程序的代码和它的所有依赖关系捆绑在一起。容器可以完全独立存在;它包含一个包含你的软件、运行环境和系统库的包。容器帮助开发者和运营团队确保软件在任何环境下都能正常运行。通过将代码与基础设施分开,被 "容器化 "的应用程序在本地环境、测试环境和生产环境中的运行是一样的。

Docker是开发和部署软件的最流行的平台之一。Docker将软件打包成一个 "镜像",在Docker镜像上执行时,会在运行时变成一个容器。这种隔离允许开发者在一台主机上同时运行许多容器。

在对现有应用进行容器化时,Rails开发者面临一系列独特的挑战。本文将提供对一个功能性Rails应用进行容器化的演练,并解释其中的重要概念和陷阱。本文不是对容器或Docker的基本描述;相反,它是对开发人员在对生产应用进行容器化时面临的问题的解释。

前提条件

如果你正在关注,那么你显然需要一个尚未被docker化的Rails应用程序(这是docker对 "容器化 "的专门术语)。 我将使用RailsWork,这是我刚刚启动的一个功能齐全的副业项目。它是一个用Rails编写的工作板,并部署在Heroku上,但它并没有被容器化。

除此之外,你还需要安装Docker。一种流行的安装方式是使用Docker Desktop,它可以通过官方网站下载。

一旦应用程序被下载,运行安装程序。运行后,它会提示你将该应用程序拖到你的应用程序文件夹。然后,你必须从你的应用程序文件夹启动该应用程序,并授予它所要求的特权权限。作为确保Docker正确安装的最后一次检查,尝试通过运行以下程序从终端列出在你的机器上运行的容器。

docker ps

如果Docker已经安装了(而且你没有运行任何容器),你会得到一个空的列表,其中只有标题,看起来像这样。

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Docker文件

在我们深入研究之前,首先要明确术语,这很重要。

在你的Rails应用程序被 "Docker化 "之后,它将在一个容器中运行。容器是独立的,可以被替换,并且经常被重建。

容器是由一个镜像构建的。一个镜像是一个文件系统的虚拟快照,与元数据配对。

Dockerfile是描述如何创建镜像的源代码。Docker文件通常包含在Docker化应用的资源库中,并与应用的其他部分一起在版本控制中被追踪。

创建Docker文件比听起来要容易得多!Docker为我们提供了特殊的语法,抽象化了容器化的艰苦工作。首先,让你的方式到你想容器化的应用程序的根目录。现在你已经准备好开始工作了,如果你使用的是git,最好是创建一个新的分支。你可以通过运行以下程序,轻松创建一个名称为dockerize-this-app 的新分支。

git checkout -b dockerize-this-app

接下来,创建一个Docker文件,指示它建立一个基于Ruby应用程序的镜像。这可以通过运行以下命令行来完成。

echo "FROM ruby:3.0.0" > Dockerfile

在这里,我们只是创建Dockerfile并添加一行,指定在哪里找到Ruby容器镜像。我的项目使用Ruby 3.0.0,所以我使用了合适的镜像。如果你用的是不同版本的Ruby,那也没问题。Docker有一个所有支持镜像的列表

接下来,手动指示Docker创建一个Docker镜像。

docker build -t rails_work .

在这里,你可以用你想要的镜像的任何名字来代替rails_work 。另外,一定要在结尾处加上句号!

如果你想看到镜像已经被创建,你可以用下面的方法列出你系统上的镜像。

docker image list

不过这个图像大部分是空的,它目前不包含我们的应用程序。我们可以通过在Dockerfile中添加以下内容来指示它添加我们应用程序的代码。

ADD . /rails_work
WORKDIR /rails_work
RUN bundle install

这将复制你的应用程序中的文件,并安装应用程序的依赖性。(在这里,你将用你的应用程序的名称替换rails_work )。

在这一点上,你应该重新运行命令来创建镜像。

docker image list

这里有可能出现一个问题,特别是当你在对一个现有的生产应用程序做这件事时。Bundler可能会抱怨说,镜像试图使用的Bundler版本与创建Gemfile.lock 文件的版本不同。如果发生这种情况,你有两个明确的选择。

  • 改变镜像所使用的版本。
  • 完全删除Gemfile.lock 。**如果你这样做,请确保将你需要的任何版本的Gems钉在特定的版本上,因为锁文件将被完全重新生成。

如果你的bundle安装仍然失败,那么你可能需要在你的Dockerfile中进行一些额外的安装

RUN apt-get update && apt-get install -y shared-mime-info

如果你仍然遇到问题,你可能选择了错误的Ruby镜像作为基础,所以值得从那里开始调查。

这里是一个设置环境变量的好机会。

ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true

接下来,添加一行来暴露端口3000,这是Rails默认运行的地方。

EXPOSE 3000

最后,指示容器在启动时打开一个bash shell。

CMD ["bash"]

总的来说,你的Docker文件应该是这样的(将rails_work的名字替换掉)。

FROM ruby:3.0.0

ADD . /rails_work
WORKDIR /rails_work
RUN bundle install

ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true

EXPOSE 3000
CMD ["bash"]

Docker命令详解

对一些最常见的Dockerfile命令有充分的了解肯定会有帮助。

  • FROM -- 这定义了要以什么图像为基础。
  • RUN -- 这是容器执行的命令。
  • ENV -- 这定义了环境变量。
  • WORKDIR -- 这将改变容器正在使用的目录。
  • CMD -> 指定容器启动时要运行的程序。

Docker Compose

根据Docker的文档,"Compose "是他们用于创建(和启动)具有多个Docker容器的应用程序的工具。在YAML中列出了启动应用程序所需的容器的一切。当有人运行docker-compose up ,这些容器就被创建了。Docker-compose让我们声明性地描述我们的容器配置。

在创建Docker Compose文件之前,必须向Docker说明哪些文件应该从被构建的镜像中排除。创建一个名为.dockerignore 的文件。(注意句号!)在这个文件中,粘贴以下内容。

.git
.dockerignore
.env

如果你的Gemfile是由构建过程维护的,那么一定要在上面的忽略中加入Gemfile.lock

接下来,创建一个名为docker-compose.yml 的文件。这就是我们要描述我们的容器配置的地方。我们将以这个文件的内容为开端。

version: '3.8'
services:
  db:
    image: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/Rails-Docker
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  postgres:

这个文件创建了两个服务:一个叫db ,另一个叫web 。db容器将从一个为postgres准备的预制镜像中构建,你应该把POSTGRES_USERPOSTGRES_PASSWORD 的相关值替换掉。你应该注意不要把生产机密放在这个文件中--见下面的 "管理机密 "部分以了解更多信息。

Web容器是从我们的Docker文件中构建的,然后在IP地址0.0.0.0的3000端口上启动一个Rails服务器。

最后,我们有一个Postgres卷来为我们保存数据。

管理秘密

构建时的认证对于生产应用来说可能是一个问题。也许你的应用程序从一个私人仓库中寻找Gems,或者你只需要存储数据库凭证。

任何直接存在于Docker文件中的信息都会被永远植入容器镜像,这是一个常见的安全隐患。

如果你使用Rails的凭证管理器,那么给Docker(或任何主机)的访问权就相对简单了。在Docker Compose文件中,你只需提供RAILS_MASTER_KEY 环境变量。对于给定的编译目标,你在environment 头部下指定密钥,如果你还没有创建,你需要创建。上面的docker-compose文件就会变成下面的样子。

version: '3.8'
services:
  db:
    image: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/Rails-Docker
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      - RAILS_MASTER_KEY=this_would_be_the_key
volumes:
  postgres:

现在,这让你处于一个十字路口。你可能想把这个文件提交到源码控制,但你肯定不希望你的主密钥甚至数据库密码被源码控制跟踪,因为这将是另一个危险的安全问题。到目前为止,最好的解决方案是利用dotenv gem,这样你就可以通过代理访问这些证书,将它们存储在一个不被源码控制跟踪的不同文件中。

运行Docker化的应用程序

最后,你可以用以下命令运行docker化的应用程序。

docker compose up

信不信由你,这就是了!Docker-compose让启动容器变得很容易,尤其是在涉及到命令行参数的时候。

如果你想要一个正在运行的容器列表,只需运行以下命令。

docker ps

如果你的Rails容器的名字是web ,你可以用一种相当直接的方式在上面执行命令。例如,如果你想运行一个Rails控制台,你所需要做的就是运行下面的命令。

docker exec -it web rails console

如果你只想在容器内使用bash shell,你可以运行下面的命令。

docker exec -it web bash

还有一些类似这里列出的陷阱

在生产中使用docker化Rails应用程序的一个常见问题是处理日志。它们不应该长期存在于容器系统中。Docker建议将日志简单地重定向到STDOUT。这可以在config/application.rb 中明确配置。

另一个常见的问题是关于邮件的问题。如果你的应用程序使用邮件,你必须明确地定义连接设置。SMTP是一种非常好的交付方式,通常在默认情况下工作得很好,但我们必须小心地设置服务器的位置和其他设置,以匹配我们的容器配置。

如果你有工作者或后台工作,比如sidekiq,那么你必须在它自己的容器中运行它。

总结

容器化生产型Rails应用程序会带来一系列的挑战,这一点你肯定已经看到了。随着你的应用程序的发展,它可能已经积累了许多依赖关系,这使得像这样的迁移具有挑战性。无论是后台工作者、邮件发送者还是秘密,都有既定的模式来处理大多数陷阱。一旦完成了让生产应用与Docker一起工作的初始工作,未来的变化和部署的便利性将使投资物有所值。