在 Linux 的 KVM 上安装 Mac OS X Mavericks 虚拟机

现在我们很方便就能在 Mac 上通过 VirtualBox 安装 Mac OS X 虚拟机,如果没 Mac 的话也能很容易在 Linux/Windows 下通过 VirtualBox 体验 Mac,甚至把 Mac OS X 直接装在 PC 上也是可能的。如果想在数据中心这种 KVM/Xen 虚拟环境里安装 Mac OS X 呢?通过 Apple Remote Desktop 连接 n 个 Mac 虚拟机,应该会比较好玩吧。

以下操作在 CentOS 6.5 上测试通过。更详细的资料请参考 Running Mac OS X as a QEMU/KVM Guest.

首先需要 Linux 3.x 以上的内核,CentOS 6.5 上默认是 2.6.x 内核,所以需要另装或升级内核,我们这里用 CentOS 官方 Xen 源(Xen4CentOS6)里面的最新 Linux 内核,注意这里用的是源里的普通内核,不是要用 Xen 内核,你也可以用其他第三方源的内核或者自己下载内核编译。注意修改 grub.conf 文件的 default=0 部分确认系统启动后启动的是普通内核(不是启动 Xen 内核):

# yum install centos-release-xen
# yum update

# vi /etc/grub.conf
...
default=0
...

# reboot

当前的主流 Linux 发行版自带的 kvm/qemu 都不支持 OS X 作为 guest 系统,所以我们需要自己编译 kvm, kvm-kmod, qemu 加上 OS X 的支持。下载 kvm 和 kvm-kmod 源代码后编译,编译会出错,解决办法见后面:

# yum install git
# yum groupinstall 'Development Tools'

# git clone git://git.kernel.org/pub/scm/virt/kvm/kvm.git
# git clone git://git.kiszka.org/kvm-kmod.git
# cd kvm-kmod
# ./configure
# make LINUX=../kvm clean sync all
...
/root/kvm-kmod/x86/assigned-dev.c: In function ‘assigned_device_enable_host_msix’:
/root/kvm-kmod/x86/assigned-dev.c:434:2: error: implicit declaration of function ‘pci_enable_msix_exact’ [-Werror=implicit-function-declaration]
  r = pci_enable_msix_exact(dev->dev,
  ^
cc1: some warnings being treated as errors
make[3]: *** [/root/kvm-kmod/x86/assigned-dev.o] Error 1
make[2]: *** [/root/kvm-kmod/x86] Error 2
make[1]: *** [_module_/root/kvm-kmod] Error 2
make[1]: Leaving directory `/usr/src/kernels/3.10.34-11.el6.centos.alt.x86_64'
make: *** [all] Error 2

修改 assigned-dev.c 这个文件的第434行,用 pci_enable_msix 函数替代 pci_enable_msix_exact,查看 git 修改记录和日志,貌似 pci_enable_msix_exact 是 Linus 上个月刚加上去的,我们这里还原,还是用原先的 pci_enable_msix 就可以了。

# vi /root/kvm-kmod/x86/assigned-dev.c
...
        /*r = pci_enable_msix_exact(dev->dev,
                                  dev->host_msix_entries, dev->entries_nr);*/
        r = pci_enable_msix(dev->dev, dev->host_msix_entries, dev->entries_nr);
...

修改代码后重新编译,注意这时候 make clean all 不要 sync,否则上面的修改会被重新覆盖:

# make LINUX=../kvm clean all

编译成功后,就可以加载 kvm-intel 内核了,使用 dmesg 确认内核加载成功:

# modprobe -r kvm_intel
# cp ./x86/kvm*.ko /lib/modules/$(uname -r)/kernel/arch/x86/kvm/
# modprobe kvm_intel

# dmesg | tail -n1
loaded kvm module (for-linus-40872-ga4e91d0)

kvm 部分告一段落,现在来编译 qemu:

# yum install zlib zlib-devel glib2-devel pixman-devel

# cd
# mkdir osx

# git clone git://git.qemu.org/qemu.git
# cd qemu
# ./configure --prefix=/root/osx --target-list=x86_64-softmmu
# make clean; make; make install

我们要安装 OS X 当然需要 OS X 的启动盘。到 App Store 里下载 Install OS X Mavericks.app 安装文件,然后在 Mac 上用下面的脚本生成一个可启动的 Mavericks ISO 文件,我们将要用这个 Mavericks.iso 安装系统,所以我们还需要把这个 Mavericks.iso 拷贝到 KVM 服务器上 :

$ vi createiso.sh
# Mount the installer image
hdiutil attach /Applications/Install\ OS\ X\ Mavericks.app/Contents/SharedSupport/InstallESD.dmg -noverify -nobrowse -mountpoint /Volumes/install_app

# Convert the boot image to a sparse bundle
hdiutil convert /Volumes/install_app/BaseSystem.dmg -format UDSP -o /tmp/Mavericks

# Increase the sparse bundle capacity to accommodate the packages
hdiutil resize -size 8g /tmp/Mavericks.sparseimage

# Mount the sparse bundle for package addition
hdiutil attach /tmp/Mavericks.sparseimage -noverify -nobrowse -mountpoint /Volumes/install_build

# Remove Package link and replace with actual files
rm /Volumes/install_build/System/Installation/Packages
cp -rp /Volumes/install_app/Packages /Volumes/install_build/System/Installation/

# Unmount the installer image
hdiutil detach /Volumes/install_app

# Unmount the sparse bundle
hdiutil detach /Volumes/install_build

# Resize the partition in the sparse bundle to remove any free space
hdiutil resize -size `hdiutil resize -limits /tmp/Mavericks.sparseimage | tail -n 1 | awk '{ print $1 }'`b /tmp/Mavericks.sparseimage

# Convert the sparse bundle to ISO/CD master
hdiutil convert /tmp/Mavericks.sparseimage -format UDTO -o /tmp/Mavericks

# Remove the sparse bundle
rm /tmp/Mavericks.sparseimage

# Rename the ISO and move it to the desktop
mv /tmp/Mavericks.cdr ~/Desktop/Mavericks.iso

$ sudo sh createiso.sh

在 Mac 上编译 smc_read.c 这个文件并运行得到 SMC,这个 SMC 序号我们会在后面用到:

$ vi smc_read.c
/*
 * smc_read.c: Written for Mac OS X 10.5. Compile as follows:
 *
 * gcc -Wall -o smc_read smc_read.c -framework IOKit
 */

#include 
#include 

typedef struct {
    uint32_t key;
    uint8_t  __d0[22];
    uint32_t datasize;
    uint8_t  __d1[10];
    uint8_t  cmd;
    uint32_t __d2;
    uint8_t  data[32];
} AppleSMCBuffer_t;

int
main(void)
{
    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
                               IOServiceMatching("AppleSMC"));
    if (!service)
        return -1;

    io_connect_t port = (io_connect_t)0;
    kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &port);
    IOObjectRelease(service);
    if (kr != kIOReturnSuccess)
        return kr;

    AppleSMCBuffer_t inputStruct = { 'OSK0', {0}, 32, {0}, 5, }, outputStruct;
    size_t outputStructCnt = sizeof(outputStruct);

    kr = IOConnectCallStructMethod((mach_port_t)port, (uint32_t)2,
             (const void*)&inputStruct, sizeof(inputStruct),
             (void*)&outputStruct, &outputStructCnt);
    if (kr != kIOReturnSuccess)
        return kr;

    int i = 0;
    for (i = 0; i < 32; i++)
        printf("%c", outputStruct.data[i]);

    inputStruct.key = 'OSK1';
    kr = IOConnectCallStructMethod((mach_port_t)port, (uint32_t)2,
             (const void*)&inputStruct, sizeof(inputStruct),
             (void*)&outputStruct, &outputStructCnt);
    if (kr == kIOReturnSuccess)
        for (i = 0; i < 32; i++)
            printf("%c", outputStruct.data[i]);

    printf("\n");

    return IOServiceClose(port);
}

$ gcc -Wall -o smc_read smc_read.c -framework IOKit
$ ./smc_read
REPLACE-YOUR-KEY-HERE(c)AppleComputerInc

快搞定了,最后我们还需要一个硬盘文件来充当虚拟机的硬盘以及 chameleon_svn2360_boot 这个文件,Chameleon 是 Darwin/XNU 系统的启动器(boot loader),用来在非苹果硬件上启动 OS X:

# /root/osx/bin/qemu-img create -f qcow2 osx.img 20G

# wget http://www.contrib.andrew.cmu.edu/~somlo/OSXKVM/chameleon_svn2360_boot

在 KVM 上启动安装这个我们准备已久的 OS X Mavericks 虚拟机吧,注意下面的 osk="REPLACE-YOUR-KEY-HERE(c)AppleComputerInc" 部分用上面的 SMC 序号替代:

# /root/osx/bin/qemu-system-x86_64 -enable-kvm -m 2048 -cpu core2duo \
-smp 2 \
-vga std \
-device ahci,id=ide \
-usbdevice keyboard -usbdevice mouse \
-device isa-applesmc,osk="REPLACE-YOUR-KEY-HERE(c)AppleComputerInc" \
-kernel /root/chameleon_svn2360_boot \
-smbios type=2 \
-device ide-drive,bus=ide.2,drive=MacHDD \
-drive id=MacHDD,if=none,cache=none,file=/root/osx.img \
-vnc 0.0.0.0:1 \
-device ide-drive,bus=ide.0,drive=MacDVD \
-drive id=MacDVD,if=none,snapshot=on,file=/root/Mavericks.iso -boot once=d

打开 VNC 客户端连接上面 KVM 服务器 IP 和端口 5901 就会看到安装界面了,先按回车开始安装,然后用菜单里的 Disk Utility 工具分区,退出 Disk Utility 就可以看到安装盘了,点击安装盘后就可以看到 Install OS X 安装界面了:
mac os x running on linux kvm

安装完后会重启进入系统:

mac os x running on linux kvm

迁移 KVM/VMware 虚拟机或物理机到 Xen PV 虚拟机

使用 KVM, VMware, XenServer/Xen HVM 这些全虚拟技术的虚拟机之间相互转化比较容易,有的转化一下虚拟机镜像文件的格式就可以了,有的可以借助一些免费的自动转化工具如 VMware vCenter Converter 等。今天要说的是全虚拟机(HVM)或物理机到半虚拟机(PV)的转化,稍微麻烦点。

HVM 到 PV 的转化思路是这样的:

1、把整个系统打包后拷贝到 Xen 服务器;
2、在 Xen 服务器上新建一个磁盘文件,把原系统解压到这个 “磁盘” 里;
3、挂载磁盘,并利用 chroot 更换原系统的内核为支持 Xen 的内核;
4、更改原系统的几个必要文件。

下面的操作是迁移一台 Ubuntu 12.04 物理服务器到 Xen PV 虚拟机,其方法也适用于其他的版本的 Linux.

首先登录到要转化的系统上,用 tar 把整个根文件系统打包,最好关闭系统后挂载硬盘到另一台机器上离线打包,如果一定要给一个正在运行的 Linux 系统在线打包的话务必停止一切服务后(如 Apach, MySQL 等)再执行打包,尽量减少打包过程中造成的数据不一致。给一个在线系统打包需要除去一些系统运行时目录如 /proc, /sys 等:

# tar -cvpzf test.tar.gz --exclude=/test.tar.gz --exclude=/sys --exclude=/dev --exclude=/proc /

把打包好的压缩包传到 Xen 母机上,然后在母机上创建一个磁盘镜像文件、格式化、挂载这个磁盘镜像到 /mnt,最后把压缩包的内容解压(细节可以参考 如何快速创建 Xen 虚拟机镜像):

# dd if=/dev/zero of=test.img bs=1 count=1 seek=10G
# mkfs.ext3 test.img
# mount -o loop test.img /mnt

# tar -zxSf test.tar.gz -C /mnt/

创建一些系统运行时需要的目录,然后 chroot:

# mkdir /mnt/proc
# mkdir /mnt/sys
# mkdir /mnt/dev
# mkdir /mnt/dev/pts
# mount -t proc proc /mnt/proc/
# mount -t sysfs sys /mnt/sys/
# mount -o bind /dev /mnt/dev/
# mount -o bind /dev/pts /mnt/dev/pts/

# chroot /mnt

特别的 Xen 需要特别的内核,所以我们需要给原 Linux 系统换内核,chroot 后更新系统并安装 linux-virtual 内核,当然,不要忘了更新 grub:

# apt-get update & apt-get upgrade

# apt-get install linux-virtual
# apt-get purge grub2 grub-pc
# apt-get install grub
# update-grub

换了内核后还需要更新和配置几个系统文件,menu.lst, fstab, hvc0.conf,以便新内核能在 Xen 虚拟环境里正常启动:

# vi /boot/grub/menu.lst
...
# kopt=root=UUID=4da51cdc-c6e9-42a2-b3c8-6056f334a124 ro
kopt=root=/dev/xvda console=hvc0 ro quiet
...

# update-grub
# vi /mnt/etc/fstab
proc         /proc    proc    defaults                     0    0
/dev/xvda    /        ext3    noatime,errors=remount-ro    0    1
/dev/xvdb    none     swap    sw                           0    0
# vi /etc/init/hvc0.conf
# hvc0 - getty
#
# This service maintains a getty on hvc0 from the point the system is
# started until it is shut down again.

start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]

respawn
exec /sbin/getty -8 38400 hvc0

基本上搞定了,退出 chroot 后记得按逆顺序卸载刚才挂载的目录:

# umount /mnt/proc/
# umount /mnt/sys/
# umount /mnt/dev/pts/
# umount /mnt/dev/
# umount /mnt/

创建一个 Xen 虚拟机配置文件,用 xm create 启动虚拟机:

# vi /etc/xen/test
bootloader = "/usr/bin/pygrub"
root = "/dev/xvda ro"
name = "test"
vcpus = "1"
memory = "2048"
disk = [ "file:/root/test.img,xvda,w","file:/root/test.swp,xvdb,w" ]
vif = [ "bridge=xenbr0" ]

# xm create test

在 Ubuntu 12.04 上安装 Open vSwitch

云计算时代人类已经成功虚拟化了服务器硬件,现在大大小小的数据中心有无数的虚拟机跑在服务器硬件上,看上去很美好,但是任务还没有完成,这么多的虚拟机都依赖物理服务器(所在)的网络端口、网络和交换机(除了物理依赖还依赖这些硬件上的软件配置),这让底层变动成为一个大问题,比如我们想改变服务器 A 上的网络设置(192.168.2.0 改成 172.16.2.0)或者物理移动服务器 A 到另一数据中心,那么服务器 A 上运行的虚拟机怎么办呢?逐个改动配置是很麻烦的。有没有一种办法能把虚拟机所在的网络和物理服务器的网络隔开呢(虽然可以用 VLAN,但是还是不够)?这正是网络虚拟化正在做的,通过软件的方式形成交换机部件(vSwitch),让各个虚拟机和虚拟交换机连接起来而不用去理会真实物理机和物理交换机。比如,让 Host A 上的 VM02 和 Host B 上的虚拟机 VM10 连成一个网络而不用理会虚拟机(VM)所在服务器(Host)的网络设置。

网络虚拟化或者软件定义网络(Software Defined Network, SDN)的应用远不止这些,任何了解 SDN 的人都不会怀疑 SDN 的未来,这也是为啥 VMware 愿意花12亿美金买 Nicira(软件定义网络的先驱)。

要使用 SDN/OpenFlow 就必须有支持 OpenFlow 协议的物理(或软件)交换机,OpenvSwitch 就是这么一个支持 OpenFlow 协议的开源虚拟交换机。我们从安装虚拟交换机 Open vSwitch 开始来慢慢了解网络虚拟化吧。

安装必要软件包:

$ sudo -i
# apt-get install kvm libvirt-bin openvswitch-controller openvswitch-brcompat openvswitch-switch openvswitch-datapath-source

启动 Open vSwitch 服务,如果报错 FATAL: Error inserting brcompat_mod 模块错误则需要编译和加载 brcompat_mod 这个模块,这个模块是 openvswitch 为了兼容 linux bridge 而来的,有些程序使用 Linux bridge(比如 brctl),这些程序为了能在 openvswitch 下继续使用将需要这个 brcompat_mod 兼容模块,这个模块为那些慢慢迁移到 openvswitch 的程序提供了方便:

# service openvswitch-switch start
FATAL: Error inserting brcompat_mod (/lib/modules/3.2.0-30-generic/updates/dkms/brcompat_mod.ko): Invalid module format
 * Inserting brcompat module
Module has probably not been built for this kernel.
For instructions, read
/usr/share/doc/openvswitch-datapath-source/README.Debian
FATAL: Error inserting brcompat_mod (/lib/modules/3.2.0-30-generic/updates/dkms/brcompat_mod.ko): Invalid module format
 * Inserting brcompat module

# module-assistant auto-install openvswitch-datapath

编译模块后再次尝试启动服务:

# service openvswitch-switch restart
 * Killing ovs-brcompatd (1606)
 * Killing ovs-vswitchd (1603)
 * Killing ovsdb-server (1594)
 * Starting ovsdb-server
 * Configuring Open vSwitch system IDs
 * Starting ovs-vswitchd
 * Starting ovs-brcompatd
 * iptables already has a rule for gre, not explicitly enabling

 # service openvswitch-controller restart
 * Restarting ovs-controller ovs-controller                                              Sep 12 13:46:46|00001|stream_ssl|INFO|Trusting CA cert from /etc/openvswitch-controller/cacert.pem (/C=US/ST=CA/O=Open vSwitch/OU=switchca/CN=OVS switchca CA Certificate (2012 Sep 12 13:42:19)) (fingerprint 46:5b:14:1f:13:56:b0:b0:a7:4d:10:39:ee:68:18:d4:39:3c:4b:d0)
                                                                                  [ OK ]

编辑配置文件,取消注释并设置 BRCOMPAT 为 yes 以便启动后能使用 brcompat_mod 模块:

# vi /etc/default/openvswitch-switch
# This is a POSIX shell fragment                -*- sh -*-

# FORCE_COREFILES: If 'yes' then core files will be enabled.
# FORCE_COREFILES=yes

# BRCOMPAT: If 'yes' and the openvswitch-brcompat package is installed, then
# Linux bridge compatibility will be enabled.
BRCOMPAT=yes

重启一下系统,验证一下是否模块被自动加载:

# reboot

# lsmod | grep brcom
brcompat_mod           13512  0 
openvswitch_mod        83993  1 brcompat_mod

使用 openvswitch 增加网桥 br0 并把网络接口 eth0 加入 br0:

# ovs-vsctl add-br br0
# ovs-vsctl add-port br0 eth0

# ovs-vsctl show          
a23c503b-bfbe-4646-8738-a7f69e5d69a0
    Bridge "br0"
        Port "eth0"
            Interface "eth0"
        Port "br0"
            Interface "br0"
                type: internal
    ovs_version: "1.4.0+build0"

配置网桥 br0 和网络接口 eth0(和用 bridge-utils 工具配置的时候一样,不同的是这里用更先进的 openvswitch/brcompat_mod 替换了 bridge-utils):

# ifconfig eth0 0
# ifconfig br0 192.168.2.45 netmask 255.255.255.0
# route add default gw 192.168.1.1 br0

为了保存网络设置,最好把上面的配置写到 /etc/network/interfaces 里:

# vi /etc/network/interfaces
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
address 192.168.2.45
netmask 255.255.255.0
gateway 192.168.2.1

auto eth0
iface eth0 inet manual
up ifconfig $IFACE 0.0.0.0 up
down ifconfig $IFACE down

Xen 和 KVM 下如何关闭 virbr0

安装 Xen安装 KVM 后都会发现网络接口里多了一个叫做 virbr0 的虚拟网络接口:

# ifconfig
...
virbr0    Link encap:Ethernet  HWaddr d2:91:97:b8:3d:fc  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
...

这是由于安装和启用了 libvirt 服务后生成的,libvirt 在服务器(host)上生成一个 virtual network switch (virbr0),host 上所有的虚拟机(guests)通过这个 virbr0 连起来。默认情况下 virbr0 使用的是 NAT 模式(采用 IP Masquerade),所以这种情况下 guest 通过 host 才能访问外部。

virtual network switch (virbr0)

大多数时候我们虚拟机使用的是 bridge(网桥)直接连到局域网里,所以这个 virbr0 不是必须的(注:不要搞混淆了,bridge 和这里的 virbr0 bridge 是互不相干的)。如何关掉这个 virbr0 呢?先 net-destroy 然后 net-undefine,最后别忘了重启 libvirtd 让设置生效:

# virsh net-list
Name                 State      Autostart
-----------------------------------------
default              active     yes

# virsh net-destroy default
Network default destroyed

# virsh net-undefine default
Network default has been undefined

# service libvirtd restart
Stopping libvirtd daemon:                                  [  OK  ]
Starting libvirtd daemon:                                  [  OK  ]

如何在 KVM 虚拟机上运行 KVM

上次讨论了如何在 VMware ESXi 虚拟机上运行 KVM 问题,前不久有读者想 “在 kvm 上面创建个虚拟机安装 rackspace 的 openstack” 问到 “如何开启虚拟机上的 CPU VT 功能以便在 KVM 上运行 KVM”,又是一个嵌套应用虚拟机的问题:在 KVM 虚拟机上运行 KVM 虚拟机。以下步骤在 Ubuntu Server 12.04 LTS 64 bit 上测试通过。

首先检查 KVM host(母机)上是否打开了嵌套虚拟机功能(默认是开启的):

# modinfo kvm_intel | grep nested
parm:           nested:bool

# cat /sys/module/kvm_intel/parameters/nested
Y

如果上面的显示结果不是 Y 的话需要开启 nested:

# modprobe -r kvm-intel
# modprobe kvm-intel nested=1
# cat /sys/module/kvm_intel/parameters/nested
Y

然后在 KVM guest(虚拟机)的 xml 配置文件中加入 vmx 选项,并启动虚拟机(这里用的是 Ubuntu 官方发布的 Ubuntu KVM 镜像 ubuntu-12.04-server-cloudimg-amd64-disk1.img):

# vi nestedvm.xml
...
<cpu match='exact'/>
   <model>core2duo</model/>
   <feature policy='require' name='vmx'//>
</cpu/>
...

# virsh create nestedvm.xml

启动虚拟机后登陆并安装 KVM,执行 kvm-ok 和 modinfo 均出现错误:

# apt-get install ubuntu-virt-server

# kvm-ok
INFO: /dev/kvm does not exist
HINT:   sudo modprobe kvm_intel
INFO: Your CPU supports KVM extensions
KVM acceleration can be used

# modinfo kvm_intel
ERROR: modinfo: could not find module kvm_intel

这是因为我们使用的是 ubuntu-12.04-server-cloudimg-amd64-disk1.img 这个官方虚拟机镜像,这个镜像使用的是 linux 3.2.0-23-virtual 内核,缺少 KVM 模块(kvm-intel.ko),所以我们需要改为 generic 内核:

# uname -a
Linux test 3.2.0-23-virtual #36-Ubuntu SMP Tue Apr 10 22:29:03 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

# apt-get install linux-generic

重启后进入虚拟机执行 kvm-ok 和 modinfo 均成功:

# kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used

# modinfo kvm_intel
filename:       /lib/modules/3.2.0-30-generic/kernel/arch/x86/kvm/kvm-intel.ko
license:        GPL
author:         Qumranet
srcversion:     3BAF54F96ECD4B02292CAA0
depends:        kvm
intree:         Y
vermagic:       3.2.0-30-generic SMP mod_unload modversions 
parm:           vpid:bool
parm:           flexpriority:bool
parm:           ept:bool
parm:           unrestricted_guest:bool
parm:           emulate_invalid_guest_state:bool
parm:           vmm_exclusive:bool
parm:           yield_on_hlt:bool
parm:           fasteoi:bool
parm:           nested:bool
parm:           ple_gap:int
parm:           ple_window:int

现在应该可以在这个虚拟机上创建另一个虚拟机了。

调整 KVM 虚拟硬盘大小

在 OpenNebula 上创建 KVM 虚拟机如果没有事先规划好虚拟机硬盘,运行一段时间后可能过小的硬盘会成为麻烦,需要能自由的增加虚拟机硬盘容积,有两个办法:一是可以在 OpenNebula 上动态加入第二块硬盘解决第一块硬盘过小的问题;二是直接在第一块硬盘上扩大容积。第一种办法好办,直接用 virsh attach-disk 就可以。如果和调整 Xen 虚拟硬盘大小一样,不想加第二块硬盘,只想在第一块硬盘上扩大容积呢?这里只讨论虚拟机文件形式的硬盘,LVM 形式的 “硬盘” 更容易一些,可以用 lvextend + fsck 调整硬盘大小。

最简单的办法是使用 GParted,挂载 gparted-live iso 文件后启动图形化界面操作分区,很容易:

# kvm -m 512 -hda disk.0 -cdrom /root/gparted-live-0.12.1-5.iso -boot d -vnc :1

这里主要介绍不用 GParted 的办法,分区用 fdisk 就可以了,没有必要也不适合在服务器上使用图形化工具。扩大硬盘镜像:

# qemu-img resize disk.0 +100GB

找一个空闲的 loop 设备并挂上硬盘镜像:

# losetup -f
/dev/loop0

# losetup /dev/loop0 disk.0

用 fdisk 把以前的分区都删除,然后重新创建分区,如果有 swap 区依然要用类型 82 标注,boot 区要标明 bootable,要非常小心:

# fdisk /dev/loop0

挂载硬盘里面的 LVM 分区、强制校验文件系统并扩大文件系统:

# kpartx -av /dev/loop0 
# e2fsck -f /dev/mapper/loop0p1 
# resize2fs -f /dev/mapper/loop0p1 

用 mount 测试一下扩大后的文件系统是否能正常 mount:

# mount /dev/mapper/loop0p1 /mnt
# ls /mnt

卸载和清理:

# umount /mnt
# kpartx -dv /dev/loop0 
# losetup -d /dev/loop0

把上面的步骤弄个小脚本,只对 http://cloud-images.ubuntu.com/ 下载的镜像有效,如果是自己做的镜像需要调整 fdisk 分区时候的指令。注意这里 fdisk 分区的时候 d 是删除分区 n 是创建分区 p 是主分区 1 是第1个 2是第2个 w 是保存,具体看 fdisk 帮助:

#!/bin/bash

DISK=$1
SIZE=$2

qemu-img resize $DISK $SIZE

losetup /dev/loop0 $DISK
fdisk /dev/loop0 <<EOF
d
n
p
1
2

w
EOF

kpartx -av /dev/loop0
e2fsck -f /dev/mapper/loop0p1
resize2fs -f /dev/mapper/loop0p1
kpartx -dv /dev/loop0

losetup -d /dev/loop0

迁移 VMware ESXi 上的 Windows 虚拟机到 KVM

我们发现 VMware vShpere 私有云成本太高,比如我们实验室随便一台服务器就有 512GB 内存,按照 VMware vSphere Standard(标准版)的授权我们需要 512/32=16 个授权,每个授权1293.5美元(又涨价了),1台服务器就需要约2万美元(16个授权),这个授权只是版权价格(LICENSE PRICE),还不包括每年的 1 YEAR SUPPORT & SUBSCRIPTION(419.9美元),这是在抢钱不~,今年初我们买了3个授权来评估和测试,但是按照这个授权方案只能用在2台服务器上(1台 24GB 内存,1台 64GB 内存),VMware 的产品实在不适合我们,我们打算把 VMware ESXi 上现有的一些虚拟机迁移到 OpenNebula/KVM 上。

最先迁移的是一台 Windows Server 2008 R2 虚拟机,这台虚拟机跑在 VMware ESXi 上专门用来运行 VMware vCenter Server(vCenter Server 只能安装在64位的 Windows 系统上),有些现成迁移工具比如 virt-v2v 等,不过个人还是喜欢自己动手,那些工具有时候不太好用。下面的步骤应该对其他的 Windows 版本也有效。

首先用 vShpere Client 登陆到 VMware ESXi 5.0 上打开防火墙设置,允许 ESXi 上的 ssh server 和 ssh client 可用,否则不能 ssh 登陆到 ESXi 也不能从 ESXi 上 scp 镜像到 KVM 服务器,设置具体在 Configuration > Software > Security Profile > Firewall > Properties … > SSH Client 里:

vmware esxi 5.0 enable ssh client

然后 ssh 登陆 VMware ESXi 5.0 服务器(172.16.39.100)后,scp 所需要的镜像文件(后缀名为 .vmdk)到 KVM 服务器(172.16.39.101)上

$ ssh root@172.16.39.100
Password: 
The time and date of this login have been sent to the system logs.

VMware offers supported, powerful system administration tools.  Please
see www.vmware.com/go/sysadmintools for details.

The ESXi Shell can be disabled by an administrative user. See the
vSphere Security documentation for more information.

~ # scp /vmfs/volumes/localstore/vcenter/vcenter-flat.vmdk root@172.16.39.101:/root

把 VMware 的 vmdk 格式转化成 KVM 的格式,因为从 v0.12 开始 qemu-kvm 已经支持 VMware 的硬盘格式 v6 和 v7,所以这一步其实是可以省略的,换句话说 kvm 可以直接启动 vmdk 格式的虚拟机。

$ ssh root@172.16.39.101
# qemu-img convert vcenter-flat.vmdk vcenter.img

最后用 virsh create vcenter.xml 的时候记得 vcenter.xml 文件里面关于硬盘的部分是如下设置,还有记得打开 vnc 设置(别忘了 Windows 是图形界面的):

# vi vcenter.xml
...
 <disk type='file' device='disk'/>
      <driver name='qemu' type='raw'/>
      <source file='/root/vcenter.img'/>
      <target dev='hda' bus='ide'/>
      <address type='drive' controller='0' bus='0' unit='0'/>
    </disk>
    <controller type='ide' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
...
    <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>
      <listen type='address' address='0.0.0.0'/>
    </graphics>
...

# virsh create vcenter.xml

Troubleshooting

如果启动 Windows 后用 vnc 客户端连接 KVM 服务器,Windows 启动过程中可能出现蓝屏 Stop 0x0000007B 错误,这是因为硬盘控制器驱动变了:

windows blue screen

解决办法是在迁移前下载 MergeIDE.zip 后解压,双击运行 MergeIDE.bat 文件,然后关闭 Windows 重新按照上面的步骤走一遍。

在 KVM 上访问 FreeBSD 虚拟机终端

在移植旧的物理服务器到虚拟机的时候还剩一些非 Linux 系统没有移,主要是 FreeBSD 和 Solaris 系统,还有几台 OpenBSD 的,这些系统大部分运行在很古老的机器上,有的甚至比我的 FreeBSD on IBM TP600E 还老,需要问一下管理员这些机器在干嘛,是否能合并到虚拟机,管理员刚好这周请假去了 WWDC 现场。

提到今年的 WWDC,看完发布会视频又要扯一下了,比较激动的是那个 2880×1800 屏幕的 MacBook Pro,码农实在太需要关怀了,任何每天看电脑8小时以上的人都需要好显示器,Life is short,对自己好点,Get a Mac:)

做了 FreeBSD for KVM 的镜像以后需要能在 KVM 上像访问 Linux 虚拟机终端那样访问 FreeBSD 的虚拟机终端,步骤和在 Linux 上差不多。

在 FreeBSD Guest 上配置

登陆 FreeBSD 后添加和编辑 loader.conf 文件:

# vi /boot/loader.conf
console="comconsole"

在 /etc/ttys 最后加上 ttyd0 一行:

# vi /etc/ttys
...
# Serial terminals
#ttyu0   "/usr/libexec/getty std.9600"   dialup   off secure
ttyu0   "/usr/libexec/getty std.9600"   vt100   on secure
...

重启 FreeBSD:

# reboot

确定 virsh edit 有下面这几行:

# virsh edit freebsd
...
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <video>
      <model type='cirrus' vram='9216' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
...

必要时重启 libvirtd:

# /etc/init.d/libvirtd restart
Stopping libvirtd daemon:                                  [  OK  ]
Starting libvirtd daemon:                                  [  OK  ]

freebsd console on kvm

在 KVM 上访问 Linux 虚拟机终端

用 OpenNebula 制作 Ubuntu 镜像后可以用这个镜像当作模版来创建 OpenNebula 虚拟机,发现一个问题是,这个 KVM 虚拟机(guest)无法在母机(host)上用 virsh console 登陆和显示终端,这是因为那个模板没有引入 console 需要的参数,而且虚拟机里面也没有做相关的设置。解决办法分两步,首先登陆到虚拟机修改一些配置;然后在运行这个虚拟机的 KVM 节点(host)上修改虚拟机的 xml 配置文件。以下介绍虚拟机是 Ubuntu 和 CentOS 的两种配置情况,FreeBSD 配置情况可以参考:在 KVM 上访问 FreeBSD 虚拟机终端

在 Ubuntu Guest 上配置

登陆 ubuntu 虚拟机增加 ttyS0.conf 文件及其内容:

$ sudo vi /etc/init/ttyS0.conf
# ttyS0 - getty
#
# This service maintains a getty on ttyS0 from the point the system is
# started until it is shut down again.
start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]
respawn
exec /sbin/getty -L 38400 ttyS0 vt102

如果不只是希望 Linux login 后看到终端,也希望看到 Linux 的启动过程的话需要在 /etc/default/grub 加入 GRUB_CMDLINE_LINUX 一行,记得运行 update-grub2 自动配置 grub:

$ sudo vi /etc/default/grub
...
GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0"
...

$ sudo update-grub2

在 CentOS Guest 上配置

在 /etc/securetty 文件末尾追加 ttyS0 一行:

# echo “ttyS0″ >> /etc/securetty

在 /etc/grub.conf 文件里的 kernel 一行增加 console=ttyS0:

# vi /etc/grub.conf
...
title CentOS (2.6.32-220.el6.x86_64)
        root (hd0,0)
        kernel /vmlinuz-2.6.32-220.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_roo
ot rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarr
cyrheb-sun16 rhgb crashkernel=auto quiet rd_LVM_LV=VolGroup/lv_root  KEYBOARDTYPP
E=pc KEYTABLE=us rd_NO_DM console=ttyS0
        initrd /initramfs-2.6.32-220.el6.x86_64.img
...

在 /etc/inittab 中增加 ttyS0 一行:

# vi /etc/inittab
...
S0:12345:respawn:/sbin/agetty ttyS0 115200

在 KVM Host 上配置

登陆 KVM 母机(host)用 virsh edit 修改虚拟机的 KVM 启动配置文件,在 device 下面加上 serial 一栏:

# virsh list
Id Name State
----------------------------------
49 one-28 running

# virsh edit one-28
...
<device>
...
    <serial type='pty'>
        <target port='0'/>
    </serial>
    <console type='pty'>
        <target type='serial' port='0'/>
    </console>
...
</devices>

修改后重启 libvirtd,并手动关闭和启动虚拟机:

# /etc/init.d/libvirtd restart
Stopping libvirtd daemon:                                  [  OK  ]
Starting libvirtd daemon:                                  [  OK  ]

# virsh shutdown one-28
# virsh start one-28

修改 OpenNebula 虚拟机实例的内存大小

OpenNebula 创建虚拟机(实例)以后将不能直接更改虚拟机的配置参数,如 CPU、内存等。如果创建虚拟机以后发现内存给的太大,想改小怎么办呢?如何给 OpenNebula 上的虚拟机修改内存呢?(注:OpenNebula 没有直接操作的命令,需要到节点上用 virsh setmem 动态修改。)让人不可思议的是,OpenNebula 推荐的方法是删除原有虚拟机以后重新创建一个配置合适的虚拟机,一些云计算平台认为虚拟机(计算资源)应该像自来水一样打开就用,不用就关闭。个人觉得云计算应该至少能随时改变计算资源(配置),而不是删除+创建。

比如下面这个 id 为28的虚拟机实例用了 2GB 内存,想修改到 1GB:

# onevm list
ID USER     GROUP    NAME         STAT CPU     MEM        HOSTNAME        TIME
...
14 root     oneadmin queue        runn   3      1G         cloud06 40 20:38:50
18 root     oneadmin grid03       runn 144     24G         cloud18 39 00:47:01
19 root     oneadmin grid02       runn 143     32G         cloud21 39 00:26:26
28 root     oneadmin monitor      runn   9      2G         cloud03 05 22:04:14
...

首先找到这个28号虚拟机实例所在的 OpenNebula 计算节点(node),从上面的 HOSTNAME 看出 monitor 运行在 cloud03 这个节点上,我们 ssh 到这个节点操作发现这台 OpenNebula ID 为28的虚拟机实例在这个节点上名字为 one-28:

# ssh root@cloud03

# virsh list
 Id Name                 State
----------------------------------
 39 one-20               running
 42 one-25               running
 45 one-28               running

可以动态修改 one-28 的内存参数为 1GB,但是这种办法重启后就会丢失配置重回到 2GB:

# virsh setmem one-28 1048576

所以最好关闭 one-28 后再修改 one-28 配置文件,改动 memory 部分为 1048576(1GB),修改完毕后启动虚拟机:

# virsh shutdown one-28

# virsh edit one-28
...
1048576
...

# virsh start one-28

修改完后 onevm list 会发现 one-28 内存大小依然是 2GB,没有变,这是因为这部分纪录在 OpenNebula 的数据库里,需要修改数据库,先 select 一下发现 OpenNebula 把 VM 的 XML 配置文件写在数据库里,这容易办,用 SQL 语句的 update 操作更新一下数据库:

# sqlite3 /var/lib/one/one.db

sqlite> select * from vm_pool where oid='28';
28|monitor|...2097152...|0|0|1337014904|3|3|1|0|0

sqlite> update vm_pool set body="...1048576..." where oid="28";

然后 onevm list 就会得到正确的、修改过内存的虚拟机实例了:

# onevm list
ID USER     GROUP    NAME         STAT CPU     MEM        HOSTNAME        TIME
...
14 root     oneadmin queue        runn   3      1G         cloud06 40 23:44:51
18 root     oneadmin grid03       runn 144     24G         cloud18 39 03:47:01
19 root     oneadmin grid02       runn 143     32G         cloud21 39 03:32:27
28 root     oneadmin monitor      runn   9   1024M         cloud03 05 01:10:15
...