lxc的内存、网络、磁盘的监控管理(一)

来源:互联网 发布:免费普通话训练软件 编辑:程序博客网 时间:2024/05/18 08:19

Lxc实现与cgroup关系

LXC 是 Linux Container的缩写。Linux 容器技术是一种内核虚拟化技术,它提供了轻量级的虚拟化技术,可以在单一控制主机上同时提供多个虚拟环境(即容器)以隔离进程和资源,每个虚拟环境拥有自己的进程和独立的命名空间。在基于容器的虚拟化技术中,进程不再是个全局概念,而是从属于某个特定的容器。理想情况下,进程跟容器之间是动态关联的,进程可以在容器之间迁移。在基于容器的虚拟化技术中,容器既是资源容器,也是隔离的命名空间,它能有效地将由单个操作系统管理的资源划分到隔离的组中,以更好地在隔离的组之间平衡有冲突的资源的占用需求。

实现LXC两大技术之一是命名空间。基于容器的虚拟化技术主要通过隔离操作系统内核的对象(例如 PID、UID、系统共享内存、IPC、net等等)来完成安全性的隔离,具体就是运用Namespace名字空间将原来的全局对象(句柄、UID等)隔离到完全不同的名称空间里,不同虚拟机之间是完全不可见的,因此它们也不能访问到名称空间之外的对象。全局对象在每个虚拟机内本地化了,换句话说,全局的对象标记仅仅是在每个虚拟机内部全局。Namespace特性的具体使用方式就是在clone时加入相应的flag(NEWNS NEWPID等等)。LXC正是通过在clone时设定这些flag,为进程创建一个有独立PID,IPC,FS,Network,UTS空间的容器。一个容器就是一个虚拟的运行环境,对容器里的进程是透明的,它会以为自己是直接在一个系统上运行的。

实现LXC另一个技术是cgroups。LXC在资源管理方面依赖与内核的 Cgroups (Control Groups) 子系统,Cgroups 子系统是内核提供的一个基于进程组的资源管理的框架,可以为特定的进程组限定可以使用的资源。

linux内核提供了cgroups控制组(control groups)的功能,最初由google的工程师提出,后来被整合进Linux内核。cgroups适用于多种应用场景,从单个进程的资源控制,到实现操作系统层次的虚拟化提供了以下功能:

1、限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发OOM(out of memory)。

2、进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。

3、记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间。

4、进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。

5、进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。

 

下面介绍lxc常用的管理接口:

1、lxc-start 创建容器,并在容器中执行给定命令

2、lxc-kill发送信号给容器中的第一个用户进程(容器内部进程号为2的进程)

3、lxc-cgroup用于获取或调整与cgroup相关的参数

简单介绍下命令用法:

lxc-start  -n name [-f config_file] [-cconsole_file] [-d] [-s KEY=VAL] [command]

      -d 将容器当做守护进程执行

     -f  后面跟配置文件

     -c 指定一个文件作为容器console的输出,如果不指定,将输出到终端

     -s 指定配置

例如:

lxc-start -n foo1 -f ./lxc-cfg -d /bin/bash

这里所说的配置文件提供了cgroups、提供单独的网络、根目录空间等,以P4080DS单板为例的配置文件如下,具体含义有时机再进行分析。

         lxc.utsname= foo23

         lxc.network.type= macvlan

         lxc.network.macvlan.mode= vepa

         lxc.network.flags= up

         lxc.network.link= br0

         lxc.network.name= eth0

         lxc.network.ipv4= 192.168.0.23

         lxc.network.type= macvlan

         lxc.network.macvlan.mode= vepa

         lxc.network.flags= up

         lxc.network.link= br0

         lxc.network.name= eth1

         lxc.network.ipv4= 192.168.128.23

         lxc.tty= 1

         lxc.pts= 1           

         lxc.cgroup.cpuset.cpus= 0,1

         lxc.cgroup.cpu.shares= 1234

         lxc.cgroup.devices.deny= a

         lxc.cgroup.devices.allow= c 1:3 rw

         lxc.cgroup.devices.allow= b 8:0 rw

         lxc.rootfs= /var/lib/lxc/temp/rootfs

         lxc.mount.entry=/lib/var/lib/lxc/temp/rootfs/lib none ro,bind 0 0

         lxc.mount=/var/lib/lxc/temp/fstab

 

下面从一个Lxc容器的创建lxc-start 的流程图,可以清楚的了解lxc实现与cgroup的关系:

lxc实现流程(与cgroup关系)框图

从代码进行分析,使用lxc 开源0.9.0版本,下载地址http://sourceforge.net/projects/lxc/,目前最新的版本为lxc-0.9.0.tar.gz.

从main函数开始Main->lxc_start->__lxc_start

int __lxc_start(const char *name, struct lxc_conf*conf,

                   structlxc_operations* ops, void *data, const char *lxcpath)

{

         structlxc_handler *handler;

         interr = -1;

         intstatus;

 

         handler= lxc_init(name, conf, lxcpath);//初始化容器的管理结构lxc_handler结构,返回该结构体,lxc_handler中除了包含容器的名字、属性,更包含char *cgroup成员用来关联对应一个cgroup

         if(!handler) {

                   ERROR("failedto initialize the container");

                   return-1;

         }

         handler->ops= ops;

         handler->data= data; //应用参数赋值,容器启动后根据该值启动容器进程,上文中的/bin/bash

 

         if (must_drop_cap_sys_boot()){

                   #ifHAVE_SYS_CAPABILITY_H

                   DEBUG("Droppingcap_sys_boot\n");

                   #else

                   DEBUG("Can'tdrop cap_sys_boot as capabilities aren't supported\n");

                   #endif

         }else {

                   DEBUG("Notdropping cap_sys_boot or watching utmp\n");

                   handler->conf->need_utmp_watch= 0;

         }

 

         err= lxc_spawn(handler);//核心函数,创建命名空间关联cgroup

         if(err) {

                   ERROR("failedto spawn '%s'", name);

                   gotoout_fini_nonet;

         }

 

         err= lxc_poll(name, handler);//父进程开始一个epoll循环,主要处理consolecontainer的两端转发epoll,以及接收一些外来查询请求的unix套接口epoll

         if(err) {

                   ERROR("mainloopexited with an error");

                   gotoout_abort;

         }

 

         while(waitpid(handler->pid, &status, 0) < 0 && errno == EINTR)

                   continue;

 

... ...

}

创建命名空间关联cgroup 的核心函数lxc_spawn:

int lxc_spawn(struct lxc_handler *handler)

{

         intfailed_before_rename = 0;

         constchar *name = handler->name;

 

         if(lxc_sync_init(handler))//调用socketpair():创造一对未命名的、相互连接的UNIX域套接字,,后续由lxc_sync_fini()关闭

                   return-1;

 

         handler->clone_flags= CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS;//容器必备命名空间属性

         if(!lxc_list_empty(&handler->conf->id_map)) {

                   INFO("Cloninga new user namespace");

                   handler->clone_flags|= CLONE_NEWUSER;

         }

         if(!lxc_list_empty(&handler->conf->network)) {//设置网络的命名空间

 

                   handler->clone_flags|= CLONE_NEWNET;

 

                   if(lxc_find_gateway_addresses(handler)) {

                            ERROR("failedto find gateway addresses");

                            lxc_sync_fini(handler);

                            return-1;

                   }

 

                    //容器的网络设备需要在clone之前填充,因为在clone进程中用到

                   if(lxc_create_network(handler)) {

                            ERROR("failedto create the network");

                            lxc_sync_fini(handler);

                            return-1;

                   }

         }

 

         if(save_phys_nics(handler->conf)) {

                   ERROR("failedto save physical nic info");

                   gotoout_abort;

         }

 

         /*

          * if the rootfs is not a blockdev, prevent thecontainer from

          * marking it readonly.

          */

 

         handler->pinfd= pin_rootfs(handler->conf->rootfs.path);//设定指定根文件系统路径

         if(handler->pinfd == -1) {

                   ERROR("failedto pin the container's rootfs");

                   gotoout_delete_net;

         }

 

         handler->pid= lxc_clone(do_start, handler, handler->clone_flags);//根据命名空间clone_flags,使用新namespace创建进程,因此容器的进程是彼此隔离的。

         if(handler->pid < 0) {

                   SYSERROR("failedto fork into a new namespace");

                   gotoout_delete_net;

         }

 

         lxc_sync_fini_child(handler);

 

         if(lxc_sync_wait_child(handler, LXC_SYNC_CONFIGURE))

                   failed_before_rename= 1;

 

         /*TODO - pass lxc.cgroup.dir (or user's pam cgroup) in for first argument */

         if((handler->cgroup = lxc_cgroup_path_create(NULL, name)) == NULL) //创建cgroup容器

                   gotoout_delete_net;

        

         if(lxc_cgroup_enter(handler->cgroup, handler->pid) < 0)//将任务pid添加至cgroup

                   gotoout_delete_net;

 

         if(failed_before_rename)

                   gotoout_delete_net;

 

         /*Create the network configuration */

         if(handler->clone_flags & CLONE_NEWNET) {

                   if(lxc_assign_network(&handler->conf->network, handler->pid)){//netlink_transaction

                            ERROR("failedto create the configured network");

                            gotoout_delete_net;

                   }

         }

         if(lxc_map_ids(&handler->conf->id_map, handler->pid)) {

                   ERROR("failedto set up id mapping");

                   gotoout_delete_net;

         }

 

         /*Tell the child to continue its initialization. we'll get

          * LXC_SYNC_CGROUP when it is ready for us tosetup cgroups

          */

          //当子进程准备好设置cgroups,告知子进程继续初始化

         if(lxc_sync_barrier_child(handler, LXC_SYNC_POST_CONFIGURE))

                   gotoout_delete_net;

//根据配置文件设置cgroup属性

         if(setup_cgroup(handler->cgroup, &handler->conf->cgroup)) {

                   ERROR("failedto setup the cgroups for '%s'", name);

                   gotoout_delete_net;

         }

 

... ...

}

lxc_cgroup_path_create根据传入的容器名,通过mkdir创建容器,然后lxc_cgroup_enter将所clone的任务pid添加至cgroup下的tasks中。

这里需要注意在lxc创建容器之前要已经配置并mount了cgroup。

子进程由clone创建后执行do_start:

static int do_start(void *data)

{

         structlxc_handler *handler = data;

 

... ...

 

         /*Setup the container, ip, names, utsname, ... */

         if(lxc_setup(handler->name, handler->conf)) {//子进程非cgroup属性设置,包含ip、名字空间、根文件系统路径、串口等

                   ERROR("failedto setup the container");

                   gotoout_warn_father;

         }

 

                           if(lxc_sync_barrier_parent(handler, LXC_SYNC_CGROUP)) //询问父进程同步设置cgroups,父进程通过上面说的lxc_sync_barrier_child响应子进程

                   return-1;

 

 ... ...

         /*after this call, we are in error because this

          * ops should not return as it execs */

         handler->ops->start(handler,handler->data);//子进程调用start,通过execve启动参数-d后带的应用程序。

 

out_warn_father:

         /*we want the parent to know something went wrong, so any

          * value other than what it expects is ok. */

         lxc_sync_wake_parent(handler,LXC_SYNC_POST_CONFIGURE);

         return-1;

}

至此一个容器创建完成,并和一个cgroup相关联。对cgroup的 Cpu、Cpuset、Cpuacct、Memory、Devices等子系统的控制与管理,就是对容器相应部分的控制与管理。

对于容器外创建的进程如果使用lxc命令并指定容器名,则该进程属于该容器。如果在容器内部创建的进程则继承父进程的命名空间以及cgroup属性,则属于父容器。

0 0
原创粉丝点击