使用Ruby on Rails开启Docker微服务之旅
Posted cSphere容器平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Ruby on Rails开启Docker微服务之旅相关的知识,希望对你有一定的参考价值。
我们谈论关于架构的话题,总离不开微服务,而微服务又会引向容器和Docker。它们都是能简化庞大而复杂应用的构建过程。本周希云和大家分享,如何在Giant Swarm上运行Docker化的Ruby on Rails应用。
对于微服务,没有一个像apt-get
这样的工具,不禁让人问:“我怎样安装这个新东西?” 答案是:“你安装不了微服务。” 更具体来说是,“你不可能一下子就用上它。”
“一个系统的架构是最难改变的”,“微服务”这个新事物也不是一颗银弹,即使在今天也没有人能轻易就重构一个复杂的系统,尤其是对于像用了Rails框架构造出来的庞大系统。
Docker承诺的一个特性是:“减少开发、测试和生产环境之间的差异” ,然而,在生产环境运行Docker并不简单,所以我们将研究Giant Swarm这样的工具是如何简化这个部署过程的。
刚开始,我会把一个简单的应用Docker化,这个应用是一个NoSQL数据库ArangoDb的ODM。你能在Github上找到这个应用,如果你要自己尝试这个例子,你的机器上需要装好Docker,Ruby 2版本以上,还有一个用于部署的账号,不需要单独安装数据库,将会在本地使用一个容器作为数据库。
☞ 各个击破
在我们制作Docker容器之前,先看一下这个应用的本身,以及我们将如何实现Docker化,我们手头上的应用是一个普通的Web工程:
一个前端
通过OAuth2使用GitHub登录
调用外部接口(GitHub)
后台长期运行的任务
主数据库
任务队列
可以把这些功能都放到一个容器中,但是这样会得不偿失。例如,会失去应用和数据库分离带来的可扩展性。遵循在每个容器,把应用分成5个容器:
nginx会作为前端代理服务器,在我们的例子中它会提供静态资源,在更复杂的应用中,它可能会作为访问控制或者为后端服务提供负载均衡。
第二层是Rails应用,它会运行在一个简单的web服务中,这里用的是Puma。
Sidekiq也会运行在一个独立的容器中,如果你有一个以上的队列,需要为每一个队列创建一个容器。
一个安装了Redis作为任务队列的容器。
一个安装了ArangoDB作为主数据库的容器。
以下这幅图帮助我们去理解这个架构,以及组件之间的通讯:
把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 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`