最近撸cloud dea 代码,略作总结。分享开来,抛砖引玉。
下图是cloudfoundry 官方文档上一张app stage 图。原图地址:http://docs.cloudfoundry.org/concepts/how-applications-are-staged.html
从上图可以看出,从step 7 之后才是dea 真正进行staging 的过程。注意,其中cc并不是和dea直接交互的,而是通过nats 的消息机制进行交互。
在看具体的staging过程之前,先大致说下dea,dea 最主要的两个模块是staging 和starting,从名字就可以看出来,staging主要负责app 的staging过程,当dea 收到cc 发送的stage 消息时,主要由这个模块相应,staging过程的最重要的任务就是生成一个droplet,并将该droplet 以及相应的buildpack 发送到blobstore上。starting主要处理app start 的消息,具体的start 过程以后再细说。
staging 部分模块如下:
- StagingTask: 这是staging中最主要的部分,负责完成staging 过程的大部分工作。
- StagingMessage: 负责分析处理从nats接收到的staging 消息
- BuildpackManager: 主要负责buildpack的管理,包括buildpack 的download 以及clean。
- StagingTaskRegistry: 主要维护一个<task_id, task>表,所有正在进行的staging task 都会在这个表中。
- StagingTaskWorkspace: 维护了一个tmp 文件夹和一个staging文件夹,staging文件夹下主要存放了droplet, app package等信息,tmp下主要存放了一些cache和warden log 等信息。
- droplet_registry: 在dea的base dir下有众多的包含droplet 的子目录,形式为/tmp/dea_ng/[sha1]/droplet.tgz, 通过droplets来seed registry.
接下来看下staging的具体的过程。
1. 接收staging 相关消息
在dea 启动过程中,会启动一个nats client,以监听nats 中的信息,
def create_nats_client
clean_servers = URICleaner.clean(config["nats_servers"])
logger.info("nats.connecting", servers: clean_servers)
::NATS.connect(
:servers => config["nats_servers"],
:max_reconnect_attempts => -1,
:dont_randomize_servers => false,
)
end
主要监听的有关staging 的消息主要有三种:staging、staging.dea_id.start、staging.stop。其中staging、staging.dea_id.start是启动一个staging 过程的消息,staging.stop是停止staging 过程的消息。
2.消息的处理
当收到一个staging 消息时,StagingMessage 会分析这个消息从而形成一个message, 在整个staging过程中,这些信息都是很重要的,message 大致可以包含以下几个主要的信息:
Message = {app_id,
properties,
task_id,
download_uri,#保存app files 的blosstore
upload_uri,
buildpack_cache_upload_uri,# 保存buildpack cache的 路径(一般为blobstore)
buildpack_cache_download_uri,
start_message,
admin_buildpacks}
3. staging task 开始前的工作
staging 真正开始之前有一些预备工作需要处理,主要包括:
- 利用StagingMessage 新建一个staging task
- 判断dea 的memory 和disk 是否能满足需求
- 在StagingTaskRegistry中注册task
- 保存snapshot。
接下来就是staging的具体的过程了。
4.staging setup
1. 准备工作空间(StagingTaskwWorkspace),这里主要做了一下几个事情。
a). 在base dir(在bootstrap时通过config来确定)下建立一个tmp 文件夹,这个文件夹下主要存放了 warden_unstaged_buildpack_cache, warden_cache, warden_unstaged_dir 等。
b). 根据StagingMessage 中的buildpack信息下载buildpack
c). 删除本地buildpack中无用的buildpack,只保留StagingMessage 指定的buildpack以及在staging_task_registry中其他staging_task使用的buildpack。
d). 记录log
2. create 一个container,并做一些限制。这里需要特别指出的是,在创建container 时候,bind mounts 是StagingTaskWorkspace中的workspace dir。
def create_container(params)
[:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_inbound_network, :rootfs].each do |param|
raise ArgumentError, "expecting #{param.to_s} parameter to create container" if params[param].nil?
end
with_em do
new_container_with_bind_mounts_and_rootfs(params[:bind_mounts], params[:rootfs])
limit_cpu(params[:limit_cpu])
limit_disk(byte: params[:byte], inode: params[:inode])
limit_memory(params[:limit_memory])
setup_inbound_network if params[:setup_inbound_network]
setup_egress_rules(params[:egress_rules])
end
end
其实,创建container的过程是dea 根据warden 的protocol来提一些request,例如:创建一个container其实就是向warden发出一个 : Warden :: Protocol :: CreateRequest
3.下载app的package,根据StagingMessage的 "download_uri"来下载,app的package 在是由cc将用户的程序上传到blobstore上的,下载dea需要将package下载到本地。
4.下载buildpack,根据StagingMessage 的“buildpack_cache_upload_uri” 来下载。
5. 在warden中运行脚本,建立log dir
6. 在warden中运行脚本,建立app dir.
在warden 中运行脚本,其实也是通过warden request来完成的。
def run_script(name, script, privileged=false, discard_output=false, log_tag=nil)
request = ::Warden::Protocol::RunRequest.new(handle: handle,
script: script,
privileged: privileged,
discard_output: discard_output,
log_tag: log_tag)
response = call(name, request)
if response.exit_status > 0
data = {
script: script,
exit_status: response.exit_status,
stdout: response.stdout,
stderr: response.stderr,
}
logger.warn('%s exited with status %d with data %s' % [script.inspect, response.exit_status, data.inspect])
raise WardenError.new("Script exited with status #{response.exit_status}", response)
else
response
end
end
5.staging
真正的staging的执行,是在warden中进行,dea 通过向warden发送一些请求和命令完成staging过程。一些主要的过程如下
1.unpack_app: 在warden中执行脚本,将app解压。
2.unpack_buildpack_cache :在warden中执行脚本,将下载的buildpack解压
3.staging: 这是在warden中正真执行staging 过程的一步,主要分为两块:a). 在warden中运行staging commond b).link warden job
def staging_command env = Env.new(staging_message, self) [ 'set -o pipefail;', env.exported_environment_variables,#系统环境变量和用户环境变量 config['dea_ruby'], #ruby 路径 /usr/bin/ruby run_plugin_path,# /dea/buildpacks/bin/run workspace.plugin_config_path,# 进行staging 的一些配置 "| tee -a #{workspace.warden_staging_log}" ].join(' ') end
4.pack app: 打包droplet5.copy out: 从/tmp/droplet.tgz 拷贝到/tmp/dea_ng/staging/staged 目录下
6. save droplet: 将/tmp/dea_ng/staging/staged 目录下的droplet拷贝到 droplet registry(也就是到/tmp/dea_ng/[sha1]/droplet.tgz)。是不多很奇怪,为什么上一步中已经将droplet.tgz拷贝一次了,还要再次拷贝呢?主要有两方面的原因,第一方面是将droplet 注册下来,另一方面是因为,在整个staging完成之后,/tmp/dea_ng/staging整个目录是被完全删除掉的。
6. upload app
在staging结束之后,接下来要做的则是将staging 产生的droplet 以及buildpack上传到blobstore上。
1.upload droplet :将/tmp/dea_ng/staging/staged/droplet.tgz 上传到blobstore上
2.upload buildpack cache
a).首先见buildpack 打包
b).将打包好的buildpack 拷贝到/tmp/dea_ng/staging/staged下
c).将buildpack 上传到blobstore
至此,staging的过程基本上已经完成,但还有些收尾的工作要处理,主要有以下的几个工作
7.善后工作~
1.销毁为了staging而创建的warden
2. close warden socket connection.
3. 删除worspace_dir ,也就是/tmp/dea_next/staging 目录。
至此,整个staging过程基本上完成了,限于个人水平问题,难免有些错误或者遗漏,请怕砖~