我们谈论关于架构的话题,总离不开微服务,而微服务又会引向容器和Docker。它们都是能简化庞大而复杂应用的构建过程。本周希云和大家分享,如何在Giant Swarm上运行Docker化的Ruby on Rails应用。
对于微服务,没有一个像apt-get这样的工具,不禁让人问:“我怎样安装这个新东西?” 答案是:“你安装不了微服务。” 更具体来说是,“你不可能一下子就用上它。”
“一个系统的架构是最难改变的”,“微服务”这个新事物也不是一颗银弹,即使在今天也没有人能轻易就重构一个复杂的系统,尤其是对于像用了Rails框架构造出来的庞大系统。
Docker承诺的一个特性是:“减少开发、测试和生产环境之间的差异” ,然而,在生产环境运行Docker并不简单,所以我们将研究Giant Swarm这样的工具是如何简化这个部署过程的。
刚开始,我会把一个简单的应用Docker化,这个应用是一个NoSQL数据库ArangoDb的ODM。你能在Github上找到这个应用,如果你要自己尝试这个例子,你的机器上需要装好Docker,Ruby 2版本以上,还有一个用于部署的Giant Swarm账号,不需要单独安装数据库,将会在本地使用一个容器作为数据库。
各个击破
在我们制作Docker容器之前,先看一下这个应用的本身,以及我们将如何实现Docker化,我们手头上的应用是一个普通的Web工程:
一个前端
通过OAuth2使用GitHub登录
调用外部接口(GitHub)
后台长期运行的任务
主数据库
任务队列
可以把这些功能都放到一个容器中,但是这样会得不偿失。例如,会失去应用和数据库分离带来的可扩展性。遵循在每个容器只运行一个进程的原则,把应用分成5个容器:
Nginx会作为前端代理服务器,在我们的例子中它会提供静态资源,在更复杂的应用中,它可能会作为访问控制或者为后端服务提供负载均衡。
第二层是Rails应用,它会运行在一个简单的web服务中,这里用的是Puma。
Sidekiq也会运行在一个独立的容器中,如果你有一个以上的队列,需要为每一个队列创建一个容器。
一个安装了Redis作为任务队列的容器。
一个安装了ArangoDB作为主数据库的容器。
以下这幅图帮助我们去理解这个架构,以及组件之间的通讯:
把Sidekiq放到一个单独的容器运行和微服务架构还相差很远,但这已经使这个应用有不错的隔离性,使每个服务都在各自的工作进程中。
各个组件
我们已经指定了各个容器的功能,现在就要动手创建它们了。Docker容器是基于Dockerfile构建的,这个文件描述了每一个构建步骤。前面说到我们需要五个容器,对应地需要五个Dockerfile。
不过幸运的是,这些容器可以共享同一个镜像。我们不需要额外的定制镜像就可以直接用了,你能找到各种应用的镜像,当然种类最多的还是数据库。
数据库
我们将使用官方的Redis和ArangoDB镜像,通过以下命令运行:
# 会运行Redis并把端口暴露到宿主机
$ docker run --name redis-dredis
# 会运行ArangoDB并把端口暴露到宿主机
$ docker run --name arangodb-darangodb/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 { listen80; 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应用。官方有个Rails Dockerfile,但不会用它,因为它会安装一些我们不需要的组件,更糟的是它安装bundle的时候没用—deployment参数。尽管如此,还是用它作为指引:
FROM ruby:2.1.5
# 如果Gemfile被修改过则抛出错误
RUN bundle config --global frozen1
RUN mkdir -p /usr/src/appWORKDIR /usr/src/appCOPY Gemfile /usr/src/app/COPY Gemfile.lock /usr/src/app/RUN bundle install --deploymentCOPY . /usr/src/app/ENV RAILS_ENV productionEXPOSE8080CMD ["/usr/src/app/bin/rails","server","-p","8080"]
不用Docker我们可以部署类似Capistrano之类的应用,而现在,需要在远程服务器上操作的步骤,我们可以在构建Docker镜像时就完成了。诸如安装gem包和复制代码到服务器,通过这样,有了一个在任何地方任何时间都能启动的容器,而且它的状态和我们最初构建它的时候一模一样。
Sidekiq Worker的Dockerfile基本和上边的一样,比直接复制这个Dockerfile更好的方式是,定义一个公用的基础镜像,用于构建Rails应用和Sidekiq Worker。
构建容器
Docker期待的是一个Dockerfile而例子中已经有三个了,把每个Dockerfile加上了一个有意义的后缀,但是使用Docker命令时会重命名这些文件。如果有一个工具可以用来实现这个,那就是Rake:
namespace :dockerdotask :build => ['docker:build:web','docker:build:app','docker:build:worker','assets:clobber'] namespace :builddotask :web => ['assets:precompile','assets:clean']dosh'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']dosh'ln -snf Dockerfile.app Dockerfile'sh'sudo docker build -t "registry.giantswarm.io/yoshida/gh-recommender-app" .'sh'rm -f Dockerfile'end task :workerdosh'ln -snf Dockerfile.worker Dockerfile'sh'sudo docker build -t "registry.giantswarm.io/yoshida/gh-recommender-worker" .'sh'rm -f Dockerfile'end endend
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"} ]}}}
这里定义了整个应用和组件之间的关联关系,回想一下Nginx的配置,我们使用了http://rails-app:8080作为后端地址,这就是我们定义的地方。rails-app组件会被链接到Nginx组件,同样,REDIS_URL也被关联到了redis组件。
如果不想在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
这个命令会从仓库中获取所有需要用到的镜像,然后以适合的参数启动各个容器,收集所有容器的日志,并且在http://gh-recommender.gigantic.io下部署好了应用。整个过程异常简洁。
如果已经到了这步,恭喜你!
扩容
现在我们为每个组件都使用了一个容器,当你的应用吸引了更多的用户,或者突然发生了不可预见的事件需要更加多的资源。传统的做法是添加更多的服务器,需要一系列的人工操作:启动机器,搭建好环境并且添加节点到负载均衡中。使用Giant Swarm的话,添加一个实例非常简单:
$ swarm scaleup github_recommender/gh-recommender/rails-app
这样减轻了很多技术负担,但是它并不能使你的应用魔法般地就支持水平扩展,当它在数据库应用上就更加复杂了,你还需要研究怎样使应用支持扩展。但是这样至少你可以专注于这块,而不需要担心基础设施的细节了。
结论
文中谈及的就是这些,在这主题下,还有更多的东西可以讨论和学习。分享的目的是希望起码可以带大家入门,如果想走得更远,这里有几点建议主题,是文中没有提及但密切关联的:
容器能在本地开发环境使用,但本文并没有涉及如何实现。
无论在本地或者生产环境下,调试容器都是一个比较大的问题。正如它的其它方面,这个也没有银弹,可能也永远不会有。这也是需要注意的地方。
在Docker世界中,安全也是一个大问题,使用Giant Swarm会有所帮助,我们需要熟悉容器和Docker可能带来的安全性问题。这里说的不是安全漏洞,而是与传统部署方式之间的不同,例如像安装或管理虚拟机那样。
此外,希云强烈建议大家自己打包镜像,不要依赖于公共镜像库。否则最终你会需要很多不同的镜像。例如,例子中的五个容器就需要三个不同的Linux分发版。
尝试在每个容器中只启动一个进程,虽然这是Docker官方的建议,但无疑这是有争议的,是否必须要这样做,应该具体问题具体分析,根据实际情况做出决定。
作者:希云Docker容器管理平台
链接:https://www.jianshu.com/p/9843d90ac748