资讯专栏INFORMATION COLUMN

Cloud Foundry中DEA组件内应用的启动与资源监控

codercao / 2808人阅读

摘要:监控应用资源完成对所在节点应用的监控主要通过周期性监控来完成的,代码如下其中默认的监控间隔为。以上便是对于的中的应用启动与应用资源监控。

      Cloud Foundry中所有的应用都运行在一个称为DEA的组件中,DEA的全称是Droplet Execution Agent。

      DEA的主要功能可以分为两个部分:运行所有的应用,监控所有的应用。本文主要讲解Cloud Foundry v1版本中DEA如何启动一个应用,以及DEA如何监控应用的资源使用。虽然DEA两个功能的实现远不止这么多,但是笔者认为启动应用和监控应用资源是 DEA的精髓所在,很多其他的内容都是在这两个点上进行封装或者强化。

DEA启动应用

      在一般情况下,启动一个应用,首先需要三样东西:环境,源码,应用入口。

      关于环境,Cloud Foundry在DEA节点处,已经部署完多套不同框架应用运行所需要的环境。关于源码,Cloud Foundry中有一个droplet的概念,它是一个可运行的源码包,比用户上传的源码还要多一些Cloud Foundry自定义添加的内容,比如说对于Spring应用,Cloud Foundry会将Tomcat将应用源码打包在一起,并且添加Tomcat中应用的启动,终止脚本等。关于程序入口,刚才已经涉及到,打包过程中,启动 脚本已经被加入droplet之中。因此,DEA只需要简单的通过环境来执行启动脚本,就可以成功启动应用。在研究了源码之后,也会发现DEA中最主要的 文件名为agent.rb,在启动这方面,也就是充当一个代理的角色,通过Linux底层的脚本命令来完成对应用的操作。

     了解Cloud Foundry中消息机制的开发者,一定会熟悉NATS的订阅/发布机制,而DEA也正是通过这种机制实现接收“启动应用”的请求。订阅代码如下:

     [ruby] view plaincopy

  1. NATS.subscribe("dea.#{uuid}.start") { |msg| process_dea_start(msg) }  

      可见DEA组件订阅“dea.#{uuid}.start”主题的消息后,只要NATS接受到相同主题消息的发布,就会将消息转发给DEA,也就是上行代 码的msg,DEA接受到msg后,通过函数process_dea_start来处理。以下我们详细介绍process_dea_start方法实现。

        在process_dea_start方法中,首先通过JSON类来解析message消息,从中获取众多的属性值,然后将这些属性值赋值给方法内的变 量。由于在下文的资源监控部分,会涉及到应用使用的内存,文件数,硬盘等,故在这里以这几个属性为例,介绍解析message。

     [ruby] view plaincopy

  1.       message_json = JSON.parse(message)  
  2.       mem     = DEFAULT_APP_MEM  
  3.       num_fds = DEFAULT_APP_NUM_FDS  
  4.       disk    = DEFAULT_APP_DISK  
  5.   
  6.       if limits = message_json["limits"]  
  7.         mem = limits["mem"] if limits["mem"]  
  8.         num_fds = limits["fds"] if limits["fds"]  
  9.         disk = limits["disk"] if limits["disk"]  
  10.       end  


       解析message后,在message_json中取出键为limits的值,如果存在的话,那就以limits的值来初始化变量mem,num_fds,disk,如果不存在的话,那就是使用DEA默认的值来给这些变量赋值。

       通过一系列资源检测之后,DEA开始为需要启动的应用创建合适的实例,这些实例最终需要保存在DEA所维护的内存块中。同时,DEA还需要为应用创建合适的文件目录来运行该应用。

      在DEA所在节点的文件系统中创建文件目录的代码如下:

     [plain] view plaincopy

  1. instance_dir = File.join(@apps_dir, "#{name}-#{instance_index}-#{instance_id}")  


         通过process_dea_start方法内的变量,创建在内从中的内存实例,简化代码如下:

     [ruby] view plaincopy

  1. instance = {  
  2.   :droplet_id => droplet_id,  
  3.   :instance_id => instance_id,  
  4.   ……  
  5.   :mem_quota => mem * (1024*1024),  
  6.   :disk_quota => disk  * (1024*1024),  
  7.   :fds_quota => num_fds,  
  8.   :state => :STARTING,  
  9.   ……  
  10.   :cc_partition => cc_partition  
  11. }  


      随后,将以上创建的实例,存入DEA维护的Hash类型内存@droplets中,代码如下:

     [ruby] view plaincopy

  1. instances = @droplets[droplet_id] || {}  
  2. instances[instance_id] = instance  
  3. @droplets[droplet_id] = instances  

       然后,在process_dea_start方法中,紧接着定义了一个lambda函数start_operation。

      在start_operation方法中,DEA首先做了以下的操作:

  • 为应用程序分配一个普通端口,供应用最终的用户进行访问
  • 若需要配置debug端口,DEA为应用分配debug端口
  • 若需要配置console端口,DEA为应用分配console端口

       做完端口分配之后,DEA开始作必要的环境设置操作。

       按次序的话,首先创建一个manifest变量,存储droplet.yaml文件中的状态值,通过该状态值,来检测应用程序是否准备就绪等先决准备工作。

       接着,创建变量prepare_script,,从源代码的某路径中将内容拷贝入prepare_script,以供执行启动操作命令的时候使用。

       接着,通过DEA运行平台的系统类型,选择不同的shell命令。

       接着,对当前应用目录进行一些权限设置,比如:使用chown命令,对应用的用户进行修改;使用chgrp命令,对用户组进行修改;使用chmod命令,对应用的读写权限进行修改。

       接着,通过setup_instance_env方法实现,对应用环境变量的设置。实现过程中,该方法将所有的环境变量都加入一个数组,最后将数组内所有的内容export到应用的环境变量中。

      紧接着,一个很重要的部分是,DEA定义了一个proc代码块,来实现对DEA中要启动应用程序启动时的资源限制,需要注意的是,这仅仅是代码块的定义,并不是在process_dea_start方法中,在这个代码块所在的位置被调用,代码如下:

      [ruby] view plaincopy

  1. exec_operation = proc do |process|  
  2.   process.send_data("cd #{instance_dir} ")  
  3.   if @secure || @enforce_ulimit  
  4.     process.send_data("renice 0 $$ ")  
  5.     process.send_data("ulimit -m #{mem_kbytes} 2> /dev/null ")  # ulimit -m takes kb, soft enforce  
  6.     process.send_data("ulimit -v 3000000 2> /dev/null ") # virtual memory at 3G, this will be enforced  
  7.     process.send_data("ulimit -n #{num_fds} 2> /dev/null ")  
  8.     process.send_data("ulimit -u 512 2> /dev/null ") # processes/threads  
  9.     process.send_data("ulimit -f #{disk_limit} 2> /dev/null ") # File size to complete disk usage  
  10.     process.send_data("umask 077 ")  
  11.   end  
  12.   app_env.each { |env| process.send_data("export #{env} ") }  
  13.   if instance[:port]  
  14.     process.send_data("./startup -p #{instance[:port]} ")  
  15.   else  
  16.     process.send_data("./startup ")  
  17.   end  
  18.   process.send_data("exit ")  
  19. end  


      该执行块,首先进入应用相应的目录下,重新设置完程序运行的优先级之后,对该进程启动时所需要的一些资源进行设置,设置过程中使用的是Linux操作系统 中的ulimit命令,该命令可以做到:限制shell启动进程是所占用的资源数量,具体的资源类型,有:物理内存,虚拟内存,所需打开的文件数,需要创 建的线程数,磁盘容量等。需要注意的是,这样的限制是临时性的限制,一旦shell会话结束之后,这样的限制也就失效了。这样也比较可以理解,否则DEA 中应用对于资源的隔离和控制也就不难实现了。资源的限制做完之后,随即实现的是env环境变量的导入,使用export命令。倒数第二实现的是,执行启动 脚本,从而真正的实现应用的启动。最后,需要将该shell会话关闭,使用exit命令。

      执行代码块如下,退出代码块如下:

     [ruby] view plaincopy

  1. exit_operation = proc do |_, status|  
  2.   @logger.info("#{name} completed running with status = #{status}.")  
  3.   @logger.info("#{name} uptime was #{Time.now - instance[:start]}.")  
  4.   stop_droplet(instance)  
  5. end  


       该部分代码较为简单,只是调用了stop_droplet方法。

       接着会有一个非常重要的模块,负责实现应用的真正启动:

     [plain] view plaincopy

  1. Bundler.with_clean_env { EM.system("#{@dea_ruby} -- #{prepare_script} true #{sh_command}", exec_operation, exit_operation) }  


        该代码的含义是:使用一个全新环境变量的env进行操作括号内的内容,EM.system中的内容即为最终执行的ruby启动脚本。

        接着会有一些检测应用准备情况或者pid信息的操作,为一些必要的辅助操作。

        最后,DEA定义了一个纤程Fiber,纤程中首先通过stage_app_dir来实现droplet中所有的内容的准备工作。一旦成功,则马上调用代码块start_operation。

        最后的最后,使用f.resume方法实现,纤程的唤醒。

DEA监控应用资源

        DEA完成对所在节点应用的监控主要通过周期性监控来完成的,代码如下:

     [ruby] view plaincopy

  1. EM.add_timer(MONITOR_INTERVAL) { monitor_apps }  

        其中默认的监控间隔为2s。所有的实现在方法中monitor_apps中实现,简化代码如下:

     [ruby] view plaincopy

  1.     def monitor_apps(startup_check = false)  
  2.       ……  
  3.       process_statuses = `ps axo pid=,ppid=,pcpu=,rss=,user=`.split(" ")  
  4.       ……  
  5.       process_statuses.each do |process_status|  
  6.         parts = process_status.lstrip.split(/s+/)  
  7.         pid = parts[PID_INDEX].to_i  
  8.         pid_info[pid] = parts  
  9.         (user_info[parts[USER_INDEX]] ||= []) << parts if (@secure && parts[USER_INDEX] =~ SECURE_USER)  
  10.       end  
  11.       ……  
  12.       if startup_check  
  13.         du_all_out = `cd #{@apps_dir}; du -sk * 2> /dev/null`  
  14.         monitor_apps_helper(startup_check, start, du_start, du_all_out, pid_info, user_info)  
  15.       else  
  16.         du_proc = proc do |p|  
  17.           p.send_data("cd #{@apps_dir} ")  
  18.           p.send_data("du -sk * 2> /dev/null ")  
  19.           p.send_data("exit ")  
  20.         end  
  21.         cont_proc = proc do |output, status|  
  22.           monitor_apps_helper(startup_check, start, du_start, output, pid_info, user_info)  
  23.         end  
  24.         EM.system("/bin/sh", du_proc, cont_proc)  
  25.       end  
  26.     end  

        其中,首先看一下代码:process_statuses = `ps axo pid=,ppid=,pcpu=,rss=,user=`.split(" "),其实现的是:通过操作系统层,将所有进程运行的信息,存入 process_status数组中,然后将这个数组中按pid信息分类,存入Hash类型pid_info中,同时又通过user信息分类,存入 Hash类型user_info中,以备后续资源监控使用。

        然后通过startup_check参数,进行不同的磁盘检测模块,代码为:

      [ruby] view plaincopy

  1.       du_entries = du_all_out.split(" ")  
  2.       du_entries.each do |du_entry|  
  3.         size, dir = du_entry.split(" ")  
  4.         size = size.to_i * 1024 # Convert to bytes  
  5.         du_hash[dir] = size  
  6.       enddu_all_out = `cd #{@apps_dir}; du -sk * 2> /dev/null`  

        该命令实现进入应用的根目录,执行du命令,统计应用目录所占用的磁盘空间,随后使用monitor_app_helper方法对方才收集到的数据进行处理,得到最终所需要的监控数据。判断语句的另外一部分,只是通过proc代码的方法来实现相同的功能。

        以下分析monitor_app_helper方法。以下是通过目录分离磁盘占用空间的实现:

     [ruby] view plaincopy

  1. du_entries = du_all_out.split(" ")  
  2. du_entries.each do |du_entry|  
  3.   size, dir = du_entry.split(" ")  
  4.   size = size.to_i * 1024 # Convert to bytes  
  5.   du_hash[dir] = size  
  6. end  


        do_all_out显示的是@droplets中所有应用的磁盘使用情况,通过split方法或者每个应用的信息,然后存入Hash类型du_hash中。

      以下是最重要的信息的汇总:

     [ruby] view plaincopy

  1.       @droplets.each_value do |instances|  
  2.         instances.each_value do |instance|  
  3.           if instance[:pid] && pid_info[instance[:pid]]  
  4.             pid = instance[:pid]  
  5.             mem = cpu = 0  
  6.             disk = du_hash[File.basename(instance[:dir])] || 0  
  7.             # For secure mode, gather all stats for secure_user so we can process forks, etc.  
  8.             if @secure && user_info[instance[:secure_user]]  
  9.               user_info[instance[:secure_user]].each do |part|  
  10.                 mem += part[MEM_INDEX].to_f  
  11.                 cpu += part[CPU_INDEX].to_f  
  12.                 # disabled for now, LSOF is too slow to run per app/user  
  13.                 # deleted_disk = grab_deleted_file_usage(instance[:secure_user])  
  14.                 # disk += deleted_disk  
  15.               end  
  16.             else  
  17.               mem = pid_info[pid][MEM_INDEX].to_f  
  18.               cpu = pid_info[pid][CPU_INDEX].to_f  
  19.             end  
  20.             usage = @usage[pid] ||= []  
  21.             cur_usage = { :time => Time.now, :cpu => cpu, :mem => mem, :disk => disk }  
  22.             usage << cur_usage  
  23.             usage.shift if usage.length > MAX_USAGE_SAMPLES  
  24.             check_usage(instance, cur_usage, usage) if @secure  
  25.   
  26.             #@logger.debug("Droplet Stats are = #{JSON.pretty_generate(usage)}")  
  27.             @mem_usage += mem  
  28.   
  29.             metrics.each do |key, value|  
  30.               metric = value[instance[key]] ||= {:used_memory => 0, :reserved_memory => 0,  
  31.                                                  :used_disk => 0, :used_cpu => 0}  
  32.               metric[:used_memory] += mem  
  33.               metric[:reserved_memory] += instance[:mem_quota] / 1024  
  34.               metric[:used_disk] += disk  
  35.               metric[:used_cpu] += cpu  
  36.             end  
  37.   
  38.             # Track running apps for varz tracking  
  39.             i2 = instance.dup  
  40.             i2[:usage] = cur_usage # Snapshot  
  41.   
  42.             running_apps << i2  
  43.   
  44.             # Re-register with router on startup since these are orphaned and may have been dropped.  
  45.             register_instance_with_router(instance) if startup_check  
  46.           else  
  47.             ……  
  48.           end  
  49.         end  
  50.       end  

        遍历@droplets,再遍历@droplets中的每一个对象的instance,首先获取实例的pid,并初始化mem,cpu,disk。随后, 找到该instance的:secure_user,通过在user_info中找到所有instance[:secure_user]的值,然后,将其 累加进入mem和cpu。

        将获得指定instance的资源使用情况后,需要将这些信息存入DEA维护的内存@usage中,代码如下:

     [ruby] view plaincopy

  1. usage = @usage[pid] ||= []  
  2. cur_usage = { :time => Time.now, :cpu => cpu, :mem => mem, :disk => disk }  
  3. usage << cur_usage  


        第一行需要注意操作符||=,代码含义为若@usage[pid]不为空的话,将[ ]赋值给@usage[pid],并最终赋值给usage。第二行代码初始化了一个hash变量,第三行代码表示将cur_usage加入usage数 组,而usage数组指向的是@usage[pid]的地址,所以也就相当于在@usage[pid]中添加cur_usage。

       而metrics变量则是记录整个DEA所有运行的应用加起来的应用资源,以便之后更新varz信息。

       以上便是对于Cloud Foundry的DEA中的应用启动与应用资源监控。

评价

        由以上的分析可知,Cloud Foundry v1版本中的DEA在实现资源的监控时,通过linux底层的ps来完成,而对于资源的控制,则并没有做的很好,使用的方式为启动时限制与超额时停止的策 略。该方法显然不是较佳的,而Cloud Foundry也推出了warden的容器,实现对DEA中应用资源使用时的控制与隔离。如果笔者这脑袋能理解或者读懂的话,期待不久给大家送上 warden的实现,以及cgroup的机制。


转载清注明出处。

这篇文档更多出于我本人的理解,肯定在一些地方存在不足和错误。希望本文能够对接触Cloud Foundry中DEA中应用运行与资源监控的人有些帮助,如果你对这方面感兴趣,并有更好的想法和建议,也请联系我。

我的邮箱:shlallen@zju.edu.cn
新浪微博:@莲子弗如清

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/4057.html

相关文章

  • 深入Cloud Foundry(下)

    续与回顾 本文第一部分介绍了CloudFoundry的整体架构,并在最后花了一点篇幅简介CloudFoundry的代码组织情况,以便于读者自己去研究源代码。笔者认为开源项目较大的好处在于:当你读懂源代码、理解总体架构后,能够成竹在胸,并吸收为己用(有点类似武侠小说中的北冥神功)。为己用就是本篇要说的内容:我们使用CloudFoundry搭建自己的私有PaaS平台。 在介绍CloudFoundry之...

    qylost 评论0 收藏0
  • 深入 Cloud Foundry(上)

    引子 今年4月份,VMware突然发布了业内第一个开源的PaaS——CloudFoundry。几个关键字:开源、PaaS、VMware,如果你对云计算感兴趣,就冲着它的ApacheV2协议,如果不去GitHub拿它的代码好好研读一下,真有点对不起自己。笔者当时就是以这样的心态去研究它的代码,并把它部署在我们labs里面。发布至今的这几个月里,笔者一直关注它的演进,并从它的架构设计中获益良多,...

    XiNGRZ 评论0 收藏0
  • 基于Cloud Foundry的PaaS开发部署

    摘要:基于的实践一综述参见还可参见基于的实践之集群部署单结点的部署由于提供的安装脚本,使用简单不再陈述,大家参照一下官网即可,在此主要谈谈多结点集群部署的要点。关于,大家可参考和。 使用Cloud foundry(以下简称CF)接近一年时间,一直缺少时间写些东西与大家探讨。最近,打算写一下。目前使用经历主要包括:  1. 搭建CF运行环境并维护;  2. 部分代码修改和新功能扩充的工作;  3. ...

    chinafgj 评论0 收藏0
  • PaaS未来世界

    摘要:未来世界此屏幕截图是一个非常好的例子,它包含了通过或者命令行开始所需要的所有内容。未来世界架构元素核心系统架构系统的核心和的附加功能都围绕着控制器。而健康管理器的作用是识别任何可能产生的问题,并通过告知控制器或者其他机制来解决这些问题。 PaaS的未来会是什么样的呢?NoOps和DevOps又如何融入其中呢?PaaS将会让开发者生活的更加轻松。 实际上,PaaS是一些事物的混合体,它关注更快...

    Lavender 评论0 收藏0
  • IBM Bluemix开启云开发时代

    摘要:运行时环境,又叫构建包上提供的一系列运行时环境包括图中显示的七种命名构建包,外加已批准用于的其他任何构建包。开发运营服务上的八种开发运营服务包括来自的五种服务和来自第三方的三种服务。 去年夏天我测评了Cloud Foundry PaaS(平台即服务),当时着眼于Pivotal和ActiveState这两种解决开源方案。这回测试时,我将关注IBM Bluemix,这是在SoftLayer上托管...

    cocopeak 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<