warden容器源码解析
趁着情人节&元宵节这天科研,居然能在CF平台上部署tuscany应用了,无比欢喜,下一步,不着急,先整理一下warden容器的知识。下面的内容不全是原创,参考部分会在最后附上链接。1 warden部署和运行的分析warden rakefile1 编译 src/成可执行文件,最后会成为container的一部分:cdxxx && make allclosefds
趁着情人节&元宵节这天科研,居然能在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使用情况。类似setquota,quotacheck等
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时,还是没有这个环境变量?
4.chroot后 apt-get install xxx #安装软件
2 资源隔离与限制
在warden-client输入create命令
linux.rb中do_create,执行create.sh和start.sh
2.1 create.sh复制skeleton,包含编译后的c的可执行文件(iomux,spawn,)
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.sh(unshared-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的值为on,nullglob为on时对于通配符匹配时,若匹配不到时为空(相对应的为通配符本身)。
3.0.4 int stat(constchar *restrict pathname, struct stat *restrict buf);
提供文件名字,获取文件对应属性。
3.0.5 aufs:lucid,natty
一种文件格式,可以mount到目录,同时控制只读和读写。
3.0.6 overlayfs:precise
另一种文件格式,在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
- spawn,link,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-client与warden-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重新link到spawn上),
iomux-link和iomux-spawn 涉及io时的一些锁机制
Ø oom通过eventfd得到内存不够的通知,kill
Ø repquota报告quota的使用情况,类似setquota,quotacheck等
wsh, wshd是container与外界连接手段,可判断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
Rakefile:rake命令所定义的配置任务文件
3.2.1 warden在rakefile中:
Ø namespace setup
setup中,将可运行的wshd和wsh,iomux-spawn,iomux-link,到拷贝到skeleton目录下。
sudo- E unshared -m root/linux/rootfs/setup.sh /var/warden/rootfs
Ø namespace warden
启动服务器:调用Warden::server的setup和run!方法。
3.2.2 setup
1 setup_network # container 的 ip及其子网掩码相关;Pool::Network -> Warden::Network::Address &Warden::Network::Netmask)
2 setup_port#(Pool::Port)container的 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 输入create时,repl_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.rb中setup函数)
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) 挂载cpu,memory
先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设置container的disk部分,quotacheck,quotaoff,
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 < Connection,class HealthCheck< Connection,
ClientConnection< Connection
Module State
在lib/warden/server.rb中,self.run!方法下,调用EM.run,并创建Fiber来执行container_setup,
recover_container,使用了EM的start_unix_domain_server的方法启动了一个unixsock,第二个参数ClientConnection定义了接收到东西怎么处理。创建一个Drainer,完成后,创建另一个Fiber将注册的container_klass写snap_shot.调用EM.stop。
unix_domain_path=/tmp/warden.sock
user,pool_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
jobs,connections集合,events集合,state
维护network,handle,container_id,host_ip,container_ip,uid,
before_create,after_create,around_create,do_create,before_destroy,after_destroy,
around_spawn,do_spawn,
hook(name,request,response,&blk)调用method(name).call(&blk)
4.3 base.rb中dispatch方法执行create过程:
1)拼接before_create:执行hook(before_create)
2)执行container/features/quota.rb的before_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.sh与base/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_stop:sh 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找到了会一一补上
更多推荐
所有评论(0)