cloud foundry之dea组件源码分析

上一篇文章分析了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的状态

嗯,,还有其余的。。就不一一列举出来了。。。。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值