转载

Rails和docker构建微服务架构

说到时下的架构,免不了会涉及到微服务。而谈到微服务架构,又跟容器和Docker技术脱不了关系。虽然容器和Docker并不完全是一回事,但两者是密不可分的,而且二者之间也有共同之处:在大型复杂应用的构建和运营方面,二者都可以大大提高企业的效率。

微服务可不像一般的应用,可以通过apt-get工具进行安装,大家可能会问了:我们该如何才能像安装应用一样实现这种服务呢?在很大的程度上,这个问题的答案是否定的,我们无法轻松实现这种服务。更准确的说,至少目前我们还无法实现。在一个系统中,最难修改的就是架构,而“微服务”正是一种架构,它可不是什么万用灵丹。放眼整个IT领域,多得是采用庞大的单块架构的传统应用,对于Rails之流的架构来说,要想进行重构是一件非常困难的事情。

Docker问世之后事情就变得不一样了。Docker号称是可以“解决开发、QA和生产环境之间的矛盾”,而且还能为用户带来许多其他的便捷。然而,Docker也有劣势,在生产环境中运行Docker是很难的,为了解决这个问题,业界又出现了Giant Swarm之类的解决方案,帮助企业实现的Docker在生产环境中的实际部署

为了方便大家理解,这里我用一个简单的应用示例来说明如何通过Giant Swarm实现应用的Docker化。首先我创建了一个Guacamole应用,作为非SQL数据库ArangoDB的ODM。大家也可以到GitHub上去下载这个应用,不过如果大家要运行该应用的话,要记得在机子上装好Docker以及Ruby2以上的版本,当然,如果要在生产环境中实际部署的话,还需要申请一个Giant Swarm帐户。这样,无需安装任何数据库,我们就可以在本机上部署生产环境并运行Docker容器了。

分而治之 要弄清楚容器化是怎么一回事,在开始干活之前我们还是先来了解下这个应用。应用本身其实并没有什么特别之处,不外乎就是下面几个要点:

·前端界面

·通过OAuth2登录GitHub

·调用外部API(GitHub)

·后台程序优化,以实现长时间运行

·主数据库

·工作队列

我们虽然可以把上面的所有功能点都放在同一个Docker容器里,但这种做法其实是利大于弊的。比方说,如果非要这么做的话,我们可能无法在不依赖于数据库的情况下,只对应用进行伸缩。对目前的IT圈子而言,一切都要求精简化,所以我们最好还是将应用拆分一下,每个进程放在一个单独的容器中。这样算下来我们要跑5个容器:

·Nginx容器,用来作前端代理服务器。在本例中主要负责提供静态资源服务。如果是更复杂的大型应用的话,Nginx还可以在各个后端服务进程之间进行访问控制和负载均衡。

·Rails应用容器。这个应用是跑在web服务器上的,这里我们用一个简单的web服务器就可以了,本例中使用的是Puma.

·接下来是Sidekiq worker,这个也是跑在一个单独的容器中的。如果应用涉及到多个队列的话,就要为每个队列分配一个容器。

·然后是Redis,这个容器主要用来做任务队列。

·最后是ArangoDB容器,这是我们的主数据库。

大家可以看看下面的图表,本例的架构大致就是这样了,另外各个容器间的通信也标明在图表中了:

Rails和docker构建微服务架构

这里我们为Sidekiq单独建了一个容器,但这种做法跟真正意义上的微服务还差得很远,要实现微服务架构,还有很多工作要做。不过,这种做法还是有好处的,这样至少可以将应用本身和worker进程分离开来,让运维工作更简单一些。

镜像仓库,应有尽有

容器的分配方案定好了,接下来就要开始创建了。Docker容器都是通过Dockerfile来创建的,这里我要先科普一下什么是Dockerfile。

Dockerfile是一种命令,它描述了容器的组成结构和创建步骤。上面说过,我们要创建5个容器,那么在创建容器的时候,是不是必须要运行5次Dockerfile呢?

好在答案是否定的。因为容器镜像是可以共享的,如果大家不需要什么自定义镜像的话,镜像仓库中提供的公共镜像就够用了。在镜像仓库中各种应用的镜像应有尽有,当然,绝大部分数据库的镜像也是有的。

幸运的是我们拥有一个可以分享镜像的容器。如果没有任何定制的公众镜像可以直接使用,你会发现各种各样的应用程序的镜像,当然还有数据库镜像。

数据库

这里我们用的是官方提供的Redis和ArangoDB镜像。如果大家不想在本机上安装数据库,但又要在本机上跑容器,我们可以运行以下命令:

Rails和docker构建微服务架构

这两条指令会从Docker官方镜像仓库获取镜像,并以detach(-d参数)运行镜像,并为镜像分配名称(--name)。这两个都会挂在数据卷,并将默认端口公开。另外如果要在生产环境中部署的话,我们还要对ArangoDB数据库进行身份验证配置。

Nginx前端代理服务器

大家千万要记住一点,Docker容器一旦构建好,就是不可变的。要对容器进行修改,只能是在构建应用的时候,而不是在应用已经运行起来以后再去做改动。不管大家对容器做了什么改动,Dockerfile都会相应地发生改变,因此在这种情况下我们必须另行构建新镜像。在本例中,我们要对Nginx前端代理服务器进行修改,将配置文件转发给Rails应用,由于Docker镜像只能在现有镜像的基础上构建,所以这里我们用官方Nginx镜像作为基础镜像来构建自定义镜像:

Rails和docker构建微服务架构

凡是Dockerfile命令,都是以FROM起头的,这条命令会告诉Docker该使用哪个镜像。这里我们在FROM后面只跟了一个nginx,因为我们只需要将公用文件夹和配置文件复制到镜像中就可以了。上面说过,容器一旦构建成功,就是不可变更的,所以如果我们要修改容器内容的话,就只有另外创建新的镜像了。nginx的配置本身并不复杂:

Rails和docker构建微服务架构

这里我要多说一句,上面代码中的Rails应用的主机名从何而来?Docker主要是通过两种方式来呈现相关容器的信息:一个是通过环境变量提供容器信息,还有一个就是通过/ etc/ hosts文件中的条目来呈现信息。本例中我们用的就是/ etc/ hosts中的条目。

Rails应用和Sidekiq Worker

接下来我们要开始创建Rails应用,并让Nginx连接到应用。这里我们没有用官方的Rails Dockerfile,因为官方镜像里包含有不相干的组件,本例中还用不到这些组件。It installs components we don't need or don't use, but worse of all it runs bundle install without --deployment flag.而且这个镜像是以捆绑安装的方式运行的,还没有--deployment标志,这就完全不符合我们的需求了。不过官方镜像也不是毫无用处的,这里我们可以用它做参考,看看自己构建得对不对:

Rails和docker构建微服务架构

如果不用Docker的话,大家也可以用Capistrano之类的技术来部署应用。不过,有了Docker,我们就不必再像以前一样部署在远程服务器上了,部署工作在我们构建Docker镜像时就已经同步完成了,就像安装gems和复制安装源“到服务器”一样。有了容器,我们就可以无惧环境的差异,随时随地都能运行应用,而且性能跟我们在本机上创建的应用完全一模一样。

Sidekiq worker的Dockerfile跟Rails 的差不多,但是我们自己来做一个自定义的基础镜像要比直接复制Rails的Dockerfile来得更好些。这里我就不讲怎么构建Sidekiq worker了,留个作业大家自己下去练习。如果大家能想出好点子,倒不妨发布出来,为Github项目做点贡献。

构建容器

对于Docker来说,只用一个Dockerfile当然是最好的,但在本例中我们要用到三个Dockerfile,虽然我们可以给每个Dockerfile加一个后缀以作区分,但这样也会导致一个问题,就是我们只能通过手动方式挨个去重命名所有的文件。这显然是不可行的,好在我们可以利用Rake之类的工具实现自动重命名:

Rails和docker构建微服务架构

不管是Web服务还是应用本身都是通过RAILS_ENV=production来构建的,因为我们要让所有资源都能适应生产环境,而不仅仅是供开发环境使用。-t参数指定了所得镜像的镜像仓库名称,这一点很重要,这是下一步中上传镜像的必要条件。

迁移到云端

详细的本地环境配置就是这样了。不过,我们的要求肯定不止这么点,我们真正希望的,是让我们搭建的环境能够适应实战的需求。

当然,大家也可以专门针对基于Docker的应用一台一台地来配置服务器。但是这么做麻烦得不是一点半点,比方说我们要怎么实现容器的互连,怎么实现容器的伸缩,怎么实现跨服务器的容器管理,这些都是问题。好在我们可以通过Giant Swarm来解决这一切问题。要使用Giant Swarm,大家要先获取邀请码,得到邀请码之后再安装一个swarm的命令行工具,然后再对本机进行设置就可以了。设置好之后,我们先来创建一个swarm.json:

Rails和docker构建微服务架构

通过上面的代码,我们对整个应用进行了定义,包括每个组件之间是如何进行交互的,都在这一段代码中设置好了。如果我们要在使用http://rails-app:8080作为后台地址前重置nginx,配置的话,就需要在这段代码中进行定义。Rails应用组件将连接到nginx组件,并由nginx判断并分配合适的主机路由。用来连接redis组件的REDIS_URL也是一样的设置法。

如果大家不想把敏感信息(如GitHub的OAuth2访问令牌)放在swarm.json文件中,我们也可以专门创建一个swarmvars.json文件来进行敏感信息的定义:

Rails和docker构建微服务架构

然后我们就可以在swarm.json文件中通过$github_key来调用这些变量了。在Giant Swarm基础设施上运行应用时,每个Docker容器都会分配到适当的启动选项,比如--link和--env。如果要确保应用能够通过外部访问获取到,我们还需要给一个以上的组件分配域名,本例中我们用的是nginx作为外部访问的接入点,所以这里要对该组件的域名进行设置。

最后,在正式启动应用之前,我们还要把自定义镜像上传到Giant Swarm的镜像仓库中,当然大家也可以上传到Docker官方镜像仓库,不过这样的话我们的镜像就成了公共镜像了,谁都可以下载。下面是上传镜像的命令:

Rails和docker构建微服务架构

上传镜像需要一定的时间,具体要传多久视大家的网络情况而定。镜像传完之后,大家只需要运行下面这条命令,就可以把应用跑起来了:

$ swarm up

运行该命令,系统会自动从镜像仓库(Docker官方镜像仓库和Giant Swarm镜像仓库)拉取所需的镜像,选取适当的参数来运行容器,并自动收集容器的运行日志,同时还会将应用发布到http://gh-recommender.gigantic.io上。这一切都是自动完成的,怎么样,够简便吧?

结尾彩蛋:容器伸缩

目前我们的应用还只是为每个组件分配了一个容器。随着用户的增多,难免会出现无法预料的需求增长,这个时候就需要我们提供更多的资源。以往针对这个问题,最常见的做法是增加服务器的台数,但是这样做非常麻烦,还需要进行手动操作,服务器你总需要手动启动吧?将服务器添加到负载均衡器也需要手动操作吧?总之,按照传统的方法来实现动态伸缩是非常复杂的。好在,随着容器技术的出现,我们有了更多选择,比方说大家可以通过Giant Swarm之类的产品来添加应用实例,而且只需要一行代码即可:

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

比起以往那种繁琐的做法,容器技术大大降低了动态伸缩的技术难度,但Giant Swarm之类的产品也不是万能的,它也无法完美解决应用的水平伸缩问题。而且,如果涉及到数据库层面的伸缩的话,这个问题就更复杂了。所以我们最好还是在构建应用的时候就考虑到伸缩性能。不过,容器技术至少可以让我们专注于应用本身,不用再过多地考虑基础设施的细节。

结论

本教程到这里就结束了,希望大家可以下来多多讨论和练习。本文只是一个入门教程,如果大家想要进深的话,这里我可以给大家提供几个相关的学习方向:

本文一开始就说了,容器可以应用于本地开发环境,而且可以说容器是相当适合本地开发环境的,但文中我们并没有深入探讨该如何应用。

容器在本地环境和生产环境中的调试也是一个大问题,容器并非万应灵丹,大家还是要多多注意容器的适用性。

Docker的另一大问题就是安全性。虽然现在也有一些第三方解决方案,但大家还是应该好好熟悉一下容器和Docker的安全机制。这里我说的不是容器本身的安全漏洞,而是从虚拟机或托管服务器之类的传统基础设施设置与容器之间的区别。

还有,建议大家不要太依赖于公共镜像,而是尽可能地构建自己的镜像。使用公共镜像和自定义镜像的差别还是很大的,以文本为例,我们构建了一个简单的应用,只不过有5个容器,就用到了3个不同的linux发行版。

在本文中,我们严格遵循了每个容器只运行一个进程的原则。虽然Docker官方支持这种做法,但这种观点本身就存在争议。老实说我倒是觉得一个容器多个进程的做法也是可行的,大家可以下来深入研究一下,自己来判断对错。

对于新手来说,切记不要一上来就钻研微服务架构,建议大家从Docker入手,先熟悉工具,看看范例,实际练习一下,找到失败的原因,弄清楚Docker对现有工具和进程的影响,然后再往深了走,试着将应用分解成组件,并分块部署到其他地方。大家一定要一步一步地来,不要急于求成。

目前Docker已经非常稳定了,虽然大家不一定会马上用到Docker,但也不妨研究一下,容器技术还是很有意思的。大家可以在非关键业务中试着用一下Docker,适应一下云计算未来的发展方向,相信会对大家有所帮助。

原文  http://www.uml.org.cn/yunjisuan/2016060610.asp
正文到此结束
Loading...