XCP/XenServer自动化创建并初始化VM


参考资料:http://wiki.xenproject.org/wiki/XCP_Beginners_Guide

在我们的生产环境中,之前一直使用的是EC2的Instances做的前端服务器,在访问量增加的时候,通过设置一个阈值,超过阈值的就自动触发创建一定数量的新的Instances,在访问量低于某一个值的时候,就自动删除一定数量的Instances;由于AWS可以根据设置好的模板复制出一个新主机并且给予一个新的IP地址,所以只需要管理好主机名就是了;这方面,我们通过一个Redis数据库来管理主机名的ID,创建主机的时候会自动根据最大的ID依次递增,而删除主机的时候会自动根据最大的ID依次递减。
但这并不是我这篇文章要讲的重点,要实现这种自动伸缩,方法有很多,我们就是通过很多简单的脚本来实现的。

后来,我们建立了本地机房,购买了一定数量的物理服务器,每台服务器安装了XCP,并打算在上面创建虚拟机。之前我们一直通过的是XenCenter的方式,图形化的操作来创建,但是要实现和上面EC2一样的自动化创建主机的话,我们遇到了一个问题,那就是,每台新建的VM,其IP地址与主机名等在复制之后,不能自动更新。我请教过一些同行,可能是他们并没有这样大规模的使用XCP/XenServer,所以都是在XenCenter里面创建好机器,然后通过console进去修改IP地址和主机名等等。

于是乎,我就在Google上反复搜索,最终找到了一个解决办法。那就是通过修改VM的内核引导程序,将自定义的参数传递给一个叫PV-args的内核参数,这样在VM启动之后就可以通过/proc/cmdline中获取到参数了,在获取到之后,VM在第一次启动时,会执行一个Shell脚本,来根据自定义的参数更新IP地址和主机名等;这样,一台新创建好的VM就可以自动连接到Puppet这样的管理工具了,接着再自动拉取最新的配置,完成整个服务器的自动化配置,然后上线。

环境介绍:
xenhost1 一台物理XCP/XenServer主机
vm-template 之前定义好的模板
vm-host-1 将要创建的VM

以下,就是我的具体操作记录:
获取vm-template的uuid
[root@xenhost1 ~]# xe vm-list | grep -B 1 vm-template

uuid ( RO)           : c77040ae-3a50-9217-ff03-41992c34d1ec
     name-label ( RW): vm-host-1

修改内核启动方式,并传递自定义参数
[root@xenhost1 ~]# xe vm-param-set uuid=c77040ae-3a50-9217-ff03-41992c34d1ec HVM-boot-policy=""
[root@xenhost1 ~]# xe vm-param-set uuid=c77040ae-3a50-9217-ff03-41992c34d1ec PV-bootloader="pygrub"
[root@xenhost1 ~]# xe vm-param-set uuid=c77040ae-3a50-9217-ff03-41992c34d1ec PV-args="_hostname=vm-template _ipaddr=192.168.1.121 _netmask=255.255.255.0 _gateway=192.168.1.1"

启动vm-template
[root@xenhost1 ~]# xe vm-start vm=vm-template

获取自定义参数
[root@vm-template ~]# cat /proc/cmdline

ro root=/dev/mapper/vg_t-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 rd_LVM_LV=vg_t/lv_root crashkernel=129M@0M  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet  _hostname=vm-template _ipaddr=192.168.1.121 _netmask=255.255.255.0 _gateway=192.168.1.1

定义初始化脚本
[root@vm-template ~]# cat /etc/rc.local

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local
/root/bootstrap.sh

创建具体的脚本
[root@vm-template ~]# touch /root/bootstrap.sh
[root@vm-template ~]# chmod +x /root/bootstrap.sh

[root@vm-template ~]# vim /root/bootstrap.sh

#!/bin/bash
# 
# Bootstrap Script for Hostname,Network...
# 
# Author: Dong Guo
# Last Modified: 2013/10/24 by Dong Guo

options=$(cat /proc/cmdline|sed 's/.*rhgb quiet  //g')
config=/etc/sysconfig/network-scripts/ifcfg-eth0
failed=/root/bootstrap.failed

function check_root(){
  if [ $EUID -ne 0 ]; then
    echo "This script must be run as root"
    exit 1
  fi
}

function configure_os(){
  echo "DEVICE=eth0" > $config
  echo "ONBOOT=yes" >> $config
  echo "BOOTPROTO=none" >> $config
  
  for i in $options
  do
    option=$(echo $i|cut -d "=" -f 1)
    value=$(echo $i|cut -d "=" -f 2)
    if [ "${option:0:1}" = "_" ]; then
      case "$option" in
        _hostname)
         oldname=$(hostname)
         newname=$value
         sed -i s/"$oldname"/"$newname"/g /etc/sysconfig/network
         hostname $newname
        ;;
        _ipaddr)
         echo "IPADDR=$value" >> $config
        ;;
        _netmask)
         echo "NETMASK=$value" >> $config
        ;;
        _gateway)
         echo "GATEWAY=$value" >> $config
        ;;
      esac
    fi
  done
}

function restart_network(){
  /etc/init.d/network restart
}

function check_status(){
  gateway=$(grep -w GATEWAY $config|cut -d "=" -f 2)
  route -n | grep -wq $gateway
  if [ $? -eq 0 ]; then
    sed -i /bootstrap/d /etc/rc.local
    if [ -a $failed ]; then
      rm -f $failed
    fi
  else
    touch $failed
  fi
}

check_root
configure_os
restart_network
check_status

查看脚本
[root@vm-template ~]# ls

anaconda-ks.cfg  install.log.syslog bootstrap.sh install.log

退出vm-template
[root@vm-template ~]# exit

关闭vm-template
[root@xenhost1 ~]# xe vm-shutdown vm=vm-template

获取xenhost1本地存储的uuid
[root@xenhost1 ~]# xe sr-list | grep -A 2 -B 3 xenhost1 | grep -A 4 -B 1 "Local Storage"

uuid ( RO)                : 38e1ae16-6d5e-55af-5b55-8e26d30b13d7
          name-label ( RW): Local storage
    name-description ( RW): 
                host ( RO): xenhost1
                type ( RO): lvm
        content-type ( RO): user

复制创建新的VM vm-host-1
[root@xenhost1 ~]# xe vm-copy new-name-label=vm-host-1 vm=vm-template sr-uuid=38e1ae16-6d5e-55af-5b55-8e26d30b13d7

9529e2e5-e5b8-a22f-83da-6d3cd1be8101

获取vm-host-1的uuid
[root@xenhost1 ~]# xe vm-list | grep -A 1 9529e2e5-e5b8-a22f-83da-6d3cd1be8101

uuid ( RO)           : 9529e2e5-e5b8-a22f-83da-6d3cd1be8101
     name-label ( RW): vm-host-1

传递自定义参数
[root@xenhost1 ~]# xe vm-param-set uuid=9529e2e5-e5b8-a22f-83da-6d3cd1be8101 PV-args="_hostname=vm-host-1 _ipaddr=192.168.1.122 _netmask=255.255.255.0 _gateway=192.168.1.1"

启动vm-host-1
[root@xenhost1 ~]# xe vm-start vm=vm-host-1

登陆vm-host-1
[root@xenhost1 ~]# ssh root@192.168.1.122

The authenticity of host '192.168.1.122 (192.168.1.122)' can't be established.
RSA key fingerprint is 81:f9:a4:45:0e:95:36:c5:e5:3e:80:33:8a:3a:db:18.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.122' (RSA) to the list of known hosts.
root@192.168.1.122's password: 

Last login: Thu Oct 24 05:49:24 2013 from 10.0.8.34

查看初始化脚本标记是否被移除
[root@vm-host-1 ~]# cat /etc/rc.local

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local

大功告成
[root@vm-host-1 ~]# ls

anaconda-ks.cfg install.log.syslog bootstrap.sh  install.log

退出vm-host-1
[root@vm-host-1 ~]# exit

, , ,

  1. #1 by holyzhou on 2013/11/01 - 23:18

    一直关注你的博客,你有一段时间没更新了,没想到一更新就两篇,给力。问一下 ${option:0:1} ,这种用法我都没见过,很新奇。似乎第一个0是表示从哪个值开始(0-based),后一位 1,应该是截取的长度。我不知道该用什么关键词,所以也没找到这种用法的文章

    • #2 by mcsrainbow on 2013/11/02 - 13:34

      惭愧,其实工作中有很多东西可以总结的,一来有时候的确是忙,二来自己现在确实有点懒惰了。
      ${option:0:1}这种用法我也是看了别人的Shell才知道的,经过测试,发现:0:1代表的是取变量的字符长度,非常方便。

  2. #3 by carfieldboy on 2013/11/05 - 21:59

    你好,为什么选择XCP而不是XenSERVER呢?XenSERVER目前6.2功能都免费开放使用了,XCP有什么优点吗?

    • #4 by mcsrainbow on 2013/11/06 - 14:36

      呵呵,算是历史遗留吧,我们使用XCP的时候,XenServer 6.2都还没有出来。不过本文的操作对于XenServer也是同样适用的。

  3. #5 by 销声匿迹linux on 2013/11/21 - 16:16

    为何这么叼,,,,,,,,,,,好牛B的感觉,,,收藏了,,,,

  4. #6 by 钱弢深 on 2014/02/27 - 22:27

    你好,在看到这篇文章时很兴奋,因为真的是我现在想找的方法,找了好多天终于找到了。然后找出来一台服务器,留了一台vm,其他删掉了,这台vm是可以远程的有固定ip。然后按照你的教程,根据自己机子的uuid和ip,再进行到第二步,修改内核启动方式,并传递自定义参数,然后启动这台vps。之后启动失败,错误提示为
    The server failed to handle your request, due to an internal error. The given message may give details useful for debugging the problem.
    message: xenopsd internal error: VM = 96d12d6a-5c7e-6c94-1383-53f3b620d52b; domid = 6; Bootloader.Bad_error Traceback (most recent call last):
    File "/usr/bin/pygrub", line 903, in ?
    fs = fsimage.open(file, part_offs[0], bootfsoptions)
    IOError: [Errno 95] Operation not supported
    能指导下这部错误是哪里的问题吗,谢谢

    • #7 by qts on 2014/02/28 - 11:00

      还有我想在多问下,这种方法适用于win2003的vm吗

      • #8 by Hong on 2014/07/14 - 09:17

        windows内核应该也是类似,首先xenserver设置ip参数给windows VM,然后再第一次启动windows VM的时候由一个自定义服务读取ip等参数进行修改,然后在xenserver把参数设为空,这样下次启动就不会再去修改ip等东西(服务读取不到参数不能进行修改),这还要进行服务开发,不过xenserver设置那个值和windows VM如何读取这个值,都不知道,你研究出来告诉我噢

(will not be published)
*