在Cloud Foundry v2版本号中,DEA为一个用户应用执行的控制模块,而应用的真正执行都是依附于warden。

更详细的来说,是DEA接收到Cloud Controller的请求;DEA发送请求给warden server;warden server创建warden container并将用户应用droplet等环境配置好;DEA发送应用启动请求至warden serve;最后warden container执行启动脚本启动应用。

本文主要详细描写叙述,DEA怎样与warden交互,以保证终于用户的应用能够成功绑定某一个port,实现用户应用对外提供服务。

DEA在运行启动一个应用的时候,主要做到下面这些部分:promise_droplet, promise_container, 当中这两个部分并发完毕;promise_extract_droplet, promise_exec_hook_script(“before_start”), promise_start等。代码例如以下:

  1.         [
  2. promise_droplet,
  3. promise_container
  4. ].each(&:run).each(&:resolve)
  5.  
  6. [
  7. promise_extract_droplet,
  8. promise_exec_hook_script('before_start'),
  9. promise_start
  10. ].each(&:resolve)

promise_droplet:

在这一个环节,DEA主要做的工作是将droplet下载本机。通过droplet_uri,当中主要的路径在/config/dea.yml中,为base_dir: /tmp/dea_ng, 因此终于DEA下载到的droplet存放于DEA组件所在的宿主机上。

promise_container:

该环节的工作主要完毕创建一个warden container,随后能够为应用的执行提供一个合适的环境。promise_container的源代码实现例如以下:

  1. def promise_container
  2. Promise.new do |p|
  3. bind_mounts = [{'src_path' => droplet.droplet_dirname, 'dst_path' => droplet.droplet_dirname}]
  4. with_network = true
  5. container.create_container(
  6. bind_mounts: bind_mounts + config['bind_mounts'],
  7. limit_cpu: config['instance']['cpu_limit_shares'],
  8. byte: disk_limit_in_bytes,
  9. inode: config.instance_disk_inode_limit,
  10. limit_memory: memory_limit_in_bytes,
  11. setup_network: with_network)
  12. attributes['warden_handle'] = container.handle
  13. promise_setup_def create_container(params)
  14. [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
  15. raise ArgumentError, "expecting #{param.to_s} parameter to create container" if params[param].nil?
  16. end
  17.  
  18. with_em do
  19. new_container_with_bind_mounts(params[:bind_mounts])
  20. limit_cpu(params[:limit_cpu])
  21. limit_disk(byte: params[:byte], inode: params[:inode])
  22. limit_memory(params[:limit_memory])
  23. setup_network if params[:setup_network]
  24. end
  25. endenvironment.resolve
  26. p.deliver
  27. end
  28. end

能够看到传入的參数主要有:

       bind_mounts:完毕宿主机文件文件夹的路径mount到container内部。

       limit_cpu:用于限制container的CPU资源分配;

       byte:磁盘限额;

       innode:磁盘的innode的限制

       limit_memory:内存限额;

       setup_network:网络的配置项。

当中setup_network一直设置为true。

在container.create_container的方法实现中,有下面的方法,例如以下:

  1. def create_container(params)
  2. [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
  3. raise ArgumentError, "expecting #{param.to_s} parameter to createdef create_container(params)
  4. [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
  5. raise ArgumentError, "expecting #{param.to_s} parameter to create container" if params[param].nil?
  6. end
  7.  
  8. with_em do
  9. new_container_with_bind_mounts(params[:bind_mounts])
  10. limit_cpu(params[:limit_cpu])
  11. limit_disk(byte: params[:byte], inode: params[:inode])
  12. limit_memory(params[:limit_memory])
  13. setup_network if params[:setup_network]
  14. end
  15. end container" if params[param].nil?
  16. end
  17.  
  18. with_em do
  19. new_container_with_bind_mounts(params[:bind_mounts])
  20. limit_cpu(params[:limit_cpu])
  21. limit_disk(byte: params[:byte], inode: params[:inode])
  22. limit_memory(params[:limit_memory])
  23. setup_network if params[:setup_network]
  24. end
  25. end

主要关注一下setup_network方法。例如以下:

  1. def setup_network
  2. request = ::Warden::Protocol::NetInRequest.new(handle: handle)
  3. response = call(:app, request)
  4. network_ports['host_port'] = response.host_port
  5. network_ports['container_port'] = response.container_port
  6.  
  7. request = ::Warden::Protocol::NetInRequest.new(handle: handle)
  8. response = call(:app, request)
  9. network_ports['console_host_port'] = response.host_port
  10. network_ports['console_container_port'] = response.container_port
  11. end

从代码中能够看到,在setup_network中。主要完毕了两次NetIn操作。对于NetIn操作,须要说明的是。完毕的工作是将host主机上的端口映射到container内部的端口。

换言之,将host_ip:port1映射到container_ip:port2。也就是说假设container在container_ip上监听的是端口port2,则host机器外部的请求訪问host机器,而且端口为port1的时候。host的内核网络栈。会将请求转发给container的port2端口,当中使用的协议为DNAT协议。

因此。在以上的代码中实现了两次NetIn操作,也就是说将container的两个port映射到了host宿主机,第一个port用于container内应用的正常占用port。第二个port是用来为应用的console功能做服务。尽管container也分配了第二个port,可是在而后的应用启动等中,该console_port都没有使用过,可见Cloud Foundry在这里仅仅是预留了接口,可是没有真正利用起来。

以上主要描写叙述了NetIn的功能,下面进入NetIn操作的源代码实现。NetIn的源代码实现,主要为warden server的部分。当中。是由DEA进程通过warden.sock和warden server建立通信,随后DEA进程发送NetIn请求给warden server,warden server终于处理该请求。

如今进入warden范畴,研究warden怎样接收请求,并实现port的映射。

在warden/lib/warden/server.rb中,大部分代码都是为了完毕warden server的执行。在run!方法中,能够看到warden server另外还启动了一个unix domain server,代码例如以下:

  1. server = ::EM.start_unix_domain_server(unix_domain_path, ClientConnection)

也就是说,warden server会通过整个unix domain server接收从DEA进程发送来的关于warden container的一系列请求,在ClientConnection类中定义,关于这部分请求怎样处理的方法。

当unix domain server中ClientConnection类通过receive_data(data)方法来实现接收请求。代码例如以下:

  1. def receive_data(data)
  2. @buffer << data
  3. @buffer.each_request do |request|
  4. begin
  5. receive_request(request)
  6. rescue => e
  7. close_connection_after_writing
  8. logger.warn("Disconnected client after error")
  9. logger.log_exception(e)
  10. end
  11. end
  12. end

从代码中能够看到,当buffer中有请求的时候,通过receive_request(request)方法来进一步提取请求。再进入receive_request(request)方法中。能够看到通过process(request)来处理请求。

接着进入真正请求处理的部分,也就时process(request)的实现:

  1. def process(request)
  2. case request
  3. when Protocol::PingRequest
  4. response = request.create_response
  5. send_response(response)
  6.  
  7. when Protocol::ListRequest
  8. response = request.create_response
  9. response.handles = Server.container_klass.registry.keys.map(&:to_s)
  10. send_response(response)
  11.  
  12. when Protocol::EchoRequest
  13. response = request.create_response
  14. response.message = request.message
  15. send_response(response)
  16.  
  17. when Protocol::CreateRequest
  18. container = Server.container_klass.new
  19. container.register_connection(self)
  20. response = container.dispatch(request)
  21. send_response(response)
  22.  
  23. else
  24. if request.respond_to?(:handle)
  25. container = find_container(request.handle)
  26. process_container_request(request, container)
  27. else
  28. raise WardenError.new("Unknown request: #{request.class.name.split("::").last}")
  29. end
  30. end
  31. rescue WardenError => e
  32. send_error(e)
  33. rescue => e
  34. logger.log_exception(e)
  35. send_error(e)
  36. end

可见。在warden server中。请求类型能够简单分为5种:PingRequest, ListRequest, EchoRequest, CreateRequest和其它请求,像NetIn请求则属于其它请求中的一种,程序运行进入case语句块的else分支,也就是:

  1. if request.respond_to?(:handle)
  2. container = find_container(request.handle)
  3. process_container_request(request, container)
  4. else
  5. raise WardenError.new("Unknown request: #{request.class.name.split("::").last}")
  6. end

代码清晰可见,warden server首先通过handle找到详细是给哪一个warden container发送请求,然后调用process_container_request(request, container)方法。进入process_container_request(request, container)方法能够看到:增加请求类型不为StopRequest以及StreamRequest,则进入case语句块的else分支。运行代码:

  1. response = container.dispatch(request)
  2. send_response(response)

能够看到。是调用了container.dispatch(request)方法才返回了response。

下面进入warden/lib/warden/container/base.rb文件里,该文件的dispatch方法主要实现了warden server接收到请求并预处理之后,怎样分发运行详细的请求,代码例如以下:

  1. def dispatch(request, &blk)
  2. klass_name = request.class.name.split("::").last
  3. klass_name = klass_name.gsub(/Request$/, "")
  4. klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
  5. klass_name = klass_name.downcase
  6.  
  7. response = request.create_response
  8.  
  9. t1 = Time.now
  10.  
  11. before_method = "before_%s" % klass_name
  12. hook(before_method, request, response)
  13. emit(before_method.to_sym)
  14.  
  15. around_method = "around_%s" % klass_name
  16. hook(around_method, request, response) do
  17. do_method = "do_%s" % klass_name
  18. send(do_method, request, response, &blk)
  19. end
  20.  
  21. after_method = "after_%s" % klass_name
  22. emit(after_method.to_sym)
  23. hook(after_method, request, response)
  24.  
  25. t2 = Time.ndef dispatch(request, &blk)
  26. klass_name = request.class.name.split("::").last
  27. klass_name = klass_name.gsub(/Request$/, "")
  28. klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
  29. klass_name = klass_name.downcase
  30.  
  31. response = request.create_response
  32.  
  33. t1 = Time.now
  34.  
  35. before_method = "before_%s" % klass_name
  36. hook(before_method, request, response)
  37. emit(before_method.to_sym)
  38.  
  39. around_method = "around_%s" % klass_name
  40. hook(around_method, request, response) do
  41. do_method = "do_%s" % klass_name
  42. send(do_method, request, response, &blk)
  43. end
  44.  
  45. after_method = "after_%s" % klass_name
  46. emit(after_method.to_sym)
  47. hook(after_method, request, response)
  48.  
  49. t2 = Time.now
  50.  
  51. logger.info("%s (took %.6f)" % [klass_name, t2 - t1],
  52. :request => request.to_hash,
  53. :response => response.to_hash)
  54.  
  55. response
  56. endow
  57.  
  58. logger.info("%s (took %.6f)" % [klass_name, t2 - t1],
  59. :request => request.to_hash,
  60. :response => response.to_hash)
  61.  
  62. response
  63. end

首先提取出请求的类型名,假设是NetIn请求的话,提取出来的请求的类型名称为net_in,随后构建出方法明do_method。也就是do_net_in。接着就通过send(do_method, request, response, &blk)方法,将參数发送给do_net_in方法。

如今就是进入warden/lib/warden/container/features/net.rb文件里, do_net_in方法实现例如以下:

  1. def do_net_in(request, response)
  2. if request.host_port.nil?
  3. host_port = self.class.port_pool.acquire
  4.  
  5. # Use same port on the container side as the host side if unspecified
  6. container_port = request.container_port || host_port
  7.  
  8. # Port may be re-used after this container has been destroyed
  9. @resources["ports"] << host_port
  10. @acquired["ports"] << host_port
  11. else
  12. host_port = request.host_port
  13. container_port = request.container_port || host_port
  14. end
  15.  
  16. _net_in(host_port, container_port)
  17.  
  18. @resources["net_in"] ||= []
  19. @resources["net_in"] << [host_port, container_port]
  20.  
  21. response.host_port = host_port
  22. response.container_port = container_port
  23. rescue WardenError
  24. self.class.port_pool.release(host_port) unless request.host_port
  25. raise
  26. end

可见,假设请求port没有指定的话,那么就使用代码host_port = self.class.port_pool.acquire来获取port号,默认情况下,containerport号与hostport号保持一致,有了这两个port号之后,运行代码_net_in(host_port, container_port)。 真正实现port映射,例如以下:

  1. def _net_in(host_port, container_port)
  2. sh File.join(container_path, "net.sh"), "in", :env => {
  3. "HOST_PORT" => host_port,
  4. "CONTAINER_PORT" => container_port,
  5. }
  6. end

能够清晰的看到是。使用了容器内部的net.sh脚本来实现port映射。如今进入warden/root/linux/skeleton/net.sh脚本。进入參数为in的运行部分:

  1. "in")
  2. if [ -z "${HOST_PORT:-}" ]; then
  3. echo "Please specify HOST_PORT..." 1>&2
  4. exit 1
  5. fi
  6. if [ -z "${CONTAINER_PORT:-}" ]; then
  7. echo "Please specify CONTAINER_PORT..." 1>&2
  8. exit 1
  9. fi
  10. iptables -t nat -A ${nat_instance_chain} \
  11. --protocol tcp \
  12. --destination "${external_ip}" \
  13. --destination-port "${HOST_PORT}" \
  14. --jump DNAT \
  15. --to-destination "${network_container_ip}:${CONTAINER_PORT}"
  16. ;;

可见,该脚本这部分的功能是在host主机创建一条DNAT规则。使得host主机上全部HOST_PORTport上的网络请求。都转发至network_container_ip:CONTAINER_PORT上。也就是完毕了目标地址IP转变。

promise_extract_droplet:

该环节主要完毕的是让container执行脚本。使得container容器将位于host主机的droplet文件,解压至container内部,代码内容例如以下:

  1. def promise_extract_droplet
  2. Promise.new do |p|
  3. script = "cd /home/vcap/ && tar zxf #{droplet.droplet_path}"
  4. container.run_script(:app, script)
  5. p.deliver
  6. end
  7. end

promise_exec_hook_script('before_start'):

这部分主要完毕的功能是让容器执行名为before_start的脚本。在老的版本号中,该部分的设置默觉得空。

promise_start:

这部分主要完毕的是应用的启动。

当中创建容器的时候。返回的port号,会被DEA保存。终于。当DEA启动应用的时候,因为DEA会将port号作为參数传递给应用程序的启动脚本。因此当应用启动时会自己主动去监听已经为它设置的port,即完毕port监听。

关于作者:

孙宏亮,DAOCLOUD软件project师。两年来在云计算方面主要研究PaaS领域的相关知识与技术。

坚信轻量级虚拟化容器的技术,会给PaaS领域带来深度影响,甚至决定未来PaaS技术的走向。

转载请注明出处。

本文很多其它出于我本人的理解。肯定在一些地方存在不足和错误。

希望本文可以对接触wardenport映射的人有些帮助,假设你对这方面感兴趣,并有更好的想法和建议。也请联系我。

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

Cloud Foundry中DEA与warden通信完毕应用port监听的更多相关文章

  1. Cloud Foundry中DEA启动应用实例时环境变量的使用

    在Cloud Foundry v2中,当应用用户须要启动应用的实例时.用户通过cf CLI向cloud controller发送请求,而cloud controller通过NATS向DEA转发启动请求 ...

  2. Cloud Foundry中warden的网络设计实现——iptable规则配置

    在Cloud Foundry v2版本号中,该平台使用warden技术来实现用户应用实例执行的资源控制与隔离. 简要的介绍下warden,就是dea_ng假设须要执行用户应用实例(本文暂不考虑ward ...

  3. Cloud Foundry中 JasperReports service集成

    Cloud Foundry作为业界第一个开源的PaaS解决方案,正越来越多的被业界接受和认可.随着PaaS的发展,Cloud Foundry顺应潮流,充分发挥开源项目的特点,到目前为止,已经支持了大批 ...

  4. Cloud Foundry中通用service的集成

    目前,CloudFoundry已经集成了很多第三方的中间件服务,并且提供了用户添加自定义服务的接口.随着Cloud Foundry的发展,开发者势必会将更多的服务集成进Cloud Foundry,以供 ...

  5. Cloud Foundry中gorouter对StickySession的支持

    Cloud Foundry作为业界出众的PaaS平台,在应用的可扩展性方面做得很优秀. 详细来讲,在一个应用须要横向伸展的时候,Cloud Foundry能够轻松地帮助用户做好伸展工作,也就是创建出一 ...

  6. Cloud Foundry中vmc tunnel与caldecott原理

    在Cloud Foundry中,用户可以vmc create-service创建一个service instance,但是常规情况下,用户不能手动地进一步对service instance进行设计.以 ...

  7. vue任意关系组件通信与跨组件监听状态 vue-communication

    大家好!我是木瓜太香! 众所周知,组件式开发方式给我们带来了方便,不过也引入了新的问题,组件之间的数据就像被一道无形的墙隔开,如果我们希望临时让两个组件直接通信,vuex 太巨,而 $emit 又不好 ...

  8. ios中键值编码kvc和键值监听kvo的特性及详解

    总结: kvc键值编码  1.就是在oc中可以对属性进行动态读写(以往都是自己赋值属性)           2. 如果方法属性的关键字和需要数据中的关键字相同的话                  ...

  9. js中如何在不影响既有事件监听的前提下新增监听器

    一. 需求澄清 比如某个按钮已经绑定了2-3个对Window对象的load事件的监听,现在需要添加一个新的对click事件的监听器,但在一定条件下才会同时触发原有的2-3个load监听器,否则只触发新 ...

随机推荐

  1. glance rabbit

  2. [Apple开发者帐户帮助]三、创建证书(3)创建企业分发证书

    作为Apple Developer Enterprise Program的成员,您可以创建多个企业分发证书. 所需角色:帐户持有人或管理员. 在证书,标识符和配置文件中,从左侧的弹出菜单中选择iOS, ...

  3. 混个脸熟 -- go

    一.第一个项目:hello world src/day1/example1/main.go package main import "fmt" func main(){ fmt.P ...

  4. 工具分享1:文本编辑器EditPlus、汇编编译器masm、Dos盒子

    工具已打包好,需要即下载 链接 https://pan.baidu.com/s/1dvMyvW 密码 mic4

  5. Djnago进阶

    详情请戳 Cookie和Session及分页设置 Ajax Django中间件 Form和ModelForm组件 auth认证组件 Django 缓存的使用 Django 信号的使用 Django a ...

  6. C#方法的练习

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Demo ...

  7. 5.26 idea生成javadoc

  8. jQuery学习笔记之概念(1)

    jQuery学习笔记之概念(1) ----------------------学习目录-------------------- 1.概念 2.特点 3.选择器 4.DOM操作 5.事件 6.jQuer ...

  9. AI:IPPR的数学表示-CNN结构/参数分析

    前言:CNN迎接多类的挑战 特定类型的传统PR方法特征提取的方法是固定的,模式函数的形式是固定的,在理论上产生了特定的"局限性" 的,分类准确度可以使用PAC学习理论的方法计算出来 ...

  10. 三维重建面试0:*SLAM滤波方法的串联综述

    知乎上的提问,高翔作了回答:能否简单并且易懂地介绍一下多个基于滤波方法的SLAM算法原理? 写的比较通顺,抄之.如有异议,请拜访原文.如有侵权,请联系删除. 我怎么会写得那么长--如果您有兴趣可以和我 ...