这篇文章结构比较糟,我在http://lory-yang.com/2012/09/09/cloud-foundry-dea/中做了一些改进,可以去那里看看。另外我的博客整体搬迁到独立地址了:lory-yang.com,欢迎大家来访问
本文是对Cloud Foundry中的DEA组件源码的一些分析。有点流水帐的感觉,就事论事吧,同时因为本人能力有限,而且研究不深,错误在所难免。诸位就随便看看,希望大家能获得些帮助。有关Cloud Foundry的基础知识可以参考:http://qing.weibo.com/2294942122/88ca09aa330004r8.html, http://qing.weibo.com/2294942122/88ca09aa33000975.html
和其他模块一样,DEA也是使用ROR的目录组织方式,主要代码均集中在lib文件夹中,而在config文件夹下面有一个yaml配置文件。
yaml配置文件:
可以看到文件里面的注释相当完善了,配置的项基本上都可以看懂的。
1. lib文件夹中的代码:
精简版:这是下面所有内容的简单描述:
DEA通过dea.rb来启动,dea.rb调用agent.run来启动DEA服务。DEA启动时会向NATS订阅所有自己需要的消息,然后当消息传来时,进行相应的处理,包括status、start、stop、discover、heartbeat等各种处理。droplet在DEA中的运作主要是:首先将staged file拿过来,然后解压至instance目录,最后执行其中的startup脚本,停止则执行stop脚本。
接下来是比较详细的介绍:
1.1 dea.rb
dea.rb相当于一个启动dea的脚本,里面内容相当简单。主要就是负责config,这些config可以来自于config文件,比如yaml文件,或者传入的opts。然后就new了一个agent对象,调用run函数。这个agent就会负责处理所有的DEA承担的任务。
其中EM就是Event Machine,简单讲就是一个基于事件的工具,可以用来网络编程和并发编程。它主要的特色是免去了并发编程的繁杂,同时提供了易用的网络通讯接口。可以看这里:http://www.infoq.com/cn/news/2008/08/eventmachine
然后看lib文件夹,总共有三个文件,其中一个就是agent,另外两个是secure和directory。
1.2 secure:
暂时没有深入研究secure问题,所以DEA中的所有的secure环节会被略去。从大致的功能上讲,secure就负责划分user和group,每次的操作会验证用户所在的组的权限。同时创建的app也会chown给特定的group。
1.3 directory:
directory利用Rack::File提供了一个对root目录的定制访问功能,在agent中只有一个地方用到,就是start_file_viewer函数,这个函数的注释表示:This is for general access to the file system for the staged droplets。也说的很明白了。在DEA start时,会让EM去不断地启动droplets的文件访问,Periodically try to start the file viewer in case of port contention。
1.4 agent:
这个文件非常长,但是结构其实比较简单。各种参数大部分也能了解其含义了,这里也不说了。主要看agent类的结构。
首先当然有构造函数,主要就是设置参数,就是DEA环境的各种配置,没有其他好说的。
1.4.1 run函数
然后是run函数,我们知道了dea.rb文件中就是调用了这个run函数,那么说明这个函数可以把DEA启动起来,开始为CF提供服务了。这里是整篇文章的一个重点了,我们仔细的看下去:
- 创建一个pid file,就是创建一个process id,当前这个DEA就是使用这个id标注了。
- 配置了一些参数,这个就略去了。
- 在180行创建了所有需要的路径,有兴趣可以全部用log打出来看看。
- 将staged dir清除干净,这个dir就是用来存放被打包(staged)的droplet。
- 然后可以看到上面提到的start_file_viewer,开启staged dir的文件访问
['TERM', 'INT', 'QUIT'].each { |s| trap(s) { shutdown() } }
如果EM捕捉到这些信号,就shutdown- 然后就是最重要的开启NATS服务了,大部分的CF模块都通过NATS交互,NATS是一个消息中间件,用来订阅和发布JSON格式的消息。首先将自己注册到Component中,然后可以获得Component提供的uuid。然后就向NATS订阅了一堆的消息,每个消息都是DEA可以提供的一个功能,其他组件如果需要使用DEA的某个功能,就只需要向NATS发布一个相对应的消息即可。文章后面会对其中几个复杂的功能做一些介绍。
- 由于是启动,所以要恢复已经存在的droplets删除不需要的instances
- 向EM添加了定时运行的事件,比如heartbeat之类的。
- 最后向NATS发布一个dea start消息就算启动完成了
2. DEA的各个主要功能
接下来是DEA向NATS订阅的一些消息以及处理函数
2.1 dea.uuid.start:启动一个droplet的instance
就是让dea启动一个droplet,对应函数是process_dea_start,start函数也比较长,慢慢看。
- 从message里面解析出n多的参数,基本上就是droplet和app的一些参数。
- 做一些mem和fs usage的判断,是否有足够的mem和disk
- 接下来可以看到:
这个tgz_file就是staged droplet。sha1当初研究了很久,不解其意,这里可以看到其实就是droplet的一个标识,目前手头没有实例可以验证,但是app的目录名称应该也和这个sha1相关。tgz_file = File.join(@staged_dir, "#{sha1}.tgz") instance_dir = File.join(@apps_dir, "#{name}-#{instance_index}-#{instance_id}")
- 然后创建了一个instance的map,这个map包含了instance的所有信息,然后处理了一下DEA自己的域。
- 然后可以看到一个lambda: start_operation。顾名思义就是用来start一个instance的。前面都是一些特殊的配置,暂时不研究了,看主要部分:line 669,这里将DEA项目的bin目录下的close_fds文件拷贝至instance目录下,然后执行,看字面意思是prepare。line697: 获得一个app_env数组,就是instance的一些环境变量,后面会把每个值都export。line 719,就是启动instance的代码了,可以看到只是简单的调用了startup脚本而已,这个脚本就是在stage的时候由stager加入的启动脚本。
- 然后启动了一个纤程Fiber,首先将staged文件放置到staged dir中(如果有cache最好,否则会去shared folder取,或者会HTTP去读),然后调用start_operation lambda。
2.2 dea.status&droplet.status:查看状态
这两个比较简单,就是把一些状态的字段(name, host, port, mem, disk, uri...)返回回去。
2.3 dea.discover:发现DEA
意思就是当一个droplet需要一个instance时,cloud controller会向NATS广播dea.discover消息,那么DEA收到之后,会根据自己情况选择是否回复此消息。当CC受到第一个回复时,就是用发送这个消息的DEA创建instance。
我们可以看到process_dea_discover这个函数首先主要判断是否能够跑这个app,包括mem、disk、runtime等限制条件。然后会根据DEA当前的情况计算一个delay,然后在delay之后发送response。这个delay相当于在做balance,比如:一般mem使用量大的DEA会delay比较长,所以比较难以被选中。
2.4 dea.find.droplet:查找droplet
很简单,就是通过droplet id查找对应的droplet信息
2.5 dea.update:更改droplet的URI
就是message中包含了droplet id,然后一个uri,那么DEA会修改droplet当前的uri,然后去router做修改
2.6 dea.stop:停止droplet的instance
通过message中指定的droplet id找到droplet,然后根据其余的信息(version, instance id, index, state)filter instance,将这些filter出来的instances停掉。 stop instance流程:首先从router unregister,然后向NATS发布本instance stop的消息,然后调用instance目录下的stop脚本来停止instance,最后将instance目录清理干净
2.7 router.start:向router注册
这里涉及到router的运作方式,router也是通过NATS和其他组件协作的,也就是发布router.start来告诉别人router已经就位,你们可以来注册了。为了应付任何时候可能加入的组件,router会不断地发送router.start消息。而dea中也会每次都傻傻的接受到router.start信息,同时做出处理。所以可以看到dea.log里面会有大量的“DEA received router start message”字段。恩,看起来这么做还是有必要的。我们设想重启了router,那么router必须重新要求所有app来注册,那么他也只能发一个router.start,如果dea做过一次就不做了,那么新的router就无法更新了。
然后处理router.start的过程很简单,就是将所有running的组件注册到router中去。
其他的就不提了,也比较简单,什么heartbeat之类的,大家都懂的
(这里还有一块内容需要补充,就是定期执行的一些操作,比如app状态(CPU,内存等)的update等,还是有一定学习意义的)