warden容器源码解析

趁着情人节&元宵节这天科研,居然能在CF平台上部署tuscany应用了,无比欢喜,下一步,不着急,先整理一下warden容器的知识。

下面的内容不全是原创,参考部分会在最后附上链接。

1 warden部署和运行的分析

warden rakefile

1 编译 src/成可执行文件,最后会成为container的一部分:cd xxx && make all

   closefds                                         #close file descriptor简称。释放文件描述符fd

   iomux-link, iomux-spawn              #涉及io时的一些锁机制

   oom                                              #out-of-memory简称。内存不足时,一种保护机制

   repquota                                      # 报告disk使用情况。类似setquotaquotacheck

   wsh,wshd                                    #container与外界连接手段,可判断container是否还活着

                                                      #“wsh(d)” – warden shell (daemon) 

它们其中的一部分会被复制到  ubuntu或者insecure bin/ 目录里,最终会成为container的一部分:

在脚本root/linux/rootfs/ubuntu.sh中。

 还有就是会下载一个精简过的Linux操作系统(称之为rootfs)供container使用

1. unshare - run program with some namespaces unsharedfrom parent。有点类似clone(),fork()等。

     sudo-E unshare -m root/linux/rootfs/setup.sh "/tmp/warden/rootfs"


2.  debootstrap- Bootstrap a basic Debian system

debootstrap–verbose –include "openssh-server,rsync" "lucid""/tmp/warden/rootfs"  http://archive.ubuntu.com/ubuntu/

debootstrap得到一个与外界隔离(严格意义上来讲,没有绝对隔离)的操作系统

rootfs大小:304M只读文件

bin boot  dev etc  home  lib lib64  media  mnt opt proc  root  sbin  selinux  srv sys  tmp usr  var

container的创建、删除所需要的时间,以及里面所包含的软件是很少的!

container大小:3.2M

container= src + skeleton + mini rootfs


·      src 指的是warden源代码里的src部分。src编译后会得到几个可执行文件,其中的 iomux-link, iomux-spawn,wsh, wshd则成为container的一部分。

·      skeleton 指的是warden源代码里的root/linux/skeleton部分。在我们‘create container’时,会原封不动的复制过来,成为container的又一部分。

·      tmp/rootfs 


这是restkuan提出的概念,为了是与上文提到的rootfs区分开来,并且说明它们之间又有一定联系.

skeleton目录下,lib/common.sh

在每个container目录下,都会有一个mnt目录

定义overlay_directory_in_rootfs,会覆盖rootfs目录下的/dev,/etc,/home,/sbin,/tmp文件夹。

setup_fs_ubuntu中,根据系统的版本号,来将读写部分(不包含只读)

mount到这个mnt中的dev,etc, home, sbin, tmp ,成为container的另一部分。

lucid|natty|oneiric

    mount -n -taufs -o br:tmp/rootfs=rw:$rootfs_path=ro+wh none mnt

precise

mount -n -toverlayfs -o rw,upperdir=tmp/rootfs,lowerdir=$rootfs_path none mnt

  The read-writefilesystem is created by formattinga large sparse file. Because the size of this file is fixed, the filesystemthat it contains cannot grow beyond this initial size.读写文件系统需要格式化一块大的文件

3. chroot -run command or interactive shell with special root directory。根目录的切换工作

 chroot"/tmp/warden/rootfs"env -iPATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games" /bin/bash

问题:其实在这里已经设置了环境变量/usr/sbin,但是sudochroot时,还是没有这个环境变量?

.chroot apt-get install xxx #安装软件


资源隔离与限制

warden-client输入create命令

linux.rbdo_create,执行create.shstart.sh

2.1 create.sh复制skeleton,包含编译后的c的可执行文件(iomuxspawn,)

bin  destroy.sh  etc  jobs  lib mnt  net_rate.sh  net.sh  run setup.sh  snapshot.json start.sh  stop.sh  tmp

 

2.2 执行skeleton/setup.shunshared-m setup.sh脱离父子关系

a)     container和warden相关的网络设置

b)     tmp/rootfs 执行common.sh中的setup_fs

               dev  etc  home sbin  var

c)      文件权限设置 chown,mknod

d)      默认vcap用户创建

   useradd -mU -u 10000 -s /bin/bashvcap

可以通过chroot切换进mini rootfs,并且执行一些操作包括创建用户。

我好像尝试的时候不行?报错failed to run command ‘/bin/bash’: No such file or directory

run, spawn, stream等命令都是以vcap用户的身份来运行的

 

2.3 以hook(钩子)的形式完成其它一些操作

a)   wshd文件的创建(前文提到过,判断container的死活它起到很大的作用。除此之外,它还有很大作用,详情请看源代码……。)

b)     cgroup相关部分的创建

hook-parent-after-clone.sh

在cgroup下的每一个subsystem建instance-id目录,存放该handle的资源限制

 

通过unshare, debootstrap, chroot等命令我们得到了一个相对隔离的环境


3 warden源码学习

3.0 基础知识

   3.0.1 rv =unshare(CLONE_NEWNS);

      unshare这个调用,可以把挂载的文件系统设置成只在新的挂载命名空间(mountnamespace)中可见。


   3.0.2 execvp(argv[0], argv);

       execvp()会从PATH环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。


   3.0.3 shopt -s nullglob

    设置shell环境变量nullglob的值为onnullglobon时对于通配符匹配时,若匹配不到时为空(相对应的为通配符本身)。


  3.0.4 int stat(constchar *restrict pathname, struct stat *restrict buf);

     提供文件名字,获取文件对应属性。


  3.0.5 aufslucidnatty

     一种文件格式,可以mount到目录,同时控制只读和读写。


  3.0.6 overlayfsprecise

     另一种文件格式,在ubuntu11.04后开始替代aufs作为官方livecd的文件格式。


  3.0.7ruby中的send方法
      send
其实就是动态地根据名字调用函数传递后面的内容作为调用参数api函数原型为
      obj.send(symbol [, args...]) => obj


3.1 warden有关资源隔离和资源管理

包括warden-protocol,

3.1.1       warden-protocol

  •   create、stop、destroy、info

    可以创建容器,停止,删除容器,查看容易的info

  •   spawnlink,run,stream

spawn: 在container内部spawna command,返回jobid.新建一个进程

link: do blocking read on resultsfrom a job,读取spawn进程的信息?job完成后,才会有LinkResponse

stop: stop container中的所有进程

stream : do blocking stream on results from a job

  •   net_in,net_out, copy_in, copy_out
  • limit_memory, limit_disk, limit_bandwidth
  • ping, list, echo

 

3.1.2       warden-client客户端驱动,提供与warden的阻塞通讯client。依赖warden-protocol。

  client/v1.rb中对应于protocol提供的方法,有self转换create,stop,destroy,info,spawn,link,stream,run,net,limit_memory,limit_disk, limit_bandwidth,ping,list, echo

client.rb中提供connect,disconnect,read,write,stream,call等方法。

 

3.1.3       em-warden-client基于eventmachine的client,

依赖 warden-clientwarden-protocol通过 unix socket来通讯.加入eventmachine和fiber

3.1.4       warden

root下是insecure和linux的执行shell脚本

src下为c源码,包括

Ø closefds用来快速复制一个环境,其中有一堆shell脚本的运行,释放文件描述符。

Ø  iomux分为iomux-spawn(把子进程的pid写到stdout,表明它已准备好接收连接,在尝试连接后一直等待)和iomux-link(根据pid重新linkspawn上)

iomux-link和iomux-spawn 涉及io时的一些锁机制

Ø oom通过eventfd得到内存不够的通知,kill

Ø   repquota报告quota的使用情况,类似setquotaquotacheck

   wsh, wshdcontainer与外界连接手段,可判断container是否还活着。

       创建虚拟机container

       通过命令与container交互

3.2 下面以create命令为例进行追踪

调用过程:
客户端bin/warden >> lib/warden/repl_v2_runner.rb>> Warden::Client>> warden.sock>>服务器lib/warden/server.rb << Warden::Server.run!<< Rakefile

处理调用:
server.rb > ClientConnection > receive_request > process(request) >process_container_request(request, container) > base.rb >dispatch(request, &blk) > send(do_method, request, response, &blk)-> linux.rb > do_xxx >
shellscript

 

Rakefilerake命令所定义的配置任务文件

 

3.2.1  wardenrakefile中:

 Ø  namespace setup

  setup中,将可运行的wshdwshiomux-spawniomux-link,到拷贝到skeleton目录下。

  sudo- E unshared -m root/linux/rootfs/setup.sh /var/warden/rootfs

 

 Ø  namespace warden

  启动服务器:调用Warden::serversetuprun!方法。


3.2.2  setup

    1     setup_network  # container  ip及其子网掩码相关;Pool::Network -> Warden::Network::Address &Warden::Network::Netmask

   2     setup_port#Pool::Portcontainer port 相关

   3     setup_user#Pool::Uid# container uid 相关

   4     最终它们都会涉及到 Warden::Container::Linux

 

3.2.3  sudo bundle exec bin/warden

启动客户端:在bin目录下有warden脚本(ruby),这将会执行lib/warden/repl_v2_runner.rb文件.调用repl_v2.rb中的代码. initialize中创建Warden::Client.

先使用Warden:Client发起对warden.sock的连接,然后在start方法中对命令行模式的交互(self.run)进行反馈:process_line(执行cmd),保存cmd历史,tab自动补全,

返回命令执行码。

 

3.2.4  输入createrepl_v2.rb执行到process_line中的respond_to的判断

if respond_to?words[0].to_sym

当前对象中是否有方法名为words[0].to_sym变量值的方法

3.2.5 create

repl.rb果断有这个create方法,然后talk_to_warden。就进入了client写的过程了。

cmd如果是run,则需要调用client.call(command)

3.2.6 warden启动

sudo bundle exec rakewarden:start[config/linux.yml]
这将使在bundle定义的环境下,执行warden/warden/Rakefile里写好的任务:Warden::Server.run!主要完成以下几件事情。

   1 调用root/linux/setup.sh(这个是在linux.rbsetup函数)

       a)       设置环境变量,包括pool_network,allow_network,deny_network,container_rootfs_path,

          container_depot_path,container_depot_mount_point_path,container_depot_mount_point_path,disk_quota_enabled.

       b)       挂载cpumemory

             先umount /tmp/warden/cgroup,然后再将tmpfs挂载到/tmp/warden/container上去

            然后mkdircpu/cpuacct/devices/memory(这些目录就是subsystems)

            再挂载cgroup/subsystems

        c)       ./net.sh setup设置container网络,脚本是一堆iptables命令

        d)       quotaon /tmp/warden/container设置containerdisk部分,quotacheckquotaoff

     2    recover_containers

         不符合条件的container有两种:

               1.没有快照信息,也就是snapshot.json记录文件;

               2.‘了的,比如:超过了设置的glance_time还没有连接或没有run/wshd.sock.

涉及container_grace_time配置;snapshot.json,run/wshd.sock文件等。


3.2.7 server.rb warden-server源码

server.rb定义classDrainer < Connectionclass HealthCheck< Connection

ClientConnection< Connection

 Module State

lib/warden/server.rb中,self.run!方法下,调用EM.run,并创建Fiber来执行container_setup,

  recover_container,使用了EMstart_unix_domain_server的方法启动了一个unixsock,第二个参数ClientConnection定义了接收到东西怎么处理。创建一个Drainer,完成后,创建另一个Fiber将注册的container_klasssnap_shot.调用EM.stop。

  unix_domain_path=/tmp/warden.sock

  userpool_start_uid:10000

  fiber_id为纤程号

  register_connection方法,添加一个conn,并且调用run_machine方法

  run_machine方法,根据state状态判断:

  如果是START,停止接收新的conn。停止server,并且在EM.next_tick方法中,先就状态改为Acceptor_closed,调用run_machine.完成后,将状态改为wait_acceptor_closed

  如果是Acceptor_clsed状态,将状态改为Draining

  如果状态为Done,状态不改变,调用block的回调方法


内部类ClientConnection中的process方法具体处理了create:

whenProtocol::CreateRequest
container = Server.container_klass.new
container.register_connection(self)
response = container.dispatch(request)这里的request就是CreateRequest

send_response(request)调用send_data

container_klass为配置中的Warden::Container::Linux

0   Class HealthCheck < EM::Connection定义receive_data(data)

1   server.rb中receive_data方法中调用receive_request

2   server.rb中receive_request方法,接收请求,并处理process(request)

3   process(request)方法,如果是Ping/List/Echo/CreateRequest(container=Server.container_klass.new,创建一个Linux对象)分别处理,其余的都交由container,调用process_container_request(request).

4   process_container_request,如果是StopRequest/StreamRequest,分别处理,else由container.dispatch(request).base.rb中有dispatch方法。

5   base.rb中dispatch(request,&blk),调用before_create,hook,around_create,hook do_create,send(do_method)

after_create,emit,hook

6  find_container(handle)方法,从Server.container_klass.register[handle].tap中,查找container,并container.register_connection(self)

最终,将会执行container/linux.rb中的do_create方法。warden启动的脚本.


self.recover_container删除(调用destroy.sh)没有snapshot的container,并且创建一个新的container?

 

4 shell脚本

4.1 linux/base/setup.sh

这个脚本在运行安装warden时会被执行,按照执行过程:
1
)分析本机最近的apt源地址。
2
debootstrap/tmp/warden
3
debootstrap的作用主要是可以用于在系统的某个目录中安装一套基本系统.参考链接点击打开链接

4)使用chroot将子系统中的缺失软件安装好


4.2  container/base.rbdispatch create

module state中定义class Base, Born,Active,Stopped,Destroyed

snapshot

jobsconnections集合,events集合,state

维护networkhandlecontainer_id,host_ip,container_ip,uid,

before_create,after_create,around_create,do_create,before_destroy,after_destroy,

around_spawn,do_spawn,

hookname,request,response,&blk)调用method(name).call(&blk)


4.3 base.rbdispatch方法执行create过程:

1拼接before_create:执行hook(before_create)

2)执行container/features/quota.rbbefore_create方法:执行setquota(uid)

       怎么跳到quoto去执行的?
3
)执行container/base.rb中的before_create方法,check_state_inf(Born),acquire(:handle)获取资源。

      产生一个十六进制的handle值(也就是每个container的标识)
4
setquota:shsetquota-u uid block_soft blokc_hard inode_soft inode_hard
5
linux.rb do_create参加下面

6)  执行around_create:dispatch(DestroyRequest.new)

7)  执行do_create: raiseWardenError.new这个方法被linux.rb覆写

8)  执行after_create: write_snapshot

4.4 container/linux.rb的do_create过程

linux.rb中在Moudle Warden,Container中定义ClassLinux < Base

alive?方法使用wshd.sock创建一个UNIXSOCKET

1)sh create.sh脚本的执行:先传入rootfs目录,检查是否存在;

create.sh中执行unshare -m setup.sh

2)unshare的作用是在指定的环境下执行程序setup.sh,不影响上一级环境。

3)此处的skeleton/setup.shbase/setup.sh相比,没有了debootstrap的过程。

      setup.sh中设置了cgroup、磁盘挂载setup_fs、限额、网络。

4)  创建完container后,write_bind_mount_commands。如果需要,会执行hook-parent-before-clone.sh(setup_fs)。

      绑定mount。mount -n--bind src des
6)修改limits.conf的参数???有吗???
8
)执行start.sh:执行net.sh中的setup。建立子网,打通两条ssh通道。wshd???

child_fork、child_handle、child_accept、child_run、child_continue、child_start、parent_run

main调用parent_run、

parent_run调用unshare,child_start,
9)完成create

4.4 container/linux.rb中的do_destroy, create_job,do_copy_in,do_copy_out

do_stopsh stop.sh

do_destroy过程,执行sh destroy.sh主要是rm-rf

create_job调用spawn_job(run/wsh --socket wshd.sock  --user root /bin/bash )

iomux-spawn启动后,可能在iomux-link连接到它之前就fail了,因此在iomux-spawn job回调方法中,我们resume the yielded fiber。检测spawner是否fail,如果fail,spawner.run。当iomux-spawn ready后,将child’s pid写到stdout。

do_copy_in,do_copy_out调用perform_rsync(执行sh rsync -erun/wsh --socket run/wshd.sock --rsh -r -p --links src dst)

linux.rb在setup过程中,调用sh setup.sh

linux.rb中includeFeatures::Cgroup,Net,MemLimit,Quota,没看到调用?

Linux<Base, Base中定义了do_limit_memory方法,linux.rb中requiremem_limit,这样,Linux中就有do_limit_memory方法了。

4.5warden/container/spawn.rb

创建moudle Spawn

定义sh(*args)方法:DeferredChild.new ,child.run

定义DeferredChild类,Thin utility class aroundEM::POSIX::Spawn::Child

 run方法:Child.new

 set_deferred_success,插入logmessage

  set_deferred_failure

 define yield如果success,fiber.resume(:ok),否则fiber.resume(:err)

 

4.6 warden/event_emitter.rb

定义moudle EventEmitter

定义一个_listeners映射表

定义addlistener方法,remove_listener,emit方法(调用每一个listenerevent)

 

4.7 warden/src/oom/oom.c利用event_fd检查是否有oom

 

5 资源控制warden/container/features/,mem,disk,cpu,net

5.1 cgroup.rb

require spawn.rb

cgroup.rb主要是命令行info时读取cgroup中container的memory和cpu信息

do_info

read_memory_stats

read_cpu_stats


5.2 mem_limit.rb

require spawn

调用”src/ /oom”

child=DeferredChild.new(“oom”,container.cgroup_path(:memory))

child.run

是负责配置内存限制,写入cgroup中,并提供oom,就是内存溢出时的行为
kill,restore,

do_limit_memory方法,调用limit_memory,

limit_memory私有方法,先判断是否设置oom_notifier,然后为了保证memory.limit_in_bytes<=

memory.memsw.limit_in_bytes.执行两次写文件操作。

start_oom_notifier_if_needed

oomed,initialize

 

5.3 net.rb ,

负责container的网络I/O,通过调用 net_rate.sh来配置tc(traffic control)的。

5.4 quato.rb,

负责container的磁盘份额。

 

5.5 Cgroup

cgroup下有四个子系统:cpu,memory,devices,cpuacct

 这个比较复杂,是交由linux系统完成的。我自己不是很清楚


5.6 warden/lib/warden/container这里都是容器的操作

spawn.rb

一堆文件,前面几个是cgroup的控制文件,然后是instance-16haaer1lpc,新创建的容器,已经加入到memory的控制组群了。后面是一些memory的参数,最后一个tasks,就是存放要监控的进程ID.

warden配置相关的,一个是memory.limit_in_byes,限制最大内存。一个是memory.memsw.limit_in_bytes,连同swap也一起限制。

warden其实是将container委托给cgroup来管理,监控,以及反馈的。 那就是创建container时,warden负责把container挂到cgroup的memory组群的内存树上,这样用户通过warden提供的命令行修改memory时, 也是warden负责修改cgroup组群上的相应container的memory.limit_in_bytes和memory.memsw.limit_in_bytes。具体我们来看一下命令行。

 

6 参考文献

1 http://blog.csdn.net/restkuan/article/details/10018885此文讲的是warden的安装启动,很详细。

2 http://blog.csdn.net/restkuan/article/details/13630351此文讲的是warden的资源隔离,与上文是同一作者。

3 http://www.54chen.com/architecture/cloud-foundry-warden-part3.html此文主要是通过源码的角度来讲解warden的启动。

4 http://blog.csdn.net/k_james/article/details/8523934此文主要讲解了warden-client

其余的参考文献,等lz找到了会一一补上


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值