0、引题
最近有需求,需要实现lxc的cpu、内存资源的动态伸缩。之前系统一直使用libvirt开发,于是乎发现这片博文《libvirt中CPU和内存的细粒度管理机制》(传送门:http://reedhong.blog.163.com/blog/static/180037190201162911559605/),其中讲使用libvirt细粒度调整cpu、内存资源。
libvirt是一款管理虚拟化的库,支持kvm、Xen、lxc等多种虚拟化技术,可以说这既是优点,也蕴含着缺点。优点是兼容性还算比较好,缺点是libvirt还在不断完善中(目前libvirt最新版本1.0.3,ubuntu apt-get 默认安装版本为0.9.8),有很多功能支持不全面,或者有bug。具体来说,libvirt的大量API对于kvm/qemu适用,但对于lxc可能就不能使用(直接报错,返回错误);或者虽然可以使用(可以正确返回),但是没有实际效果;或者可以达到类似的效果,但是要使用不同的API、设置不同的变量。
《libvirt中CPU和内存的细粒度管理机制》的作者也意识到了上述问题,因此他在博文中多次提到哪些方法对lxc可用,哪些方法对lxc不可用。但是很多问题混在一起表述,而且文章排版比较随意,我还是没有在第一时间高清楚How to do。不过,这篇文章介绍了主要问题和方法,给我大量启发,我所做的工作都是在其基础上整理。后面从工程实践的基础上,阐述如何通过libvirt动态调整lxc的cpu和内存资源。
一、环境介绍
OS:Ubuntu 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
libvirt:0.9.8、1.0.2
二、内存管理
1、关键变量
与内存有关的变量主要包括以下几个:
(1)hard_limit,对应cgroup中的memory.limit_in_bytes,官方解释为it represents the maximum memory the guest can use,即guest能使用的最大内存。
(2)soft_limit,对应cgroup中的memory.soft_limit_in_bytes,官方解释为it represents the memory upper limit enforced during memory contention.
(3)swap_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_limit、memory、curruntmemory没有这种“硬关系”。
2、xml配置
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、关键方法
与内存有关的方法包括以下几个:
(1)virDomainSetMemory,修改了memory,但是没有修改hard_limit,测试证明lxc内存使用率可以超过memory值;
(2)virDomainSetMaxMemory,情况与virDomainSetMemory类似;
(3)virDomainSetMemoryParameters,这个方法才有作用,可以改变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_name。lxc_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资源隔离有关的变量是以下两个:
(1)cpu_shares,对应cgroup中的cpu.shares,是cpu共享时间比例。按照比例计算,例如两个lxc共享一个cpu,cpu.shares分别为1024、2048,那它们使用cpu的理论比值就是1:2.
(2)vcpupin,对应cgroup中的cpuset.cpus,是分配给lxc具体使用的cpu。
2、xml配置
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(我本机只有4个cpu),即将所有cpu分配给该lxc。
3、关键方法
两个关键方法,基本思路还是get和set,两个函数原型如下:
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。
4、libvirt存在bug或问题
正如前文所说,libvirt还在不断完善中,对于lxc的支持还不成熟,尤其lxc的cpu设置存在,较多问题。在开发过程中,我遇到过如下问题和bug。
(1)使用libvirt0.9.8,使用 virDomainGetSchedulerParameters方法会提示错误,cgroup没有挂载。但是我可以看到cgroup相应的lxc已经挂载。后来google出,这是libvirt0.9.8的bug,在1.0.1的到了解决。
(2)使用libvirt1.0.2,在创建lxc时报错,提示selinux配置有误,之前版本libvirt不直接使用selinux,后续版本必须设置selinux。 安装selinux,修改配置文件/etc/selinux/config重启后解决问题。
(3)通过xml配置vcpupin vcpu发现没有作用,cgroup cpuset.cpus默认配置了所有的cpu。
//(4)virDomainSetSchedulerParameters报错,提示系统不支持 对cpu_shares的修改。不过还好,有一条virsh命令支持对cpu_shares的修改,即 virsh -c lxc:/// schedinfo lxc_name cpu_shares=1001。virDomainSetSchedulerParameters和 virsh -c lxc:/// schedinfo lxc_name cpu_shares=1001的作用相同,推测底层实现应当是一样的,为何一个可行一个不可行,目前尚不清楚。后记:本条是因为本人输入函数名错误导致,感谢@军临天下的发现和提醒。
备注:当然我个人水平有限,上述问题也可能是由于我使用配置不当造成的,如是这种情况请各位指正。
5、代码样例
代码中有一些注释,主要流程就是先get cpu parameters,修改之后然后再set cpu parameters,不过set cpu parameters时报上文提到的错误。编译后使用如下:./setmem lxc_name。lxc_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
转载于:https://blog.51cto.com/speakingbaicai/1168497