Cloud Foundry中Stager组件的源码分析

2023-12-08 00:32

本文主要是介绍Cloud Foundry中Stager组件的源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        Cloud Foundry中有一个组件,名为Stager,它主要负责的工作就是将用户部署进Cloud Foundry的源代码打包成一个DEA可以解压执行的droplet。

        关于droplet的制作,Cloud Foundry v1中一个完整的流程为:

  1. 用户将应用源代码上传至Cloud Controller;
  2. Cloud Controller通过NATS发送请求至Stager,要求制作dropet;
  3. Stager从Cloud Controller下载压缩后的应用源码,并解压;
  4. Stager将解压后的应用源码,添加运行容器以及启动终止脚本;
  5. 压缩成droplet形式上传至Cloud Foundry。


Stager制作droplet总流程实现

        现在从制作一个droplet的流程将分析Cloud Foundry中Stager的源码。

        从Stager接收到请求,则开始制作droplet。而Stager能接收到Cloud Controller发送来的stage请求,是由于Stager订阅了主题为“staging”的消息,/stager/lib/vcap/server.rb中代码如下:

  def setup_subscriptions@config[:queues].each do |q|@sids << @nats_conn.subscribe(q, :queue => q) do |msg, reply_to|@thread_pool.enqueue { execute_request(msg, reply_to) }@logger.info("Enqueued request #{msg}")end@logger.info("Subscribed to #{q}")endend
        由以上代码可知,订阅了@config[:queues]中的主题后,关于该主题消息的发布,被Stager接收到后,Stager执行execute_request方法执行接收到的消息msg。在这里的实现,还借助了@thread_pool,该变量是创建了一个线程池,将执行结果加入线程池。

        以下进入/stager/lib/vcap/server.rb的execute_request方法:

  def execute_request(encoded_request, reply_to)beginrequest = Yajl::Parser.parse(encoded_request)rescue => e……returnendtask = VCAP::Stager::Task.new(request, @task_config)result = nilbegintask.perform……endencoded_result = Yajl::Encoder.encode(result)EM.next_tick { @nats_conn.publish(reply_to, encoded_result) }nilend
        在该方法中首先对request请求进行解析,获得request对象,然后通过request对象和@task_config产生一个task对象,Task类为VCAP::Stager::Task。其中@task_config中有属性:ruby_path, :ruby_plugin_path, secure_user_manager。创建为task对象之后,执行task.perform。关于perform方法,是整个Stager执行流中最为重要的部分,以下便进入/stager/lib/vcap/stager/task.rb的perform方法中:

  def performworkspace = VCAP::Stager::Workspace.createapp_path = File.join(workspace.root_dir, "app.zip")download_app(app_path)unpack_app(app_path, workspace.unstaged_dir)stage_app(workspace.unstaged_dir, workspace.staged_dir, @task_logger)droplet_path = File.join(workspace.root_dir, "droplet.tgz")create_droplet(workspace.staged_dir, droplet_path)upload_droplet(droplet_path)nilensureworkspace.destroy if workspaceend
        该方法中的流程非常清晰,顺序一次是download_app, upack_app, stage_app, create_droplet, upload_app。

        首先先进入download_app方法:

  def download_app(app_path)cfg_file = Tempfile.new("curl_dl_config")write_curl_config(@request["download_uri"], cfg_file.path,"output" => app_path)# Show errors but not progress, fail on non-200res = @runner.run_logged("env -u http_proxy -u https_proxy curl -s -S -f -K #{cfg_file.path}")unless res[:status].success?raise VCAP::Stager::TaskError.new("Failed downloading app")endnilensurecfg_file.unlink if cfg_fileend
        该方法中较为重要的部分就是如何write_curl_config,以及如何执行脚本命令。在write_curl_config中有一个参数@request["download_uri"],该参数的意义是用户上传的应用源码存在Cloud Controller处的位置,也是Stager要去下载的应用源码的未知。该参数的产生的流程为:Cloud Controller中的app_controller.rb中,调用stage_app方法,该方法中调用了download_uri方法,并将其作为request的一部分,通过NATS发给了Stager。关于write_curl_config主要实现的是将curl的配置条件写入指定的路径,以便在执行curl命令的时候,可以通过配置文件的读入来方便实现,实现代码为:res = @runner.run_logged("env -u http_proxy -u https_proxy curl -s -S -f -K #{cfg_file.path}")。

        当将应用源码下载至指定路径之后,第二步需要做的是将其解压,unpack_app方法则实现了这一点,实现代码为:res = @runner.run_logged("unzip -q #{packed_app_path} -d #{dst_dir}")。

        第三步需要做的,也是整个stager最为重要的部分,就是stage_app方法。实现代码如下:

  def stage_app(src_dir, dst_dir, task_logger)plugin_config = {"source_dir"   => src_dir,"dest_dir"     => dst_dir,"environment"  => @request["properties"]}……plugin_config_file = Tempfile.new("plugin_config")StagingPlugin::Config.to_file(plugin_config, plugin_config_file.path)cmd = [@ruby_path, @run_plugin_path,@request["properties"]["framework_info"]["name"],plugin_config_file.path].join(" ")res = @runner.run_logged(cmd,:max_staging_duration => @max_st./bin/catalina.sh runaging_duration)……ensureplugin_config_file.unlink if plugin_config_filereturn_secure_user(secure_user) if secure_userend

        在该方法中,首先创建plugin_config这个Hash对象,随后创建plugin_config_file, 又创建了cmd对象,最后通过res = @runner.run_logged(cmd, :max_staging_duration => @max_staging_duration)实现了执行了cmd。现在分析cmd对象:

     cmd = [@ruby_path, @run_plugin_path,@request["properties"]["framework_info"]["name"],plugin_config_file.path].join(" ")
        该对象中@ruby_path为Stager组件所在节点出ruby的可执行文件路径;@ruby_plugin_path为运行plugin可执行文件的路径,具体为:/stager/bin/run_plugin;@request["properties"]["framework_info"]["name"]为所需要执行stage操作的应用源码的框架名称,比如,Java_web,spring,Play, Rails3等框架;而 plugin_config_file.path则是做plugin时所需配置文件的路径。

        关于cmd对象的执行,将作为本文的一个重要模块,稍后再讲,现在先将讲述一个完整dropet制作流程的实现。

        当昨晚stage_app工作之后,也就相当于一个将源码放入了一个server容器中,也做到了实现添加启动终止脚本等,但是这些都是一个完成的文件目录结构存在与文件系统中,为方便管理以及节省空间,Stager会将stage做完后的内容进行压缩,create_droplet则是实现了这一部分的内容,代码如下:

  def create_droplet(staged_dir, droplet_path)cmd = ["cd", staged_dir, "&&", "COPYFILE_DISABLE=true", "tar", "-czf", droplet_path, "*"].join(" ")res = @runner.run_logged(cmd)unless res[:status].success?raise VCAP::Stager::TaskError.new("Failed creating droplet")endend
        当创建完droplet这个压缩包之后,Stager还会将这个dropet上传至Cloud Controller的某个路径下,以便之后DEA在启动这个应用的时候,可以从Cloud Controller的文件系统中下载到droplet,并解压启动。以下是upload_app的代码实现:

  def upload_droplet(droplet_path)cfg_file = Tempfile.new("curl_ul_config")write_curl_config(@request["upload_uri"], cfg_file.path, "form" => "upload[droplet]=@#{droplet_path}")res = @runner.run_logged("env -u http_proxy -u https_proxy curl -s -S -f -K #{cfg_file.path}")……nilensurecfg_file.unlink if cfg_fileend
        至此的话,Stager所做工作的大致流程,已经全部完成,只是在实现的同时,采用的详细技术,本文并没有一一讲述。

        

        虽然Stager的实现流程已经讲述完毕,但是关于Stager最重要的模块stage_app方法的具体实现,本文还没有进行讲述,本文以下内容会详细讲解这部分内容,并以Java_web和standalone这两种不同框架的应用为案例进行分析。

        上文中以及提到,实现stage的功能的代码部分为:

    cmd = [@ruby_path, @run_plugin_path,@request["properties"]["framework_info"]["name"],plugin_config_file.path].join(" ")./bin/catalina.sh runres = @runner.run_logged(cmd,:max_staging_duration => @max_staging_duration)
        关于cmd中的各个参数,上文已经分析过,现在更深入的了解@run_plugin_path,该对象指向/stager/bin/run_plugin可执行文件,现在进入该文件中:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'rubygems'
require 'bundler/setup'
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'vcap/staging/plugin/common'
unless ARGV.length == 2puts "Usage: run_staging_plugin [plugin name] [plugin config file]"exit 1
end
plugin_name, config_path = ARGV
klass  = StagingPlugin.load_plugin_for(plugin_name)
plugin = klass.from_file(config_path)
plugin.stage_application
        该部分的源码主要实现了,提取出cmd命令中的后两个参数,一个为plugin_name,另一个为config_name,并通过这两个参数实现加载plugin以及真正的stage。

        首先进入load_plugin_for方法,源码位置为/vcap_staging/lib/vcap/staging/plugin/common.rb:

  def self.load_plugin_for(framework)./bin/catalina.sh runframework = framework.to_splugin_path = File.join(staging_root, framework, 'plugin.rb')require plugin_pathObject.const_get("#{camelize(framework)}Plugin")end

Java_web框架应用的stage流程       

        首先以Java_web为例在load_plugin_for方法中,首先提取出该应用源码所设置的框架,并通过框架来创建plugin_path对象,然后再实现require该框架目录下的plugin.rb文件,Java_web的应用则需要require的文件为:/vcap-staging/lib/vcap/staging/plugin/java_web/plugin.rb。

        通过require了子类的plugin.rb之后,当执行plugin.stage_application的时候,直接进入/vcap-staging/lib/vcap/staging/plugin/java_web/plugin.rb中的stage_application方法,该方法的源码实现为:

  def stage_applicationDir.chdir(destination_directory) docreate_app_directorieswebapp_root = Tomcat.prepare(destination_directory)copy_source_files(webapp_root)web_config_file = File.join(webapp_root, 'WEB-INF/web.xml')unless File.exist? web_config_fileraise "Web application staging failed: web.xml not found"endservices = environment[:services] if environmentcopy_service_drivers(File.join(webapp_root,'../../lib'), services)Tomcat.prepare_insight destination_directory, environment, insight_agent if Tomcat.insight_bound? servicesconfigure_webapp(webapp_root, self.autostaging_template, environment) unless self.skip_staging(webapp_root)create_startup_scriptcreate_stop_scriptendend
        在stage_application中,实现步骤为:1.创建相应的目录;2.拷贝源码和相应的所需文件;3.设置配置文件;4.添加启动和终止脚本。

        创建相应的目录,包括create_app_directory,在应用的的目标目录下创建log文件夹以及tmp文件夹。

        在实现代码 webapp_root = Tomcat.prepare(destination_directory) 时,进入/vcap-staging/lib/vcap/staging/plugin/java_web/tomcat.rb的prepare方法:

  def self.prepare(dir)FileUtils.cp_r(resource_dir, dir)output = %x[cd #{dir}; unzip -q resources/tomcat.zip]raise "Could not unpack Tomcat: #{output}" unless $? == 0webapp_path = File.join(dir, "tomcat", "webapps", "ROOT")server_xml = File.join(dir, "tomcat", "conf", "server.xml")FileUtils.rm_f(server_xml)FileUtils.rm(File.join(dir, "resources", "tomcat.zip"))FileUtils.mv(File.join(dir, "resources", "droplet.yaml"), dir)FileUtils.mkdir_p(webapp_path)webapp_pathend
       该方法中,首先从resource文件夹拷贝至dir目录下,随即将resource文件夹中的tomcat.zip文件解压,随后在dir目录下进行一系列的文件操作。最后返回web_app路径。

       返回至stage_application方法中的,copy_source_fiiles从一些source文件全部拷贝至webapp_path下,代码为:copy_source_files(webapp_root);随后检查WEB-INF/web.xml配置文件是否存在,若不存在的话,抛出异常,代码为“

unless File.exist? web_config_fileraise "Web application staging failed: web.xml not found"end

        接着将应用所需的一些服务驱动拷贝至指定的lib目录下,代码为:copy_service_drivers(File.join(webapp_root,'../../lib'), services);随即又实现了对Tomcat的配置,主要是代理的配置:      Tomcat.prepare_insight destination_directory, environment, insight_agent if Tomcat.insight_bound? services ;又然后对webapp路木进行了配置,代码为:configure_webapp(webapp_root, self.autostaging_template, environment) unless self.skip_staging(webapp_root);最后终于到了实现对启动脚本和终止脚本的生成,代码为:

      create_startup_scriptcreate_stop_script
         首先来看一下Java_web框架应用的的启动脚本创建,在/vcap-staging/lib/vcap/staging/plugin/common.rb中的create_startup_script方法:

  def create_startup_scriptpath = File.join(destination_directory, 'startup')File.open(path, 'wb') do |f|f.puts startup_scriptendFileUtils.chmod(0500, path)end
        在该方法的时候,首先在目标文件中添加一个startup文件,然后再打开该文件,并通过startup_script产生的内容写入startup文件,最后对startup文件进行权限配置。现在进入startup_script方法中,位置为/vcap-staging/lib/vcap/staging/plugin/java_web/plugin.rb:

  def startup_scriptvars = {}vars['CATALINA_OPTS'] = configure_catalina_optsgenerate_startup_script(vars) do<<-JAVA
export CATALINA_OPTS="$CATALINA_OPTS `ruby resources/set_environm./bin/catalina.sh run./bin/catalina.sh run./bin/catalina.sh runent`"
env > env.log./bin/catalina.sh run
PORT=-1
while getopts ":p:" opt; docase $opt inp)PORT=$OPTARG;;esac
done
if [ $PORT -lt 0 ] ; thenecho "Missing or invalid port (-p)"exit 1
fi
ruby resources/generate_server_xml $PORTJAVAendend
         在generate_startup_script方法中回天夹start_command,位于/vcap-staging/lib/vcap/staging/plugin/common.rb,运行脚本的添加实现为:

<%= change_directory_for_start %>
<%= start_command %> > ../logs/stdout.log 2> ../logs/stderr.log &
        其中change_directory_for_start为: cd tomcat;而start_command为:./bin/catalina.sh run。

        至此的话,启动脚本的添加也就完成了,终止的脚本添加的话,也大致一样,主要还是获取应用进程的pid,然后强制杀死该进程,本文就不再具体讲解stop脚本的生成。

        讲解到这的话,一个Java_web框架的app应用以及完全stage完成了,在后续会被打包成一个droplet并上传。stage完成之后,自然是需要由DEA来使用的,而DEA正是获取droplet,解压到相应的目录下,最后通过DEA节点提供的运行环境,执行压缩后的droplet中的startup脚本,并最终完全在DEA中启动应用。


Standalone框架应用的stage流程

        在Cloud Foundry中,standalone应用被认为是不能被Cloud Foundry识别出框架的所有类型应用。在这种情况下,Cloud Foundry的stager还是会对其进行打包。

        在制作standalone应用的droplet时,总的制作流程与Java_web以及其他能被Cloud Foundry识别的框架相比,没有说明区别,但是在stage的时候,会由很大的区别。因为对于standalone的应用,Cloud Foundry的stager不会提供出源码以外的所有运行依赖,所以关于应用的执行的依赖,必须由用户在上传前将其与应用源码捆绑之后在上传。

        在识别到需要打包的源码的框架被定义为standalone之后,Stager使用子类StagingPlugin的子类StandalonePlugin来实现stage过程。流程与其类型框架的应用相同,但是具体操作会有一些区别,首先来阅读stage_application的方法:

  def stage_applicationDir.chdir(destination_directory) docreate_app_directoriescopy_source_files#Give everything executable perms, as start command may be a scriptFileUtils.chmod_R(0744, File.join(destination_directory, 'app'))runtime_specific_stagingcreate_startup_scriptcreate_stop_scriptendend
        主要的区别在与runtime_specific_staging及之后的方法。runtime_specific_staging方法的功能主要是判断该应用程序的运行环境是否需要ruby,若不需要的话,staging_application继续往下执行;若需要的话,则为应用准备相应的gem包以及安装gem包。

        随后在create_startup_script的方法中,首先解析应用的运行时类型,如果是ruby,java,python的话,那就生成相应的启动脚本,如果是其他类型的话,那就直接进入generate_startup_script方法。现在以java运行时为例,分析该过程的实现,代码如下:

  def java_startup_scriptvars = {}java_sys_props = "-Djava.io.tmpdir=$PWD/tmp"vars['JAVA_OPTS'] = "$JAVA_OPTS -Xms#{application_memory}m -Xmx#{application_memory}m #{java_sys_props}"generate_startup_script(vars)end
         该方法的实现,创建了vars对象之后,通过将vars参数传递给方法generate_startup_script,来实现启动脚本的生成,代码如下:

  def generate_startup_script(env_vars = {})after_env_before_script = block_given? ? yield : "\n"template = <<-SCRIPT
#!/bin/bash
<%= environment_statements_for(env_vars) %>
<%= after_env_before_script %>
<%= change_directory_for_start %>
<%= start_command %> > ../logs/stdout.log 2> ../logs/stderr.log &
<%= get_launched_process_pid %>
echo "$STARTED" >> ../run.pid
<%= wait_for_launched_process %>SCRIPT# TODO - ERB is pretty irritating when it comes to blank lines, such as when 'after_env_before_script' is nil.# There is probably a better way that doesn't involve making the above Heredoc horrible.ERB.new(template).result(binding).lines.reject {|l| l =~ /^\s*$/}.joinend
        到这里,便是stage过程中启动脚本的生成,终止脚本的生成的话,流程也一致,主要是获取应用进程的pid,然后通过kill pid来实现对应用的终止。


        以上便是笔者对于Cloud Foundry中Stager组件的简单源码分析。


关于作者:

孙宏亮,DAOCLOUD软件工程师。两年来在云计算方面主要研究PaaS领域的相关知识与技术。坚信轻量级虚拟化容器的技术,会给PaaS领域带来深度影响,甚至决定未来PaaS技术的走向。


转载清注明出处。

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

我的邮箱:allen.sun@daocloud.io
新浪微博: @莲子弗如清


这篇关于Cloud Foundry中Stager组件的源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/467875

相关文章

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

React+TS前台项目实战(十七)-- 全局常用组件Dropdown封装

文章目录 前言Dropdown组件1. 功能分析2. 代码+详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局Dropdown组件封装,可根据UI设计师要求自定义修改。 Dropdown组件 1. 功能分析 (1)通过position属性,可以控制下拉选项的位置 (2)通过传入width属性, 可以自定义下拉选项的宽度 (3)通过传入classN

springboot家政服务管理平台 LW +PPT+源码+讲解

3系统的可行性研究及需求分析 3.1可行性研究 3.1.1技术可行性分析 经过大学四年的学习,已经掌握了JAVA、Mysql数据库等方面的编程技巧和方法,对于这些技术该有的软硬件配置也是齐全的,能够满足开发的需要。 本家政服务管理平台采用的是Mysql作为数据库,可以绝对地保证用户数据的安全;可以与Mysql数据库进行无缝连接。 所以,家政服务管理平台在技术上是可以实施的。 3.1

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

基于Java医院药品交易系统详细设计和实现(源码+LW+调试文档+讲解等)

💗博主介绍:✌全网粉丝10W+,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码+数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人  Java精品实战案例《600套》 2023-2025年最值得选择的Java毕业设计选题大全:1000个热

美容美发店营销版微信小程序源码

打造线上生意新篇章 一、引言:微信小程序,开启美容美发行业新纪元 在数字化时代,微信小程序以其便捷、高效的特点,成为了美容美发行业营销的新宠。本文将带您深入了解美容美发营销微信小程序,探讨其独特优势及如何助力商家实现业务增长。 二、微信小程序:美容美发行业的得力助手 拓宽客源渠道:微信小程序基于微信社交平台,轻松实现线上线下融合,帮助商家快速吸引潜在客户,拓宽客源渠道。 提升用户体验:

风水研究会官网源码系统-可展示自己的领域内容-商品售卖等

一款用于展示风水行业,周易测算行业,玄学行业的系统,并支持售卖自己的商品。 整洁大气,非常漂亮,前端内容均可通过后台修改。 大致功能: 支持前端内容通过后端自定义支持开启关闭会员功能,会员等级设置支持对接官方支付支持添加商品类支持添加虚拟下载类支持自定义其他类型字段支持生成虚拟激活卡支持采集其他站点文章支持对接收益广告支持文章评论支持积分功能支持推广功能更多功能,搭建完成自行体验吧! 原文