上一篇文章分析了router的运行原理,在这一篇开始分析dea组件的运行。。。,记得以前自己刚刚开始接触cloud foundry的时候就会觉得dea是最神奇的一个模块,不过看了它的代码之后它的神秘感也就渐渐的消失了。。
首先来看启动的代码:
EM.run {
agent = DEA::Agent.new(config)
agent.run()
}
还是在eventmachine中启动,主要的代码都在agent类中。。。
agent类的构造函数主要是进行一些基本信息的配置,例如app目录的路径,总共可用的内存大小等。。。这里就不一看他的代码了。。主要是来看run函数中的代码,因为重要的部分都在这个里面。。。
begin #用于创建文件夹
FileUtils.mkdir_p(@droplet_dir)
FileUtils.mkdir_p(@staged_dir)
FileUtils.mkdir_p(@apps_dir)
FileUtils.mkdir_p(@db_dir)
if @secure # Allow traversal by secure users
FileUtils.chmod(0711, @apps_dir)
FileUtils.chmod(0711, @droplet_dir)
end
rescue => e
@logger.fatal("Can't create support directories: #{e}")
exit 1
end
这部分代码用于创建dea存放app代码的文件夹,以及其他一些运行信息的代码。。。。接下来就是最为重要的部分了。。。
#启动NATS循环
NATS.start(:uri => @nats_uri) do
# Register ourselves with the system
status_config = @config['status'] || {}
VCAP::Component.register(:type => 'DEA',
:host => @local_ip,
:index => @config['index'],
:config => @config,
:port => status_config['port'],
:user => status_config['user'],
:password => status_config['password'])
uuid = VCAP::Component.uuid
@logger.info("DEA uuid #{uuid}")
# Setup our identity
@hello_message = { :id => uuid, :ip => @local_ip, :port => @file_viewer_port, :version => VERSION }.freeze
@hello_message_json = @hello_message.to_json
# Setup our listeners..
NATS.subscribe('dea.status') { |msg, reply| process_dea_status(msg, reply) } #用于报告dea的状态
NATS.subscribe('droplet.status') { |msg, reply| process_droplet_status(msg, reply) } #用于报告app的状态
NATS.subscribe('dea.discover') { |msg, reply| process_dea_discover(msg, reply) } #controller发送的查找合适的运行环境的询问
NATS.subscribe('dea.find.droplet') { |msg, reply| process_dea_find_droplet(msg, reply) } #在dea中寻找对应的droplet
NATS.subscribe('dea.update') { |msg| process_dea_update(msg) }
NATS.subscribe('dea.stop') { |msg| process_dea_stop(msg) }
NATS.subscribe("dea.#{uuid}.start") { |msg| process_dea_start(msg) }
NATS.subscribe('router.start') { |msg| process_router_start(msg) } #相当于是router的心跳广播消息,dea收到消息后向router报告当前dea的一些基本信息
NATS.subscribe('healthmanager.start') { |msg| process_healthmanager_start(msg) } #healthmanger的广播消息,也是用于报告一些消息
NATS.subscribe('dea.locate') { |msg| process_dea_locate(msg) } #用于通告当前dea的一些环境信息
# Recover existing application state.
recover_existing_droplets
delete_untracked_instance_dirs
EM.add_periodic_timer(@heartbeat_interval) { send_heartbeat } #10
EM.add_periodic_timer(@advertise_interval) { send_advertise } #5
EM.add_timer(MONITOR_INTERVAL) { monitor_apps } #监视app的状态
EM.add_periodic_timer(CRASHES_REAPER_INTERVAL) { crashes_reaper }
EM.add_periodic_timer(VARZ_UPDATE_INTERVAL) { snapshot_varz }
EM.add_periodic_timer(DROPLET_FS_PERCENT_USED_UPDATE_INTERVAL) { update_droplet_fs_usage }
NATS.publish('dea.start', @hello_message_json) #发布消息,表示当前这个dea已经启动了,告诉别人
send_advertise
首先注册该dea组件,接着要在nats中订阅以及发布消息,最后还要向整个系统发送广播消息,表示当前dea模块已经启动了。。。
这样,整个模块就已经启动了,接下来dea就开始进入消息循环了,不断的等待nats的消息,并发送一些心跳消息等。。。
这里就分析几个重要的就可以了。。。首先是:
NATS.subscribe('dea.discover') { |msg, reply| process_dea_discover(msg, reply) } #controller发送的查找合适的运行环境的询问
该消息是cc模块发送过来的,当dea接收到这个消息之后会根据发送过来的数据进行分析,判断当前的运行环境是否满足要求,如果满足的话,那么会向cc返回消息,如果不满足的话,那么就会忽略这个消息。。。另外有一个小的插曲就是,dea会根据当前所运行的app的负载状态来推迟回复消息的时间,而cc会选择第一个回复该消息的dea运行该app。。。
#这个函数用来处理从nats收到的controller用来查找合适的运行环境的查询,如果不满足,那么dea将不会向controller返回消息
def process_dea_discover(message, reply)
return if @shutting_down
@logger.debug("DEA received discovery message: #{message}") #msg一般是需要运行的app的一些基本的信息
message_json = JSON.parse(message)
# Respond with where to find us if we can help.
if @shutting_down #dea是否已经停止了
@logger.warn('Ignoring request, shutting down.')
elsif @num_clients >= @max_clients || @reserved_mem > @max_memory #判断app的数目是否已经超过了限制
@logger.warn('Ignoring request, not enough resources.')
elsif droplet_fs_usage_threshold_exceeded?
@logger.warn("Droplet FS has exceeded usage threshold, ignoring request")
else
# Check that we properly support the runtime requested
unless runtime_supported? message_json['runtime_info'] #判断运行环境是否满足要求
@logger.debug("Ignoring request, #{message_json['runtime_info']} runtime not supported.")
return
end
# Ensure app's prod flag is set if DEA's prod flag is set
if @prod && !message_json['prod']
@logger.debug("Ignoring request, app_prod=#{message_json['prod']} isn't set, and dea_prod=#{@prod} is.")
return
end
# Pull resource limits and make sure we can accomodate
limits = message_json['limits']
mem_needed = limits['mem']
droplet_id = message_json['droplet'].to_s
if (@reserved_mem + mem_needed > @max_memory)
@logger.warn('Ignoring request, not enough resources.')
return
end
delay = calculate_help_taint(droplet_id)
delay = ([delay, TAINT_MAX_DELAY].min)/1000.0
EM.add_timer(delay) { NATS.publish(reply, @hello_message_json) } #之所以要延迟返回,时为了平衡各个dea之间的压力,因为压力较小的dea先返回
end
end
上述就是discovery消息的处理函数,其实也很简单,说白了就是判断当前的运行环境以及内存等情况是否满足app的需求。。。
接下来再来看一个十分重要的消息:
NATS.subscribe("dea.#{uuid}.start") { |msg| process_dea_start(msg) }
这个是从cc发送过来的运行某个app的消息。。。该消息的处理函数首先会根据发送过来的消息生成相应的app的信息,接着会获取该app的源代码,然后再进行启动,由于代码比较长,我们就重点来看app是怎么启动的吧。。。
@logger.debug('Completed download')
if not instance[:uris].empty?
port = grab_port #用于获取app的运行端口
if port
instance[:port] = port
else
@logger.warn("Unable to allocate port for instance#{instance[:log_id]}")
stop_droplet(instance)
return
end
else
@logger.info("No URIs found for application. Not assigning a port")
end
if debug
debug_port = grab_port
if debug_port
instance[:debug_ip] = VCAP.local_ip
instance[:debug_port] = debug_port
instance[:debug_mode] = debug
else
@logger.warn("Unable to allocate debug port for instance#{instance[:log_id]}")
stop_droplet(instance)
return
end
end
if console
console_port = grab_port
if console_port
instance[:console_ip] = VCAP.local_ip
instance[:console_port] = console_port
else
@logger.warn("Unable to allocate console port for instance#{instance[:log_id]}")
stop_droplet(instance)
return
end
end
首先是为app分配运行时端口等信息,
#启动app的函数
exec_operation = proc do |process|
process.send_data("cd #{instance_dir}\n")
if @secure || @enforce_ulimit
process.send_data("renice 0 $$\n")
process.send_data("ulimit -m #{mem_kbytes} 2> /dev/null\n") # ulimit -m takes kb, soft enforce
process.send_data("ulimit -v 3000000 2> /dev/null\n") # virtual memory at 3G, this will be enforced
process.send_data("ulimit -n #{num_fds} 2> /dev/null\n")
process.send_data("ulimit -u 512 2> /dev/null\n") # processes/threads
process.send_data("ulimit -f #{disk_limit} 2> /dev/null\n") # File size to complete disk usage
process.send_data("umask 077\n")
end
#设置环境变量,书要是访问服务的一些环境变量
app_env.each { |env| process.send_data("export #{env}\n") }
if instance[:port]
process.send_data("./startup -p #{instance[:port]}\n")
else
process.send_data("./startup\n")
end
process.send_data("exit\n")
end
exit_operation = proc do |_, status|
@logger.info("#{name} completed running with status = #{status}.")
@logger.info("#{name} uptime was #{Time.now - instance[:start]}.")
stop_droplet(instance)
end
# Being a bit paranoid here and wipe all processes for the secure user
# before we start..
kill_all_procs_for_user(user) if @secure
#用于真正的启动
Bundler.with_clean_env { EM.system("#{@dea_ruby} -- #{prepare_script} true #{sh_command}", exec_operation, exit_operation) }
接着调用bundler来启动app,在着了我们可以看到
process.send_data("renice 0 $$\n")
process.send_data("ulimit -m #{mem_kbytes} 2> /dev/null\n") # ulimit -m takes kb, soft enforce
process.send_data("ulimit -v 3000000 2> /dev/null\n") # virtual memory at 3G, this will be enforced
process.send_data("ulimit -n #{num_fds} 2> /dev/null\n")
process.send_data("ulimit -u 512 2> /dev/null\n") # processes/threads
process.send_data("ulimit -f #{disk_limit} 2> /dev/null\n") # File size to complete disk usage
process.send_data("umask 077\n")
这些用于设置app的运行时的一些资源限制。。。。
app_env.each { |env| process.send_data("export #{env}\n") }
这句代码用于给app设置环境变量,其实这些环境变量大多是用于访问服务的一些信息,例如访问数据库的url等信息。。在app当中就可以通过这些环境变量的信息来访问服务了。。。。
if instance[:port]
process.send_data("./startup -p #{instance[:port]}\n")
else
process.send_data("./startup\n")
end
process.send_data("exit\n")
最后就是调用启动脚本具体启动app了。。。
detect_app_ready(instance, manifest) do |detected|
if detected and not instance[:stop_processed]
@logger.info("Instance #{instance[:log_id]} is ready for connections, notifying system of status")
instance[:state] = :RUNNING
instance[:state_timestamp] = Time.now.to_i
send_single_heartbeat(instance)
register_instance_with_router(instance) #向router注册app的信息
schedule_snapshot
else
@logger.warn('Giving up on connecting app.')
stop_droplet(instance)
end
end
最后分析app的运行情况,并向router发送注册刚刚启动的app的信息。。。
好了,dea最重要的部分就已经分析完成了。。。
其余的消息还有
NATS.subscribe('dea.status') { |msg, reply| process_dea_status(msg, reply) } #用于报告dea的状态
用于报告当前dea的运行状态。。。
NATS.subscribe('droplet.status') { |msg, reply| process_droplet_status(msg, reply) } #用于报告app的状态
用于报告droplet的状态
嗯,,还有其余的。。就不一一列举出来了。。。。