cf之app解读

    笔者小菜,接触CF以来一直在读代码,其中重点是app,针对app的增删改读,以及app状态监控与错误处理,笔者进行了一定粒度的总结,不太可能很详细,敬请指教。本文的思路像是讲一个悠长的故事,一个个函数地讲,既是为了满足实验室修改代码的需求,也是为了让读者能够沿着代码的轨迹,对照着本文一步步地走下去。

HealthManager

    run函数中定义了一个处理nats连接错误的函数、一个EM连接错误的函数后,调用了三个函数:

configure_timers

一、此函数首先执行update_from_db,这里采用了next_tickadd_periodic_timer合用的方式,前者实现将一个块调度到reactor的下一次迭代时执行,后者实现间隔一段时间执行,代码里有很多这样的写法,这里不详述,可参考实验室resouer的博文Research on EventMachineupdate_from_db函数主要做两件事

        1.通过ensure_connected函数连接数据库后,有代码如下

old_droplet_ids =Set.new(@droplets.keys)
App.all.each do |droplet|
old_droplet_ids.delete(droplet.id)
update_droplet(droplet)
end
    上面这段代码中表示将实例变量@droplets中的数据用数据库中读出来的droplet的信息更新,其实所有的droplet都存在述数据库中,但我们使用的时候不能每次从数据库中访问,所以在hm的run函数中需要首先取出来。

     2.update_from_db中更新了一些状态信息,包括app的数量、startedapp的数量、mem等。

二、然后,在update_from_db后,同样地,采用next_tickadd_periodic_timer结合的方式,执行analyze_all_apps函数,不同的是add_periodic_timer的外面包了一个add_timer(@droplet_lost),注释很清楚,由于在analyze_all_apps函数中,主要完成的工作是对于所有droplet的分析,即坏掉的要stop,冗余的要delete,所以需要等一个droplet_lost的时间以保证所有droplet的心跳都已收到,有关心跳也就是状态app的监测后文详述。

    在analyze_all_apps中调用两个函数:

prepare_analysis(collect_stats) #为下面做一些初始化,构建了一个名为@analysis的键值对集合
perform_and_schedule_next_quantum

        这里perform_and_schedule_next_quantu中循环n次做函数perform_quantum,n为droplet的个数。perform_quantum中调用了实现主要功能的analyze_app函数,然后更新键值对集合analysis中instances的值以及表示已当掉的droplet的数量crashed的值。

       下面介绍重点analyze_app,这个函数针对某个app,其思路是这样的:

1.初始化,若APP_STABLE_STATES包含当前droplet的state,则标记extra_instances和missing_indices都为空
2.若此instance对应的droplet的状态是STOPPED||droplet的index已超过droplet的数量||droplet的版本不是当前当前活跃着的版本,那么extra_instance变量记为true,下面变量extra_instances则记录下此instance
3.若此instance对应的droplet是STARTED的并且此app曾经完成过stage过程(sanity函数检查是否完成stage),并且状态是DOWN||droplet距离上次使用的时间超过重启时间,则missing_indices记录下此instances的index
4.根据前面的标记与记录变量,若extra_instances有值,则调用stop_instance函数将其记录的instance停掉,若missing_indices有值,则调用start_instances函数将此其记录的instance重启

三、处理请求队列

      如果请求队列不为空,则每隔1秒调用deque_a_batch_of_requests函数处理请求,此函数中调用publish_start_message函数对每个请求进行处理。此函数稍候详述。
      至此config_timers函数介绍完毕,简单小结,此函数就是用数据库中droplet信息更新了内存中的droplet变量;管理app,stop一些失效的instance,start一些missing_indices的instance;处理一些遗留的请求。

register_as_component

        hm启动的时候向Component注册自己的信息,这里的Component,有组件注册时,会为这个组件启动一个server,我们能够访问相应的url以获得组件的信息。注册完毕即可向Component的varz写信息了。有关varz用法与原理,请阅读实验室cherry_sun的博文cloudfoundry的状态监控:varz

subscribe_to_messages 

     在这个函数中hm订阅一些消息,订阅了如下类型的消息:
      dea.heartbeat#dea发来的instance的心跳
      Droplet.exited#droplet退出的消息
      Droplet.updated#droplet更新的消息
      Healthmanager.status#请求healthmanager的状态的消息
      Healthmanager.health#询问healthmanager是否健康的消息
      router.active_apps#router发来的询问有效app的消息
      这些subscribe除了后三项在下文中的状态监控部分会有讲解,然后hm又发布了一个消息告诉大家它已经在工作了NATS.publish('healthmanager.start')。这些订阅消息会在下文中的状态监控中介绍。

app的状态监控

      app的实现主要就是在dea中的agent.rb文件中。下面讨论几种app状态监控的情况:

   1.cc创建完一个instance

   如上文所述,在process_dea_start()中处理了instance的创建,在检测到instance创建成功后(detect_app_ready函数中检测app的状态是否为Running),就send_single_heartbeat(instance),此函数是向hm发送一个心跳。

       Heartbeat的内容包括:

      heartbeat = {
        :droplets => [generate_heartbeat(instance)],
        :dea => VCAP::Component.uuid,
        :prod => @prod
      }
      NATS.publish('dea.heartbeat', heartbeat.to_json)

    这里publish了一个类型为dea.heartbeat的消息,此消息被HM订阅,在[cloud_controller/health_manager/lib/health_manager.rb]中hm启动时就在函数subscribe_to_messages中订阅了许多消息:

    NATS.subscribe('dea.heartbeat') do |message|
      @logger.debug { "heartbeat: #{message}" }
      process_heartbeat_message(message)
    end

    那么当NATS接收到类型为dea.heartbeat的消息时先写log,然后回调proces_heartbeat_message函数,那么现在详细讲解下hm收到心跳后做的事,也就是函数process_heartbeat_message

    取出heartbeat传过来的instance记为instance;再取出数据库中的droplet_entry

      droplet_id = heartbeat['droplet'].to_s
      instance = heartbeat['instance']
      droplet_entry = @droplets[droplet_id]

    然后做一些事,这里的判断比较多,下面是一段不是很美观的似伪代码解释如下:

if(droplet_entry){
    表示droplet是存在的,则:
    if(heartbeat的state是starting或running){
       从droplet_entry中根据heartbeat提供的index和version取出index_entry,如果数
       据库中的instance与heartbeat传过来的instance不是同一个,那么stop这个instances
    }
    else if(state是CRASHED){
      表示这个心跳是CRASHED心跳,更新crash的时间戳
    }
}else {
    表示这个app是unknown的,则:
    算出这个instance的时间戳距离现在的时间instance_uptime,如果这个hm已经运行了足够久(>threshold),
    但instancer仍很久没有发心跳(instance_uptime>定值threshold),则stop这个instance.
}
      上文中多次用到的stop_instances解释如下:
       参数为相应的droplet_id和instances。整理stop消息,包括将message的op置为stop后发送publish消息:

NATS.publish("cloudcontrollers.hm.requests.#{@cc_partition}", stop_message)

        grep了一下发现cc中的[cloud_controller/cloud_controller/app/subscriptions/health_manager_channel.rb]订阅了这条消息,这个health_manager_channel.rb文件其实就是接收一些消息的,真正处理的文件是在models中,所以里面有调用处理函数的语句:App.process_health_manager_message(payload),我们进入到app.rb中的这个函数:考虑一种情况,如果传过来的app不存在了,则立刻发送一个dea.stop的消息、如果存在,再去处理这个传过来的消息,在appmanager中的health_manager_message_received函数中进行处理:

case payload[:op]
when/START/i
……
when/STOP/i
if payload[:last_updated]==app.last_updated
stop_msg = { :droplet=> app.id, :instances=> payload[:instances]}
NATS.publish('dea.stop',Yajl::Encoder.encode(stop_msg))
end
when/SPINDOWN/i
……
    这里判断传过来的消息的op是什么,当是stop时,判断如果收到的是app的旧的版本,则不管,将其留在系统中,如果正是最新版本,那么同样地发布一个类型为dea.stop的消息。
    显然,在dea的agent.rb文件中订阅了此消息,在process_dea_stop 函数中对其进行处理,这里与app的stop指令实现的方式是一样的。
遍历数据库中的所有droplet对应的instance,找到那个传过来的instance:
instances.each_value do |instance|
……
if (version_matched && instance_matched && index_matched && state_matched)
……
        找到后,设置instance的exit_reason值,下面会有用处的。另外如果它的状态已经为CRASHED,则将state值改为DELETED,无论是什么状态都最后会去执行stop_droplet(),此函数中会去调用函数send_exited_message 发送一些退出信息:
unregister_instance_from_router(instance) #告诉router,这个instance已经没有了,
#以后不要将访问app的请求路由过来了
send_exited_notification(instance) #此函数中会发送类型为droplet.exited的消息
droplet.exited的消息被hm订阅,在process_exited_message()中处理,这个函数主要是在instance被stop后,更新一些值。
droplet_entry = @droplets[droplet_id]
if droplet_entry
version_entry = droplet_entry[:versions][version]
if version_entry
index_entry = version_entry[:indices][index]
end
……
       这一段就是取出数据库中记录的droplet的值,这一段有利于理解这常用的几个变量的意义以及之间的关系。
       下面就是根据exit_reason进行处理,exit_reason大致有四种:CRASHED、DEA_SHUTDOWN、DEA_EVACUATI、STOPPED,前三种都是属于意外事故或者instance不完整,是需要进恢复的stop,而STOPPED是正常的stop。所以下面的代码中,如果exit的reason是RESTART_REASONS (RESTART_REASONS是一个set包括CRASHED, DEA_SHUTDOWN, DEA_EVACUATION这三个值,也就是instance本身坏了、dea关掉了以及dea当了这三种原因),那么分类进行一些状态和时间戳的设置,再调用start_instances函数进行instance的恢复。
现在有必要总结下instance的几种exit_reason以及几种state:
CRASHED
       在[dea/lib/dea/agent.rb]中的process_dea_start 函数中,也就是新建一个instance的回调函数中,instance因为stage过程未完成,此instance必须被stop,这里会设置其exit_reason为CRASHED,state也是CRASHED,代码如下所示:
instance[:state]=:CRASHED
instance[:exit_reason] = :CRASHED
instance[:state_timestamp] = Time.now.to_i
stop_droplet(instance)
       CRASHED是一个很糟糕的exit_reason,表示这个instance是不完整的,如果有DEA_EVACUATION和DEA_SHUTDOWN的情况也不能取代这个糟糕的原因,从下面的代码中就可看到:
instances.each_value do|instance|
# skip any crashed instances
next if instance[:state] == :CRASHED
instance[:exit_reason] = :DEA_EVACUATION

instances.each_value do|instance|
# skip any crashed instances
instance[:exit_reason] = :DEA_SHUTDOWN unless instance[:state] == :CRASHED
       当instance的exit_reason为空时,就是不知道什么原因时,就做最坏处理,此时也会设置成CRASHED,state也会设置成CRASHED,在dea的sned_exited_message函数中代码如下:
unless instance[:exit_reason]
instance[:exit_reason] = :CRASHED
instance[:state] = :CRASHED
end

DEA_SHUTDOWN

['TERM', 'INT', 'QUIT'].each { |s| trap(s) { shutdown() } }
    这是在dea的agent.rb中捕获到这三者时会调用shutdown函数,shutdown函数中skip掉CRASHED状态的instance,将dea上的其他所有的instance的exit_reason置为DEA_SHUTDOWN。

DEA_EVACUATION

trap('USR2') { evacuate_apps_then_quit() }
     Dea捕获到USR2错误时调用函数evacuate_apps_then_quit(),这里同样的,skip掉CRASHED状态的instance,将dea上的所有其他的instance的exit_reason置为DEA_EVACUATION。

STOPPED

     当instance的状态很正常,如state是STARTING或者是RUNNING时,又需要stop这个instance时,基本这种情况就是正常的stop命令,这时会将exit_reason设置为STOPPED,函数process_dea_stop中代码如下:

instances.each_value do |instance|
        version_matched = version.nil? || instance[:version] == version
        instance_matched = instance_ids.nil? || instance_ids.include?(instance[:instance_id])
        index_matched = indices.nil? || indices.include?(instance[:instance_index])
        state_matched = states.nil? || states.include?(instance[:state].to_s)
        if (version_matched && instance_matched && index_matched && state_matched)
          instance[:exit_reason] = :STOPPED if [:STARTING, :RUNNING].include?(instance[:state])
          if instance[:state] == :CRASHED
            instance[:state] = :DELETED
            instance[:stop_processed] = false
          end
          stop_droplet(instance)
        end
end

     这里涉及instance的几个状态,状态有CRASHED、STOPPED

CRASHED

         在没有完成stage过程的情况下被设置,是很糟糕的状态,在设置其他状态时会skip掉这个状态,如在dea的stop_droplet函数中:

instance[:state] = :STOPPED unless instance[:state] == :CRASHED

     在process_dea_stop中找到了相应的需要stop的instance后,判断此instance的状态,如果它的state是CRASHED的话,那么就将其state状态改为DELETED,注意这里改的是state不是exit_reason,也就是说,对于这样糟糕的状态的instance,stop请求被hm接收后,会因为exit_reason为CRASHED而重新start,那么原来的instance的state在恢复之前就被设置为DELETED,表示已经不存在了,代码如下:

if instance[:state] == :CRASHED
            instance[:state] = :DELETED
            instance[:stop_processed] = false
end

     另外对于CRASHED的instance,系统有一个定时工作的crashes_reaper来检测然后做delete处理,这个会在后面的dea启动时加的几个EM计时器里面详细讲述。
STOPPED、STARTED
    即正常的被stop了的instance和已经start的instance。dea的update_app_from_params函数中调用的update_app_state(app)函数,判断此次更新是将app开启还是停止,然后将app的state设置成相应的:
def update_app_state(app)
    return if body_params.nil?
    state = body_params[:state]
    return if state.nil? || app.state.to_s =~ /#{state}/i
    case state
         when /STARTED/i
            app.state = 'STARTED'
         when /STOPPED/i
            app.state = 'STOPPED'
    end
end

flapping
         总的来说,Flapping状态表示一个instance是不稳定的,当一个instance总是crash,那么它的状态就会被设为flapping。
在healthmanager的version2.0中,也就是最新版中,对于flapping的状态交代的比较清楚,由于实验室里集群中hm没有更新到2.0,所以以上所有的分析都是基于hm旧版的,hm2.0对于app状态监控的处理更加完善,同时也稍改了一些函数的名称,比如,下面要讲的hm2.0中的process_droplet_exited(message) 函数实际就是hm1.0中的process_exited_message函数,他们都是接收到droplet.exit类型的消息之后的回调函数,订阅的代码如下:

NATS.subscribe('droplet.exited')do|message|
    process_droplet_exited(message)
end
       在函数process_droplet_exited(message)中:
Case message['reason']
     when CRASHED
         varz.inc(:crashed_instances)
         droplet.process_exit_crash(message)
     when DEA_SHUTDOWN, DEA_EVACUATION
         droplet.process_exit_dea(message)
     when STOPPED
         droplet.process_exit_stopped(message)
end
        判断退出的原因是什么,如果是"CRASHED",则调用process_exit_crash函数,此函数中,首先计算在flapping_timeout时间内的crash的次数:
instance['crashes']=0iftimestamp_older_than?(instance['crash_timestamp'],AppState.flapping_timeout)
instance['crashes'] = 1
instance['crash_timestamp'] = message['crash_timestamp']
       然后判断如果instance它crash的次数已超过了AppState.flapping_death,那么将其状态置为FLAPPING
if instance['crashes']>AppState.flapping_death
   instance['state'] = FLAPPING
end

2.Dea启动

       在dea启动时订阅了一些消息,之后 又加了很多的EM的计时器,这里是app状态监控的很重要的内容,下面依次介绍。
EM.add_periodic_timer(@heartbeat_interval) { send_heartbeat }
       这个是定时向hm发送所有的instance的心跳,这个过程跟单个instance启动时发送的心跳处理是相同的,都是发送类型为dea.heartbeat的消息。
EM.add_periodic_timer(@advertise_interval) { send_advertise }
       这个是定时发送一个广告,做什么呢,其实是监测这个dea的mem的,grep发现这个消息只被cc订阅了,这里对于mem监测的处理也有点特殊的。
在[cc/app/subscriptions/dea_advertise_channel]中订阅了此消息,在处理此消息时又publish了一个dea.locate的消息,而dea.locate消息只被dea订阅了,但在处理此消息的函数中又是publish了类型为dea.advertise的消息。有点乱,画个图就是这样的:

    左边代表的是em定时器的循环,不断发送advertise消息,一般监测的实现方式就是定期发送,但这里还有一个从cc发出的locate消息,这个消息会使得dea再发一个advertise,这个循环相当于cc主动请求的。个人理解可能是对于mem的监测很重要,需要保证实时的监控,所以才这样。在dea启动时、创建完instance后、删除droplet时,都需要发送advertise告诉cc,目前这个dea有多少可用的mem,advertise内容如下,计算了一个可用mem:

advertise_message = {
        :id => VCAP::Component.uuid,
        :available_memory => @max_memory - @reserved_mem,
        :runtimes => @runtime_names,
        :prod => @prod
      }
NATS.publish('dea.advertise', advertise_message.to_json)

EM.add_periodic_timer(CRASHES_REAPER_INTERVAL) { crashes_reaper }

        这个是一个对于CRASHED的app的反应器,定期执行函数crashed_reaper,进行清理。

@droplets.each_value do |instances|
    # delete all crashed instances that are older than an hour
    instances.delete_if do |_, instance|
        delete_instance = instance[:state] == :CRASHED&& Time.now.to_i - instance[:state_timestamp] > CRASHES_REAPER_TIMEOUT
        if delete_instance
           @logger.debug("Crashes reaper deleted: #{instance[:instance_id]}")
           EM.system("rm -rf #{instance[:dir]}") unless @disable_dir_cleanup
        end
         delete_instance
    end
end
        这里函数很容易理解,遍历所有的instance,删掉状态为CRASHED超过CRASHES_REAPER_TIMEOUT(1h)的状态。
3.Healthmanager启动

    还记得hm在启动后发送了一个消息告诉大家这个hm在工作了。其实此消息只被dea订阅了。在dea中收到此消息后回调函数process_healthmanager_start,这个函数中记了log后调用send_heartbeat函数,此函数遍历所有的droplet的所有instance,每个instance都向这个新启的hm发送一个心跳。心跳的处理就跟上文中的一样的。

App的增删改读

    CF中最核心的就是app的处理,app有apps、push、delete、update、start、stop、reststart这几个重要的命令,分别对应app的读、增、删、改、启动、停止、重启。下面就依次介绍这几个命令的实现,因为笔者阅读的时候比较细也比较乱,所以这里采用的是函数到函数的理解,某些函数可能会深入讲解,某些会大致讲解,同时会注重总结归纳,不足之处请指教。
首先从vmc开始,vmc是通向应用平台的一个命令行接口,[vmc / lib / cli / commands / apps.rb]目录下可看到关于app的诸多命令的接口的定义,在[vmc / lib / vmc / client.rb]中是app的各命令接口的一些实现函数。
vmc apps
      在apps.rb中list函数表示对所有apps的列表显示

      apps = client.apps
      apps.sort! {|a, b| a[:name] <=> b[:name] }
      return display JSON.pretty_generate(apps || []) if @options[:json
      调用client.rb文件中的apps函数,此函数返回apps的所有状态,包括name、instances、health、url、services。
     
      apps_table = table do |t|
         t.headings = 'Application', '# ', 'Health', 'URLS', 'Services'
         apps.each do |app|
            t << [app[:name], app[:instances], health(app), app[:uris].join(', '), app[:services].join(', ')]
         end
      end
      display apps_table
      这里将client返回的上述的app状态做成表显示在命令行中。
      在client.rb中的apps函数里:   
def apps
    check_login_status
    json_get(VMC::APPS_PATH)
  end

      首先检查登录状态,然后发出http的get请求,这里VMC::APPS_PATH在[vmc / lib / vmc / const.rb ]中定义,值为"apps",所以最后发送了这样一个请求,url="apps",这样的一个请求被路由到cloudcontroller(cc)中,在cc中的[cloud_controller / cloud_controller / config / routes.rb]中定义了cc的路由表,其中有一行如下:       

get     'apps'       =>         'apps#list',         :as=>:list_apps
       这一行第一列表示的是http方法,第二列表示url,第三列表示在cc的controllers处理这个请求的文件名以及对应的函数名,第四列是一个别名。如上面例子,表示对于url为apps的get请求,需要到cc的controllers中的apps_controller.rb文件中的list函数中处理,相应的还有。这里相信大家早已理解ror的目录结构与MVC模型了,简单地说就是MVC讲Model、View、Controller独立,model负责数据和规则,View负责视图,Controller是整体的调度包括接受输入并调用Model和View,这里cc中没有View,需要关注的就是[cc\cloud_controller\app]下的controllers和models文件夹。文件夹中的文件是通过名字匹配的,如controllers中的services_controller.rb和models中的service.rb相对应,具体可参考 Ruby on Rails。在apps_controller.rb的list函数中有,下面的内容就很简单了,不再详述,通过这个例子读者就理解了app操作的大致流程,那么下面的介绍就会简单很多。

vmc push

       push过程分为三步,分别为建一个app实例变量、执行stage、start一个instance
第一步
有个比较抽象的图如下:


       上图中,首先cli询问需要上传的app的本地地址,然后进入do_push函数,这个函数中,询问是否需要保存配置,然后取得app的各种信息,如appname、url、mem、runtime等,然后检查是否有同名的app存在,检查app的数量是否超过限制,检查是否有足够内存,检查app部署的目录,如果都ok的,将app的所有信息存到manifest中,调用函数[vmc/lib/vmc/client.rb]中的client.create_app(appname, manifest)函数 ,代码如下所示:

def create_app(name,manifest={})
    check_login_status
    app = manifest.dup
    app[:name] = name
    app[:instances] ||= 1
    json_post(VMC::APPS_PATH, app)
end
     首先检查用户登录状态,然后整理有关app的信息即manifest以及appname和instance个数,发出post请求,post请求:
def json_post(url,payload)
    http_post(url, payload.to_json, 'application/json')
end
     可看到post请求中的url为常量值VMC::APPS_PATH,即"apps",这样的一个请求会被router路由给cc,在cc中能够根据url以及http请求方法找到cc中对应的处理函数,上文已做介绍。cc中处理此请求的是[cc/cloud_controller/app/controllers/apps_controller.rb]中create函数,在create函数中,取出http中的payload,根据用户名和post请求中的appname生成相应的一个名为app的变量以供下面用,然后调用了update_app_from_params(app) ,此函数结束后会返回给vmc的client一个响应,这是后话。
     update_app_from_params(app) 函数其实就是处理app命令中的各种更新,是多个命令的处理函数,包括push、update、stop等:
1.更新app的版本,加1
2.更新app的状态。调用函数update_app_state(app)来设置app的state是STARTED还是STOPPED。因为当我们update一个app时,都是调用update_app_from_params这个函数,所以对于app的操作需要在函数开始时就判断,并设置app的state
3.进行一系列的检查。包括检查上传此app的用户app数量是否未超限、此用户的url个数是否未超限
4.给app分配mem、取得post传来的数据中的有关于stage的设置,计算出此app对应的instance个数的变化
5.将更新后的app保存
6.调用update_app_services函数,进行service更新,如果有关service的binding做了变化,也就是在body_params不为空,那么app的package_state会被设置为PENDING,表示会被stage。由于在vmc的do_push函数中service的部分还没做到,所以这里在update_app_services开始会退出,接下来。cc的update_app_from_params 函数会询问stage_app(app) if app.needs_staging?,这里不会stage。
在create函数中调用完update_app_from_params函数后发送了一个响应render :json => {:result => 'success', :redirect => app_url }, :location => app_url, :status => 302。
第二步
      在cli的do_push函数中收到响应后就会询问是否绑定服务。处理完服务的绑定后调用函数 upload_app_bits(appname, @application)将包括service在内的所有的数据打包,调用函数client.upload_appa函数,这个函数中检查登录状态后,发出url为”apps”的http的post请求。同样地,这个请求会被路由到cc中,cc中[cc/cloud_controller/app/controllers/apps_controller.rb]的create函数处理此请求。这次同样进行app的更新,但service的内容不为空,app的package_state在更新service时被设置为pending,接下来调用函数stage_app(app) 完成stage过程。在create函数中调用完update_app_from_params函数后又会发送一个响应给vmc。


第三步,start一个instance
      在do_push函数中调用upload函数后会执行start函数来启动一个instance:

upload_app_bits(appname, @application)
start(appname, true) unless no_start
     当cc需要启动一个droplet的instance:
1.cc先用nats.publish{'dea.discover'}找到一个dea。
2.找到dea后向它publish了类型为dea.#{uuid}.start的消息,dea启动时subscribe了一个自己uuid的消息nats.subscrribe(“dea.#{uuid}.start”){},那么dea收到这个消息后回调函数process_dea_start(msg)
3.在此函数中dea会从msg中解析出参数、创建instance、向router注册,以后有关于app的访问就会被router路由到这个instance。
vmc delete


vmc update

update命令也是被router路由到cc,在cc的routers.rb文件中定义了被路由到app_controllers.rb中的update函数。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值