使用Ruby on Rails开启Docker微服务之旅

Posted cSphere容器平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Ruby on Rails开启Docker微服务之旅相关的知识,希望对你有一定的参考价值。


我们谈论关于架构的话题,总离不开微服务,而微服务又会引向容器和Docker。它们都是能简化庞大而复杂应用的构建过程。本周希云和大家分享,如何在Giant Swarm上运行Docker化的Ruby on Rails应用。

使用Ruby on Rails开启Docker微服务之旅
对于微服务,没有一个像apt-get这样的工具,不禁让人问:“我怎样安装这个新东西?” 答案是:“你安装不了微服务。” 更具体来说是,“你不可能一下子就用上它。” 

“一个系统的架构是最难改变的”,“微服务”这个新事物也不是一颗银弹,即使在今天也没有人能轻易就重构一个复杂的系统,尤其是对于像用了Rails框架构造出来的庞大系统。

Docker承诺的一个特性是:“减少开发、测试和生产环境之间的差异” ,然而,在生产环境运行Docker并不简单,所以我们将研究Giant Swarm这样的工具是如何简化这个部署过程的。

刚开始,我会把一个简单的应用Docker化,这个应用是一个NoSQL数据库ArangoDb的ODM。你能在Github上找到这个应用,如果你要自己尝试这个例子,你的机器上需要装好Docker,Ruby 2版本以上,还有一个用于部署的账号,不需要单独安装数据库,将会在本地使用一个容器作为数据库。

☞ 各个击破

在我们制作Docker容器之前,先看一下这个应用的本身,以及我们将如何实现Docker化,我们手头上的应用是一个普通的Web工程:

  • 一个前端

  • 通过OAuth2使用GitHub登录

  • 调用外部接口(GitHub)

  • 后台长期运行的任务

  • 主数据库

  • 任务队列

可以把这些功能都放到一个容器中,但是这样会得不偿失。例如,会失去应用和数据库分离带来的可扩展性。遵循在每个容器,把应用分成5个容器:

  1. nginx会作为前端代理服务器,在我们的例子中它会提供静态资源,在更复杂的应用中,它可能会作为访问控制或者为后端服务提供负载均衡。

  2. 第二层是Rails应用,它会运行在一个简单的web服务中,这里用的是Puma。

Sidekiq也会运行在一个独立的容器中,如果你有一个以上的队列,需要为每一个队列创建一个容器。

  1. 一个安装了Redis作为任务队列的容器。

  2. 一个安装了ArangoDB作为主数据库的容器。

以下这幅图帮助我们去理解这个架构,以及组件之间的通讯:

使用Ruby on Rails开启Docker微服务之旅

把Sidekiq放到一个单独的容器运行和微服务架构还相差很远,但这已经使这个应用有不错的隔离性,使每个服务都在各自的工作进程中。

☞ 各个组件

我们已经指定了各个容器的功能,现在就要动手创建它们了。Docker容器是基于构建的,这个文件描述了每一个构建步骤。前面说到我们需要五个容器,对应地需要五个Dockerfile。

不过幸运的是,这些容器可以共享同一个镜像。我们不需要额外的定制镜像就可以直接用了,你能找到各种应用的镜像,当然种类最多的还是数据库。

☞ 数据库


我们将使用官方的Redis和ArangoDB镜像,通过以下命令运行:

# 会运行Redis并把端口暴露到宿主机

$ docker run --name redis -d redis

# 会运行ArangoDB并把端口暴露到宿主机

$ docker run --name arangodb -d arangodb/arangodb

这两个命令会从官方的镜像库获取到镜像,以后台方式(-d)启动,并且分配了一个名称(—name),它们都会分配到一个卷还有默认的端口。对于ArangoDB,至少应该为生产环境配置的设置。

☞ Nginx 前端代理


记住,Docker容器应该是被看作不可变的,改变应该发生在构建时而不是运行时,这个要在每次更改时重新构建镜像。对于Nginx前端,需要这样一个更改:指定一个配置文件来代理Rails应用。因为Docker镜像每次构建时都使用一个已经存在的镜像,我们使用了官方的Nginx镜像作为基础镜像:

FROM nginx

RUN rm -rf /usr/share/nginx/html
COPY public /usr/share/nginx/html
COPY config/deploy/nginx.conf /etc/nginx/conf.d/default.conf

Dockerfile的开头总是FROM语句,它告诉Docker要继承于哪个基础镜像。另外我们只需要COPY这个public目录和配置到镜像中,正如我前边所说,容器应该被看作不可变的,每当需要改变这些资源时,应该创建一个新的镜像。Nginx的配置如下:

server {
    listen       80;
    server_name  localhost;

    location / {
        root      /usr/share/nginx/html;
        index     index.html index.htm;
        try_files $uri/index.html $uri.html $uri @upstream;
    }

    location @upstream {
        proxy_pass http://rails-app:8080;
    }
}

还有一件事应该提一下:这个rails-app主机名从哪里来的呢?Docker将提供两种方法去连接容器(我们会解释这点),一串环境变量和/etc/hosts文件。在这个例子中,我们使用了/etc/hosts

☞ Rails应用和Sidekiq Worker


现在添加Nginx代理的后端服务:Rails应用。官方有个,但不会用它,因为它会安装一些我们不需要的组件,更糟的是它安装bundle的时候没用—deployment参数。尽管如此,还是用它作为指引:

FROM ruby:2.1.5

# 如果Gemfile被修改过则抛出错误

RUN bundle config --global frozen 1

RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY Gemfile /usr/src/app/ COPY Gemfile.lock /usr/src/app/ RUN bundle install --deployment COPY . /usr/src/app/ ENV RAILS_ENV production EXPOSE 8080CMD ["/usr/src/app/bin/rails", "server", "-p", "8080"]

不用Docker我们可以部署类似Capistrano之类的应用,而现在,需要在远程服务器上操作的步骤,我们可以在构建Docker镜像时就完成了。诸如安装gem包和复制代码到服务器,通过这样,有了一个在任何地方任何时间都能启动的容器,而且它的状态和我们最初构建它的时候一模一样。

Sidekiq Worker的Dockerfile基本和上边的一样,比直接复制这个Dockerfile更好的方式是,定义一个公用的基础镜像,用于构建Rails应用和Sidekiq Worker。

☞ 构建容器


Docker期待的是一个Dockerfile而例子中已经有三个了,把每个Dockerfile加上了一个有意义的后缀,但是使用Docker命令时会重命名这些文件。如果有一个工具可以用来实现这个,那就是:

namespace :docker do
  task :build => ['docker:build:web', 'docker:build:app', 'docker:build:worker', 'assets:clobber']

  namespace :build do
    task :web => ['assets:precompile', 'assets:clean'] do
      sh 'ln -snf Dockerfile.web Dockerfile'
      sh 'sudo docker build -t "registry.giantswarm.io/yoshida/gh-recommender-web" .'
      sh 'rm -f Dockerfile'
    end

    task :app => ['assets:precompile', 'assets:clean'] do
      sh 'ln -snf Dockerfile.app Dockerfile'
      sh 'sudo docker build -t "registry.giantswarm.io/yoshida/gh-recommender-app" .'
      sh 'rm -f Dockerfile'
    end

    task :worker do
      sh 'ln -snf Dockerfile.worker Dockerfile'
      sh 'sudo docker build -t "registry.giantswarm.io/yoshida/gh-recommender-worker" .'
      sh 'rm -f Dockerfile'
    end
  end
end

web和app的构建都需要使用RAILS_ENV=production,因为要这些文件都是给生产环境而不是开发环境生成的。-t参数会指定目标镜像的仓库名称,这对下一步把镜像推到云上是必须的。

☞ 转移到云上


目前为止我们已经有了一个完整的本地环境,这很好,但是如果想真正要的是对外的环境,至少还要几步。

任何人都可以配置服务器来运行基于Docker的应用。但是这样的话就要面对各种挑战:把容器链接在一起,扩展容器,管理跨节点的容器,还有更多。幸运的是,你可以直接使用Giant Swarm,这些它都帮你考虑了。首先你需要获取一个邀请码,你注册之后就可以使用swarm命令行工具去配置你本地的机器了。第一件要做的事是创建一个swarm.json:

{
  "name": "github_recommender",
  "components": {    "arangodb": {      "image": "arangodb/arangodb",      "ports": [        "8529/tcp"      ],      "volumes": [        {          "path": "/data",          "size": "5 GB"        }      ]    },    "nginx": {      "image": "registry.giantswarm.io/yoshida/gh-recommender-web",      "ports": [        "80/tcp"      ],      "domains": {        "80/tcp": [          "gh-recommender.gigantic.io"        ]      },      "links": [        {          "component": "rails-app",          "target_port": "8080/tcp"        }      ]    },    "rails-app": {      "image": "registry.giantswarm.io/yoshida/gh-recommender-app",      "ports": [        "8080/tcp"      ],      "env": [        "RAILS_ENV=production",        "SECRET_KEY_BASE=$secret_key_base",        "REDIS_URL=redis://redis:6379",        "GITHUB_KEY=$github_key",        "GITHUB_SECRET=$github_secret"      ],      "links": [        {          "component": "arangodb",          "target_port": "8529/tcp"        },        {          "component": "redis",          "target_port": "6379/tcp"        }      ]    },    "redis": {      "image": "redis",      "ports": [        "6379/tcp"      ]    },    "sidekiq-worker": {      "image": "registry.giantswarm.io/yoshida/gh-recommender-worker",      "env": [        "RAILS_ENV=production",        "SECRET_KEY_BASE=$secret_key_base",        "REDIS_URL=redis://redis:6379"      ],      "links": [        {          "component": "arangodb",          "target_port": "8529/tcp"        },        {          "component": "redis",          "target_port": "6379/tcp"        }      ]    }  }}

如果不想在swarm.json中放置敏感信息(例如Github OAuth2的token),可以单独在一个swarmvars.json文件中定义这些变量:

{    "GIANT_SWARM_USER/dev": {        "github_key": "GITHUB_KEY",        "github_secret": "GITHUB_SECRET",        "secret_key_base": "SECRET_KEY_BASE"
    }
}

可以使用例如$github_key关联这些变量到swarm.json,当应用在Giant Swarm上运行时,各个容器会使用适当的—link—env选项。为了使所有服务都能从外部访问,我们需要指定域名到至少一个组件,Nginx是我们的入口,所以把域名指定到它上。

在启动应用之前,首先需要上传镜像到Giant Swarm的镜像库上(当然你也可以推到Docker Hub上,但可能你不想你的镜像能被公开访问):

$ docker push registry.giantswarm.io/yoshida/gh-recommender-web
$ docker push registry.giantswarm.io/yoshida/gh-recommender-app
$ docker push registry.giantswarm.io/yoshida/gh-recommender-worker

网络状况会直接影响这个上传过程,一旦上传完成,就可以用这个命令启动所有容器:

$ swarm up

这个命令会从仓库中获取所有需要用到的镜像,然后以适合的参数启动各个容器,收集所有容器的日志,并且在 下部署好了应用。整个过程异常简洁。

如果已经到了这步,恭喜你!

☞ 扩容


现在我们为每个组件都使用了一个容器,当你的应用吸引了更多的用户,或者突然发生了不可预见的事件需要更加多的资源。传统的做法是添加更多的服务器,需要一系列的人工操作:启动机器,搭建好环境并且添加节点到负载均衡中。使用Giant Swarm的话,添加一个实例非常简单:

$ swarm scaleup github_recommender/gh-recommender/rails-app

这样减轻了很多技术负担,但是它并不能使你的应用魔法般地就支持水平扩展,当它在数据库应用上就更加复杂了,你还需要研究怎样使应用支持扩展。但是这样至少你可以专注于这块,而不需要担心基础设施的细节了。

☞ 结论

文中谈及的就是这些,在这主题下,还有更多的东西可以讨论和学习。分享的目的是希望起码可以带大家入门,如果想走得更远,这里有几点建议主题,是文中没有提及但密切关联的:

  • 容器能在本地开发环境使用,但本文并没有涉及如何实现。

  • 无论在本地或者生产环境下,调试容器都是一个比较大的问题。正如它的其它方面,这个也没有银弹,可能也永远不会有。这也是需要注意的地方。

  • 在Docker世界中,安全也是一个大问题,使用Giant Swarm会有所帮助,我们需要熟悉容器和Docker可能带来的安全性问题。这里说的不是安全漏洞,而是与传统部署方式之间的不同,例如像安装或管理虚拟机那样。

此外,希云强烈建议大家自己打包镜像,不要依赖于公共镜像库。否则最终你会需要很多不同的镜像。例如,例子中的五个容器就需要三个不同的Linux分发版。

尝试在每个容器中只启动一个进程,虽然这是Docker官方的建议,但无疑这是有争议的,是否必须要这样做,应该具体问题具体分析,根据实际情况做出决定。


如了解更多Docker相关知识,请观看培训视频:

如需要Docker相关产品,请访问希云官网首页:

cSphere1.0版本已发布,正式商用!欢迎咨询 400-686-1560

使用Ruby on Rails开启Docker微服务之旅







以上是关于使用Ruby on Rails开启Docker微服务之旅的主要内容,如果未能解决你的问题,请参考以下文章

Ruby on Rails 6 + Docker = Webpacker::Manifest::MissingEntryError?

尝试从 Ruby on Rails 连接到 postgres(在 docker 容器中)

通过Ruby on Rails和docker构建微服务架构之入门教程

部署 Ruby on Rails 6 - AWS Elastic Beanstalk - Docker: ArgumentError: Missing `secret_key_base`

Ruby on Rails 開發秘籍 | Ruby on Rails 快速入門

ruby on rails (函数使用)