WARNING! This article is written for Xen 4.0 or 4.1, which come with the xm utility for managing the hypervisor out of the box. Newer Xen versions (4.2 and above) have only xl by default, and this article does not apply to working with it. A bit later I'll write a separate article on how to do this for 4.2 and above (since I'm already going through these steps myself).
In one of the articles I already showed how to install a paravirtualized Debian Squeeze or convert an existing HVM domain to paravirtual mode. The downside of that method was that you first had to install an HVM domain and only then convert it to paravirtual. That is, at the very least, it takes longer and requires CPU support for virtualization technologies (Intel Vanderpool, for example).
In this same topic I want to show how to install Squeeze right away as a paravirtual machine.
So, let's begin.
Creating the directory and disk
$ cd /etc/xen
$ sudo mkdir pvtest
$ cd pvtest
$ sudo dd if=/dev/zero of=hdd0 bs=1M count=1 seek=10240
From here on, instead of pvtest specify the actual directory - that is, the directory where your virtual machine with all its accessories will be located.
You also need to set your own hard disk size. In this example it's set to 10240MB (10GB) and the disk is created as dynamic (i.e., it will grow as it fills with data rather than immediately claiming all 10GB).
If you need to create a static disk right away (this takes longer to create - the disk gets filled with zeros - but is faster to work with afterward and will save you from trouble when you start running out of space because you're keeping more dynamic disks than your physical space allows).
To do this, instead of the previous dd command use this variant:
$ sudo dd if=/dev/zero of=hdd0 bs=1M count=10240
Downloading the installation images
$ sudo wget http://ftp.debian.org/debian/dists/squeeze/main/installer-i386/current/images/netboot/xen/initrd.gz
$ sudo wget http://ftp.debian.org/debian/dists/squeeze/main/installer-i386/current/images/netboot/xen/vmlinuz
$ sudo wget http://ftp.debian.org/debian/dists/squeeze/main/installer-i386/current/images/netboot/xen/xm-debian.cfg
Here the 32-bit kernel, initrd, and config file for installing the virtual machine are downloaded. If you need the 64-bit versions (I have not tested their installation!) - instead of "installer-i386" write "installed-amd64".
Configuring the xm-debian.cfg fileOpen this file for editing and adjust the options to suit your needs. For example, change the amount of RAM allocated to the virtual machine (the "memory" parameter), change the path to the hard disk we created (the "disk" parameter), set the network interface, etc.
Here is an example of this file for our "pvtest" machine:
# -*- mode: python; -*-
#============================================================================
# Example Python setup script for Debian guest installation.
#============================================================================
#
# Standard options are configured as normal. Only a subset are included below.
# See /usr/share/doc/xen-utils-common/examples for full examples.
#
# After standard options are configure use
# xm create xm-debian.cfg install=true"
# to start the Debian Installer.
#
# In the installation case the following additional variables exist:
#
# COMMON OPTIONS
# install-method: "cdrom" or "network"
# install-arch: which architecture to install. e.g. i386 or amd64
# install-installer: URL or path to the Debian Installer bits. By
# default for a network install these are located under
# install-mirror. For a CDROM install the default is a fixed path on
# the CD.
# install-kernel, install-ramdisk: URL/path to the installer kernel and
# ramdisk to use, by default these are located via install-installer.
# install-extra: extra command line arguments
#
# CDROM SPECIFIC OPTIONS
# install-media: Path to the Debian install media (i.e. an ISO)
# install-cdrom-device: Name of the CD-ROM device within the guest.
#
# NETWORK SPECIFIC OPTIONS
# install-suite: which Debian version to install. e.g. lenny, squeeze or sid
# install-mirror: which Debian mirror to use
# e.g. http://ftp.uk.debian.org/debian
#============================================================================
#----------------------------------------------------------------------------
# Standard variables
# Initial memory allocation (in megabytes) for the new domain.
memory = 256
# A name for your domain. All domains must have different names.
name = "pvtest"
# 128-bit UUID for the domain. The default behavior is to generate a new UUID
# on each call to 'xm create'.
#uuid = "06ed00fe-1162-4fc4-b5d8-11993ee4a8b9"
# List of which CPUS this domain is allowed to use, default Xen picks
#cpus = "" # leave to Xen to pick
#cpus = "0" # all vcpus run on CPU0
#cpus = "0-3,5,^1" # run on cpus 0,2,3,5
# Number of Virtual CPUS to use, default is 1
vcpus = 1
#----------------------------------------------------------------------------
# Define network interfaces.
# By default, no network interfaces are configured. You may have one created
# with sensible defaults using an empty vif clause:
#
# vif = ['']
#
# or optionally override backend, bridge, ip, mac, script, type, or vifname:
#
# vif = ['mac=fe:16:3e:68:fc:fe, bridge=eth0']
#
# or more than one interface may be configured:
#
# vif = ['', 'bridge=xenbr1']
vif = ['mac=fe:16:3e:68:fc:fe, bridge=eth0']
#----------------------------------------------------------------------------
# Define the disk devices you want the domain to have access to, and
# what you want them accessible as.
# Each disk entry is of the form phy:UNAME,DEV,MODE
# where UNAME is the device, DEV is the device name the domain will see,
# and MODE is r for read-only, w for read-write.
#
# NB: Only xvd devices are supported by the kernel in Debian Lenny and later.
disk = ['file:/etc/xen/pvtest/hdd0,xvda,w']
#----------------------------------------------------------------------------
# Define frame buffer device.
#
# By default, no frame buffer device is configured.
#
# To create one using the SDL backend and sensible defaults:
#
# vfb = [ 'type=sdl' ]
#
# This uses environment variables XAUTHORITY and DISPLAY. You
# can override that:
#
# vfb = [ 'type=sdl,xauthority=/home/bozo/.Xauthority,display=:1' ]
#
# To create one using the VNC backend and sensible defaults:
#
# vfb = [ 'type=vnc' ]
#
# The backend listens on 127.0.0.1 port 5900+N by default, where N is
# the domain ID. You can override both address and N:
#
# vfb = [ 'type=vnc,vnclisten=127.0.0.1,vncdisplay=1' ]
#
# Or you can bind the first unused port above 5900:
#
# vfb = [ 'type=vnc,vnclisten=0.0.0.0,vnunused=1' ]
#
# You can override the password:
#
# vfb = [ 'type=vnc,vncpasswd=MYPASSWD' ]
#
# Empty password disables authentication. Defaults to the vncpasswd
# configured in xend-config.sxp.
#============================================================================
# Debian Installer specific variables
def check_bool(name, value):
value = str(value).lower()
if value in ('t', 'tr', 'tru', 'true'):
return True
return False
global var_check_with_default
def var_check_with_default(default, var, val):
if val:
return val
return default
xm_vars.var('install', use='Install Debian, default: false', check=check_bool)
xm_vars.var("install-method",
use='Installation method to use "cdrom" or "network" (default: network)',
check=lambda var, val: var_check_with_default('network', var, val))
# install-method == "network"
xm_vars.var("install-mirror",
use='Debian mirror to install from (default: http://ftp.debian.org/debian)',
check=lambda var, val: var_check_with_default('http://ftp.debian.org/debian', var, val))
xm_vars.var("install-suite",
use='Debian suite to install (default: squeeze)',
check=lambda var, val: var_check_with_default('squeeze', var, val))
# install-method == "cdrom"
xm_vars.var("install-media",
use='Installation media to use (default: None)',
check=lambda var, val: var_check_with_default(None, var, val))
xm_vars.var("install-cdrom-device",
use='Installation media to use (default: xvdd)',
check=lambda var, val: var_check_with_default('xvdd', var, val))
# Common options
xm_vars.var("install-arch",
use='Debian mirror to install from (default: i386)',
check=lambda var, val: var_check_with_default('i386', var, val))
xm_vars.var("install-extra",
use='Extra command line options (default: None)',
check=lambda var, val: var_check_with_default(None, var, val))
xm_vars.var("install-installer",
use='Debian installer to use (default: network uses install-mirror; cdrom uses /install.ARCH)',
check=lambda var, val: var_check_with_default(None, var, val))
xm_vars.var("install-kernel",
use='Debian installer kernel to use (default: uses install-installer)',
check=lambda var, val: var_check_with_default(None, var, val))
xm_vars.var("install-ramdisk",
use='Debian installer ramdisk to use (default: uses install-installer)',
check=lambda var, val: var_check_with_default(None, var, val))
xm_vars.check()
if not xm_vars.env.get('install'):
bootloader="pygrub"
elif xm_vars.env['install-method'] == "network":
import os.path
print "Install Mirror: %s" % xm_vars.env['install-mirror']
print "Install Suite: %s" % xm_vars.env['install-suite']
if xm_vars.env['install-installer']:
installer = xm_vars.env['install-installer']
else:
installer = xm_vars.env['install-mirror']+"/dists/"+xm_vars.env['install-suite'] + \
"/main/installer-"+xm_vars.env['install-arch']+"/current/images"
print "Installer: %s" % installer
print
print "WARNING: Installer kernel and ramdisk are not authenticated."
print
if xm_vars.env.get('install-kernel'):
kernelurl = xm_vars.env['install-kernel']
else:
kernelurl = installer + "/netboot/xen/vmlinuz"
if xm_vars.env.get('install-ramdisk'):
ramdiskurl = xm_vars.env['install-ramdisk']
else:
ramdiskurl = installer + "/netboot/xen/initrd.gz"
import urllib
class MyUrlOpener(urllib.FancyURLopener):
def http_error_default(self, req, fp, code, msg, hdrs):
raise IOError("%s %s" % (code, msg))
urlopener = MyUrlOpener()
try:
print "Fetching %s" % kernelurl
kernel, _ = urlopener.retrieve(kernelurl)
print "Fetching %s" % ramdiskurl
ramdisk, _ = urlopener.retrieve(ramdiskurl)
except IOError, _:
raise
elif xm_vars.env['install-method'] == "cdrom":
arch_path = { 'i386': "/install.386",
'amd64': "/install.amd" }
if xm_vars.env['install-media']:
print "Install Media: %s" % xm_vars.env['install-media']
else:
raise OptionError("No installation media given.")
if xm_vars.env['install-installer']:
installer = xm_vars.env['install-installer']
else:
installer = arch_path[xm_vars.env['install-arch']]
print "Installer: %s" % installer
if xm_vars.env.get('install-kernel'):
kernelpath = xm_vars.env['install-kernel']
else:
kernelpath = installer + "/xen/vmlinuz"
if xm_vars.env.get('install-ramdisk'):
ramdiskpath = xm_vars.env['install-ramdisk']
else:
ramdiskpath = installer + "/xen/initrd.gz"
disk.insert(0, 'file:%s,%s:cdrom,r' % (xm_vars.env['install-media'],
xm_vars.env['install-cdrom-device']))
bootloader="pygrub"
bootargs="--kernel=%s --ramdisk=%s" % (kernelpath, ramdiskpath)
print "From CD"
else:
print "WARNING: Unknown install-method: %s." % xm_vars.env['install-method']
if xm_vars.env.get('install'):
# Figure out command line
if xm_vars.env['install-extra']:
extras=[xm_vars.env['install-extra']]
else:
extras=[]
# Reboot will just restart the installer since this file is not
# reparsed, so halt and restart that way.
extras.append("debian-installer/exit/always_halt=true")
extras.append("--")
extras.append("quiet")
console="hvc0"
try:
if len(vfb) >= 1:
console="tty0"
except NameError, e:
pass
extras.append("console="+ console)
extra = str.join(" ", extras)
print "command line is \"%s\"" % extra
Starting the installation
$ sudo xm create -c /etc/xen/pvtest/xm-debian.cfg install=true install-mirror=ftp://ftp.ru.debian.org/debian install-ramdisk=/etc/xen/pvtest/initrd.gz install-kernel=/etc/xen/pvtest/vmlinuz
In the parameters where the path is specified (/etc/xen/pvtest/) - this path must be set according to the location of the virtual guest in your case.
In this example the RU mirror - Russian - is specified. You can specify the one you prefer.
The -c parameter tells Xen that we want to connect directly to the virtual machine's console.
The install=true and install-mirror=... parameters indicate that we want to run the virtual machine installation specifically, and show where to download the installer images from.
This launches the installation console. The difference from the usual HVM console shows up in the color - it's black and white, with no colors at all.
Installation proceeds via the usual method, the same as with an HVM install or an install on bare metal:
a) Choose the language - only C and English are available. Choose C.
b) Our location. Choose "Europe" and "Russian Federation" (if, of course, you live in Russia; otherwise choose the country where you live).
c) Mirror - choose the nearest or your preferred mirror.
d) It downloads the packages.
e) Set the root password and create the first user.
f) Specify the timezone
g) It loads the packages again
h) Partition the disk (create partitions and format them). If you are not sure, you can use the simplest option, "Guided - use entire disk".
i) Agree to the "write the changes to disk" question. The installer partitions the disk and formats it.
j) Next it loads the packages again.
k) Choose the keyboard layout (you can leave the default).
l) Answer the question of whether you want to send package usage data to the Debian developers. Up to you.
m) Next you are asked what software to install. Do not forget to uncheck "Graphical desktop environment" if you are installing a server. I recommend right away checking "SSH server" - you will need SSH access to this machine.
n) And again the installer downloads a set of packages from the internet.
o) You are asked whether to install the GRUB bootloader. Even though we have a paravirtual system - agree.
p) That is it, you are warned that the installation is finished and the virtual machine will now be shut down.
q) Now, after the OS is installed and the virtual machine is stopped - we need to create a configuration file to launch it. No, this is not the same file we used to create the virtual guest - we need a different one.
Below is an example of such a file. In this example the parameters are chosen to match the installation configuration file example shown earlier. Of course, you can change the parameters as you need.
Let's call this file "vm.cfg" and put it in "/etc/xen/pvtest/vm.cfg".
root = "root=/dev/xvda1 ro console=hvc0"
memory = '256'
bootloader = 'pygrub'
disk = [ 'file:/etc/xen/pvtest/hdd0,xvda,w' ]
vif = [ 'bridge=eth0, mac=fe:16:3e:68:fc:fe' ]
name = 'pvtest'
vnc = 1
sdl = 0
apic = 1
acpi = 1
#localtime = 0
vcpus = 1
pae = 1
on_poweroff = 'destroy'
on_reboot = 'restart'
on_crash = 'restart'
vncconsole = 1
stdvga = 0
videoram = 16
shadow_memory = 8
timer_mode = 1
Pay special ATTENTION to the "root=" option, where we have set the value "xvda1". This value is suitable for cases where the "/boot" or "/" partition is on the first partition (xvda1). If, for example, you put a swap partition or something else at the start of the disk, then specify in this parameter the number of the partition with the boot information ("/boot" or "/" if you have everything on a single partition).
It's also worth noting that here we use the PyGRUB bootloader to load the kernel from inside the installed operating system. This is good because:
a) You can immediately boot the freshly installed paravirtualized system without first having to mount its filesystem to extract the kernel;
b) OS updates that affect the kernel actually take effect - since on reboot the kernel is loaded from inside the virtual machine, so it is the updated one.
There are also downsides to this method, the most important of which, as other administrators and Xen themselves point out, is security. However, if you alone are in charge of the virtual machines and they are entirely at your disposal (rather than being rented out), then this hole is not exploitable in your case.
c) Start the virtual machine.
$ sudo xm create -c vmtest/vm.cfg
The -c flag, as already mentioned, tells Xen to immediately open the machine's console.
So, if you did everything correctly, including specifying the correct partition in "root=" in the final config - you will get a paravirtualized Linux Debian Squeeze running, paravirtualized right out of the box.
Comments