目标:
快速建立环境用语测试 deb 包安装 / web 应用部署。
可参考 https://github.com/wolf0403/lvmvm 的 README
基础环境:
Ubuntu Natty (11.04) server.
文件系统:
测试环境的快速部署依赖 LVM (Logical Volume Management) 。安装宿主系统方法不限,留足够硬盘创建单一分区,如 /dev/sda5 。
将此分区创建为 LVM 物理卷
pvcreate /dev/sda5
在此分区之上创建 LVM 组
vgcreate data /dev/sda5
创建逻辑分区
lvcreate -n natty /dev/data -L2G
创建 文件系统
mkfs.ext4 /dev/data/natty
创建挂载点并挂载
mkdir /mnt/natty mount /dev/data/natty /mnt/natty
创建测试环境
debootstrap natty /mnt/natty
完成之后的 /mnt/natty 为一个最基础的 Ubuntu Natty 环境(debootstrap 中可以指定其他版本,如 oneiric 等。将 /mnt/natty 卸载作为范本。umount /mnt/natty
可以压缩分区以节约磁盘空间:将文件系统减到最小
resize2fs -M /dev/data/natty # 会要求先执行 e2fsck -f /dev/data/natty 等
然后根据文件系统大小相应压缩逻辑卷。$NEW_SIZE 为上一条操作执行后文件系统的大小;应该留有适当余地,否则可能损坏文件系统。或使用 -r 参数。
lvresize -L $NEW_SIZE /dev/data/natty
创建新测试环境
使用 LVM 的 snapshot 功能创建测试环境:
NEWSIZE=`lvdisplay /dev/data/natty | grep 'Current LE' | egrep -o '[[:digit:]]+'` VOL=snap1 lvcreate -s /dev/data/natty -n $VOL -l$NEWSIZE mkdir /mnt/$VOL mount /dev/data/$VOL /mnt/$VOL
export MP=/mnt/$VOL touch $MP/chroot.$VOL # 盗梦的陀螺 mount --bind /dev $MP/dev
mount none $MP/dev/pts -t devpts
mount --bind /proc $MP/proc
mount --bind /sys $MP/sys chroot /mnt/$VOL
网络
此时创建的测试环境虽然文件系统独立,但其他均为共享。如果同时创建多个环境运行网络服务(如 web server 或 fastcgi 等)则可能出现端口冲突。解决方法是通过 veth 给每个环境分配独立的网络。
参考:http://lxc.sourceforge.net/index.php/about/kernel-namespaces/network/configuration/
攻略:
宿主环境打开路由和 arp 代理
创建虚拟网卡对echo 1 > /proc/sys/net/ipv4/ip_forward echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp
为网卡对的主机端指定地址。不应与 ethX 冲突ip link add type veth
为虚拟网卡打开 arp 转发ifconfig veth0 192.168.3.101/24 up
为虚拟网卡对端指定路由(这里 192.168.3.102 是虚拟环境使用的 IP 地址)echo 1 > /proc/sys/net/ipv4/conf/veth0/proxy_arp
route add -host 192.168.3.102 dev veth0
另开一个会话,chroot 进入虚拟环境。用 unshare 命令隔离与宿主的网络空间unshare -mun chroot /mnt/$VOL
这时执行 ifconfig 应该看不见网卡,ifconfig lo0 可以看见 loopback 网卡,但是没有 IP 地址。在这个 shell 执行
echo $$
得到 PID
在第一个会话中(宿主环境)执行
将 veth1 划入虚拟环境ip link set veth1 netns $PID
在虚拟环境 chroot shell 中 ifconfig 应该可以看到 veth1 。将 veth1 的地址指定为前述(路由指向的)IP 地址
ifconfig veth1 192.168.3.102
在虚拟环境中测试监听
nc -vv -l 10888
在主机中测试nc -vv localhost 10888
失败,但
nc -vv 192.168.2.102 10888
成功,说明网络隔离室成功的。
CPU、内存资源
测试利用 cgroups 限定可用内存和 CPU
通过 cgcreate -g memory:Name 创建分组,然后在 /sys/fs/cgroup/memory/Name/memory.limit_in_bytes 中进行设置,如 echo 10M > memory.limit_in_bytes 等。
FIXME: 通过 for (;;) malloc (1024 * 1024) 并未测得相应的结果,内存增长(top 中 VIRT / RES / SHR)与实际占用( /sys/fs/cgroup/Name/memory.usage_in_bytes)并不对应。
问题
Ubuntu 虚拟环境中 MySQL 等脚本无法执行
Ubuntu 8.04 之后的系统启动脚本由 Sysvinit 替换成了 Upstart。Sysvinit 是一组简单的 shell 脚本,由 /sbin/init 创建进程并独立执行,因此相对简单。Ubuntu 使用的 upstart 系统( http://upstart.ubuntu.com )依赖 dbus 通信,因此脚本的执行依赖于 /sbin/init 的执行。而 Ubuntu 的 /sbni/init 又强制要求自己的 PID 为 1,否则直接退出。因为以上的 unshare 命令目前只能处理网络、System V IPC 等名字空间,但是空间仍然是共用的,无法在虚拟环境中正常执行 /sbin/init。
解决方法 1:对于依赖 upstart 的服务,手工启动(如手工执行 mysqld 等)解决方法 2:利用 LD_PRELOAD 劫持 getpid 返回 1 强制启动虚拟环境的 /sbin/init (http://blog.youkuaiyun.com/Wolf0403/article/details/389276)#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> pid_t getpid (void) { char *pidstr = getenv ("HJPID"); if (pidstr && pidstr[0] == '1' && pidstr[1] == '\0') { return (pid_t) 1; } void *dlh = dlopen ("libc.so.6", RTLD_LAZY); if ( !dlh ) { exit (1); } pid_t (* glibc_getpid) (); glibc_getpid = dlsym ( dlh, "getpid" ); if ( ! glibc_getpid ) { exit (2); } pid_t r = glibc_getpid (); dlclose ( dlh ); return r; }
编译cc -shared -fPIC getpid.c -o libpid.so -ldl
然后用 env LD_PRELOAD=libpid.so HJPID=1 方式插入 /sbin/init 这样可以启动 /sbin/init,但是 MySQL 启动仍然有问题。FIXME
整理
目前测试成功的是在虚拟环境中执行 nginx / php-fcgi,MySQL 可以在 chroot 中执行关于 PID namespace 的问题,Linux 的 clone(2) 系统调用提供了新的 CLONE_NEWPID 参数用于隔离 PID 名称空间。http://linux.die.net/man/2/clone 可以尝试整合到 unshare 中(TODO)。资源
Upstart /sbin/init PID = 1: http://linux-vserver.org/Upstart_issuesLinux unshare(1) 命令:http://linux.die.net/man/1/unshare