0、引题

   最近有需求,需要实现lxccpu、内存资源的动态伸缩。之前系统一直使用libvirt开发,于是乎发现这片博文《libvirtCPU和内存的细粒度管理机制》(传送门:http://reedhong.blog.163.com/blog/static/180037190201162911559605/),其中讲使用libvirt细粒度调整cpu、内存资源。

       libvirt是一款管理虚拟化的库,支持kvmXenlxc等多种虚拟化技术,可以说这既是优点,也蕴含着缺点。优点是兼容性还算比较好,缺点是libvirt还在不断完善中(目前libvirt最新版本1.0.3ubuntu apt-get 默认安装版本为0.9.8),有很多功能支持不全面,或者有bug。具体来说,libvirt的大量API对于kvm/qemu适用,但对于lxc可能就不能使用(直接报错,返回错误);或者虽然可以使用(可以正确返回),但是没有实际效果;或者可以达到类似的效果,但是要使用不同的API、设置不同的变量。

   《libvirtCPU和内存的细粒度管理机制》的作者也意识到了上述问题,因此他在博文中多次提到哪些方法对lxc可用,哪些方法对lxc不可用。但是很多问题混在一起表述,而且文章排版比较随意,我还是没有在第一时间高清楚How to do。不过,这篇文章介绍了主要问题和方法,给我大量启发,我所做的工作都是在其基础上整理。后面从工程实践的基础上,阐述如何通过libvirt动态调整lxccpu和内存资源。


一、环境介绍

OSUbuntu 12.04.1 LTS

内核:Linux 3.2.0-33-generic-pae #52-Ubuntu SMP Thu Oct 18 16:39:21 UTC 2012 i686 i686 i386 GNU/Linux

libvirt0.9.81.0.2


二、内存管理

1、关键变量

   与内存有关的变量主要包括以下几个:

   (1hard_limit对应cgroup中的memory.limit_in_bytes官方解释为it represents the maximum memory the guest can use,即guest能使用的最大内存。

   (2soft_limit,对应cgroup中的memory.soft_limit_in_bytes,官方解释为it represents the memory upper limit enforced during memory contention.

   (3swap_hard_limit,对应cgroup中的memory.memsw.limit_in_bytes,官方解释为it represents the maximum swap plus memory the guest can use. This limit has to be more than hard_limit。从解释中可以发现,它必须大于hard_limit.

通过反复测试,lxc的内存上限是hard_limit这个变量,lxc内的内存使用率永远不会超过hard_limit,而与soft_limitmemorycurruntmemory没有这种“硬关系”。

2xml配置

   libvirt通过xml文档配置lxc,关于内存部分,典型的配置如下所示:

<memory>286954</memory> //这个参数必须有,不然无法启动
<memtune>
    <hard_limit>1048576</hard_limit> //实际限制lxc内存上限的变量
    <soft_limit>131072</soft_limit>
    <swap_hard_limit>2097152</swap_hard_limit>
    <min_guarantee>65536</min_guarantee> //对lxc无效
</memtune>

   如果在xml配置文件中没有设置hard_limit,那么hard_limit将会和memory同样大。


3、关键方法

   与内存有关的方法包括以下几个:

   (1virDomainSetMemory,修改了memory,但是没有修改hard_limit,测试证明lxc内存使用率可以超过memory值;

   (2virDomainSetMaxMemory,情况与virDomainSetMemory类似;

   (3virDomainSetMemoryParameters,这个方法才有作用,可以改变hard_limit

   (4)与virDomainSetMemoryParameters相关的方法还有virDomainGetMemoryParameters,由于这两个方法参数较多,通常的编程方法是先Get,再Set

   两个方法的函数原型如下:

int virDomainGetMemoryParameters (virDomainPtr domain,
                                  virTypedParameterPtr params,
int * nparams,
                                  unsigned int flags)
int virDomainSetMemoryParameters (virDomainPtr domain,
                                  virTypedParameterPtr params,
int nparams,
                                  unsigned int flags)


4、代码样例

   代码涉及一些指针和分配内存操作,使用时要注意鲁棒性。下面是我写的一个函数,鲁棒性应该还可以。代码中有一些注释,主要流程就是先get memory parameters,修改之后然后再set memory parameters。编译后使用如下:./setmem lxc_namelxc_name是正在运行中的lxc名称。

/* setmem.cpp  */
/* compile with: g++ setmem.cpp -o setmem -lvirt */
/* autumn_sky_is@163.com    */
/* date: 2013-02-27         */
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include <string>
#include "libvirt/libvirt.h"
#include <libvirt/virterror.h>
using namespace std;
int main(int argc, char * argv[])
{
    if (2 != argc)
    {   
         cout<<"Error: need 1 parameter"<<endl;
         cout<<"usage: ./setmem lxc_name"<<endl;   
         return -1;
    }
                                                                                                                                             
    virConnectPtr conn = virConnectOpen("lxc:///");
    if (NULL == conn)
    {
        fprintf(stderr, "Failed to build connection to lxc:///.\n");   
        return -1;
    }
    virDomainPtr domain = virDomainLookupByName(conn, argv[1]);
    if (domain == NULL)
    {   
         cout << "can not find " <<argv[1]<< ", regard it as success."<<endl;
         return -1;
    }
    // get lxc Info
    virDomainInfo info;
    virDomainGetInfo(domain, &info);
    printf("state:%d|maxmem:%d|memused:%d|cpunum:%d|cputime:%ld\n", info.state,
info.maxMem, info.memory, info.nrVirtCpu, info.cpuTime);
                                                                                                                                             
    int nparams = 0;
    virTypedParameterPtr params;
    // get memory parameters
    if (-1 == virDomainGetMemoryParameters(domain, NULL, &nparams, 0))
    {   
         cout<<"Get Memory Parameters Error."<<endl;   
         return -1;
    }
    if (0 == nparams)
    {   
         cout<<"Get Memory Parameters is 0."<<endl;
         return -1;
    }
                                                                                                                                             
    if (( params = (virTypedParameter *)malloc(sizeof(* params)*nparams) ) == NULL)
    {
         cout<<"Malloc error"<<endl;
         return -1;
    }
    memset(params,0,sizeof(*params)*nparams);
    if (-1 == virDomainGetMemoryParameters(domain, params, &nparams, 0))
    {
         cout<<"Get Memory Parameters Error."<<endl;
         return -1;
    }
    // print memory parameters
    for (int i=0; i<nparams; i++)
    {   
         cout<<"field:"<<params[i].field<<endl;   
         cout<<"type:"<<params[i].type<<endl;   
         cout<<"value:"<<params[i].value.ul<<endl;
    }
                                                                                                                                             
    // set hard_limit
    params[0].value.ul=256000;
    if (-1 == virDomainSetMemoryParameters(domain, params, nparams, 0))
    {
        cout<<"Set Memory Parameters error"<<endl;
        return -1;
    }
    virDomainGetInfo(domain, &info);
    printf("state:%d|maxmem:%d|memused:%d|cpunum:%d|cputime:%ld
\n",info.state,info.maxMem,info.memory,info.nrVirtCpu,info.cpuTime);
    return 0;
}


三、CPU管理

1、关键变量

   与cpu资源隔离有关的变量是以下两个:

   (1cpu_shares,对应cgroup中的cpu.shares,是cpu共享时间比例。按照比例计算,例如两个lxc共享一个cpucpu.shares分别为10242048,那它们使用cpu的理论比值就是12.

   (2vcpupin,对应cgroup中的cpuset.cpus,是分配给lxc具体使用的cpu

2xml配置

   libvirt通过xml文档配置lxc,关于cpu部分,典型的配置如下所示:

<cputune>
   <vcpupin vcpu="0" cpuset="1-4,^2"/>
   <vcpupin vcpu="1" cpuset="0,1"/>
   <vcpupin vcpu="2" cpuset="2,3"/>
   <vcpupin vcpu="3" cpuset="0,4"/>
   <shares>2048</shares>
</cputune>

   两点注意的地方,一是倒数第二行应当时“shares”,而不是有的文档中的“cpushares”;二是vcpupin vcpu的设置没有实际效果,无论libvirt怎样配置 ,我查看底层cgroup cpuset.cpus的配置参数都是0-3(我本机只有4cpu),即将所有cpu分配给该lxc

3、关键方法

   两个关键方法,基本思路还是getset,两个函数原型如下:

int     virDomainGetSchedulerParameters (virDomainPtr domain,
                                         virTypedParameterPtr params,
                                         int * nparams)
int     virDomainSetSchedulerParameters (virDomainPtr domain,
                                         virTypedParameterPtr params,
                                         int nparams)

   第二个函数使用会报错,libvir:  error : invalid argument: parameter 'cpu_shares' not supported

4libvirt存在bug或问题

   正如前文所说,libvirt还在不断完善中,对于lxc的支持还不成熟,尤其lxccpu设置存在,较多问题。在开发过程中,我遇到过如下问题和bug

   (1)使用libvirt0.9.8,使用 virDomainGetSchedulerParameters方法会提示错误,cgroup没有挂载。但是我可以看到cgroup相应的lxc已经挂载。后来google出,这是libvirt0.9.8bug,在1.0.1的到了解决。

   (2)使用libvirt1.0.2,在创建lxc时报错,提示selinux配置有误,之前版本libvirt不直接使用selinux,后续版本必须设置selinux。 安装selinux,修改配置文件/etc/selinux/config重启后解决问题。

   (3)通过xml配置vcpupin vcpu发现没有作用,cgroup cpuset.cpus默认配置了所有的cpu

    //(4virDomainSetSchedulerParameters报错,提示系统不支持 对cpu_shares的修改。不过还好,有一条virsh命令支持对cpu_shares的修改,即 virsh -c lxc:/// schedinfo lxc_name cpu_shares=1001virDomainSetSchedulerParametersvirsh -c lxc:/// schedinfo lxc_name cpu_shares=1001的作用相同,推测底层实现应当是一样的,为何一个可行一个不可行,目前尚不清楚。后记:本条是因为本人输入函数名错误导致,感谢@军临天下的发现和提醒。

备注:当然我个人水平有限,上述问题也可能是由于我使用配置不当造成的,如是这种情况请各位指正。

5、代码样例

   代码中有一些注释,主要流程就是先get cpu parameters,修改之后然后再set cpu parameters,不过set cpu parameters时报上文提到的错误。编译后使用如下:./setmem lxc_namelxc_name是正在运行中的lxc名称。

/* setcpu.cpp  */
/* compile with: g++ setcpu.cpp -o setcpu -lvirt */
/* autumn_sky_is@163.com    */
/* date: 2013-02-27         */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include "libvirt/libvirt.h"
#include <libvirt/virterror.h>
using namespace std;
int main(int argc, char * argv[])
{
    if (2 != argc){
    cout<<"Error: need 1 parameter"<<endl;
    cout<<"usage: ./setcpu lxc_name"<<endl;
    return -1;
    }
    virConnectPtr conn = virConnectOpen("lxc:///");
    if (NULL == conn){
        fprintf(stderr, "Failed to build connection to lxc:///.\n");
        return -1;
    }
                                                                                                  
    virDomainPtr domain = virDomainLookupByName(conn, argv[1]);
    if (domain == NULL){
        cout << "can not find " <<argv[1]<< ", regard it as success."<<endl;
        return -1;
    }
                                                                                                      
    // get lxc Info
    virDomainInfo info;
    virDomainGetInfo(domain, &info);
    printf("state:%d|maxmem:%d|memused:%d|cpunum:%d|cputime:%ld\n", info.state, info.maxMem, info.memory, info.nrVirtCpu, info.cpuTime);
                                                                                                  
    // cpu info
    cout<<"cpuinfo:"<<endl;
                                                                                                  
    /*char * type;
    type = virDomainGetSchedulerType(domain,NULL);
    cout<<type<<endl;*/
    //get cpu parameters
    int nparams =10;
    virTypedParameterPtr params;
                                                                                                  
    if (0 == nparams) {
        cout<<"Get CPU Parameters is 0."<<endl;
        return -1;
    }
    if ((params = (virTypedParameter *)malloc(sizeof(* params)*nparams)) == NULL) {
        cout<<"Malloc error"<<endl;
        return -1;
    }
    memset(params,0,sizeof(*params)*nparams);
                                                                                                     
    if (-1 == virDomainGetSchedulerParameters(domain, params, &nparams))
    {
        cout<<"Get CPU Parameters Error."<<endl;
        return -1;
    }
                                                                                                     
    // print cpu parameters
    for(int i=0; i<nparams; i++){
        cout<<"field:"<<params[i].field<<endl;
        cout<<"type:"<<params[i].type<<endl;
        cout<<"value:"<<params[i].value.ul<<endl;
    }
                                                                                                     
    //set cpu parameters
    params[0].value.ul=1001;
    cout<<params[0].value.ul<<endl;
    if (-1 == virDomainSetSchedulerParameters(domain, params, nparams)){
        cout<<"Set CPU Parameters error"<<endl;
        return -1;
    }
    return 0;
}

6、等价virsh命令

   除了libvirt api之外,我们还可以使用virsh命令实现等价操作。

   virDomainGetSchedulerParameters 相当于virsh -c lxc:/// schedinfo lxc_name virDomainSetSchedulerParameters(假设只将cpu_shares修改为1001)相当于virsh -c lxc:/// schedinfo lxc_name cpu_shares=1001

shell操作与输出如下所示:

# virsh -c lxc:/// schedinfo lxc1
调度程序   : posix
cpu_shares     : 1000
vcpu_period    : 100000
vcpu_quota     : -1
# virsh -c lxc:/// schedinfo lxc1 cpu_shares=1001
调度程序   : posix
cpu_shares     : 1001
vcpu_period    : 100000
vcpu_quota     : -1