Creating a KVM Ubuntu VM using Debootstrap

The goal is to create a small KVM guest from scratch using deboostrap and a list of additional packages. The additional packages are defined in a file, aptly named, additional.packages.

Note this is very long but pulls together a bunch of information. I also think it will only work if you are starting this from an existing Debian or Ubuntu system.

I have created scripts which execute all the steps and made the available here. Patches to the scripts are more than welcome. Just fork on github and send me a pull request.

Why? In my case I wanted to make sure that my test servers, and production servers could be reliably rebuilt/managed without manual fiddling.

The assumptions

  1. The KVM guest will have a preallocated DNS name and static IP address
  2. The host has KVM and Grub installed
  3. You have sudo access on the host

This is important if tools like puppet or chef are going to be used to manage the server.

In the case of where the guest is a puppet client, having a fully qualified domain name helps ensure the guest puppet client generates a correct SSL certificate when it starts up. If the KVM guest doesn't have a properly set fully qualified domain name then the puppet configuration will generate certificates with an invalid name, and the post boot puppet configuration will fail.

Resources

These sites have been invaluable and I have tried to summarise a condensed version of them here which meet my goals, but this would not have been possible without them:

High-level Overview

Steps:

  1. Find out the current list of packages used for a deboostrap install
  2. Create a partial local mirror of those
  3. Create the KVM disk
  4. Install the debootstrap on the KVM disk
  5. Setup a bridged network wireless network (required for laptop hosts)
  6. Create some basic configuration on the KVM
  7. Launch the KVM instance

Please note that you will need root access, or sudo to complete a number of the steps.

Finding out what packages deboostrap needs

Use deboostrap to find out the base packages for a particular Ubuntu release. The base package list is then used to create a partial mirror. This is useful as it saves time and bandwidth.

A Ubuntu x_64 Maverick release is used in this tutorial.

Note: the same target architecture is used for both the destination servers and the laptop used for the build. The target architecture is specified using: --arch=amd64

mkdir maverick-amd64-bootstrap
cd maverick-amd64-bootstrap

maverick-amd64-bootstrap$ debootstrap --print-debs --arch=amd64 maverick \
                          `pwd`/tmp > debootstrap.packages
  I: Retrieving Release
  I: Retrieving Packages
  I: Validating Packages
  I: Resolving dependencies of required packages...
  I: Resolving dependencies of base packages...
  I: Deleting target directory

maverick-amd64-bootstrap$ cat debootstrap.packages
  base-files base-passwd bash bsdutils coreutils dash debconf debconf-i18n
  debianutils diff dpkg e2fslibs e2fsprogs findutils gcc-4.4-base grep gzip
  hostname initscripts insserv libacl1 libattr1 libblkid1 libc-bin libc6
  libcomerr2 libdb4.7 libdbus-1-3 libgcc1 liblocale-gettext-perl libncurses5
  libpam-modules libpam-runtime libpam0g libselinux1 libsepol1 libslang2 libss2
  libssl0.9.8 libstdc++6 libtext-charwidth-perl libtext-iconv-perl
  libtext-wrapi18n-perl libudev0 libuuid1 locales login lsb-base lzma makedev
  mawk mount mountall ncurses-b...

The next step is to create a local mirror of those packages.

Create a partial mirror for those packages

In order to speed up the process of creating guest VMs, create a local mirror of the minimal packages needed by debootstrap, namely those saved in the debootstrap.packages file. reprepro is the tool used to create the mirror.

These are the highlights about how to use reprepro but the for a better explanation see: lostwebsite.net blog -- Partial Debian Mirror.

Create a location to hold the mirror

$ pwd
maverick-amd64-bootstrap

mkdir mirror
cd mirror

$pwd
mirror

Note: in each section of commands the working path for those commands is displayed using pwd.

Selecting a distribution to mirror

The mirror is configured by creating two files: conf/distributions and conf/updates. The distributions file contains information about the distributions held in the mirror directory. In this case it will only be partial mirror of Ubuntu Maverick. The conf/updates specifies what to download and from where.

$pwd
maverick-amd64-bootstrap/mirror

mkdir conf
cat > conf/distributions <<EOF
Codename: maverick
Architectures: amd64 source
Description: Ubuntu maverick-amd64 (Required packages only)
Components: main
Update: ubuntu-maverick-amd64-update

Codename: maverick-updates
Architectures: amd64 source
Description: Ubuntu maverick-amd64 Updates (Required packages only)
Components: main
Update: ubuntu-maverick-amd64-update
EOF

The details of what to download and how to locate the .deb files is contained in the conf/updates. The linkage between the distributions and the updates file is the configuration item Update pointing to ubuntu-maverick-amd64-update that is defined in the updates file.

The Codename corresponds to a path on the mirror and so can not be changed. E.g. http://gb.archive.ubuntu.com/ubuntu/dists/maverick for maverick.

Next create the updates file.

$pwd
maverick-amd64-bootstrap/mirror

cat > conf/updates <<EOF
Name: ubuntu-maverick-amd64-update
Method: http://gb.archive.ubuntu.com/ubuntu
Components: main
Architectures: amd64 source
FilterList: purge all-packages.mirror
VerifyRelease: blindtrust
EOF

Of note, here is the Name: ubuntu-maverick-amd64-update which should match the name specified in the Update item of the distributions file. The Method is the Ubuntu mirror to download from. Other mirrors can be found at Official Archive Mirrors for Ubuntu.

Next we need to decide what to mirror.

Creating a list of packages to mirror

The deboostrap.mirror file contains a list of packages to download and mirror. The file is in the format: <packagenane> install. One package per line. This file will be created based on the debootstrap.packages determined in step 1.

This next chunk of code is about changing the format of the debootstrap.packages list into a format that reprepro expects. reprepro is the tool used to create the mirror.

$ pwd
maverick-amd64-bootstrap/mirror

for pkg in $(cat ../debootstrap.packages); do \
          echo $pkg install; \
        done > conf/debootstrap.mirror

cat conf/debootstrap.mirror
  base-files install
  base-passwd install
  bash install
  bsdutils install
  coreutils install
  ...

Specifying Additional Packages to Mirror

Additional packages can be added to the mirror by defining them in the debootstrap.mirror list. For this install add the following packages:

  • ruby
  • build essential
  • openssh-server
  • curl and wget
$ pwd
maverick-amd64-bootstrap/mirror

cat > additional.packages <<EOF
ruby
ruby-dev
libopenssl-ruby
rdoc
ri
irb
build-essential
curl
wget
ssl-cert
openssh-server
EOF

Choosing a Kernel

Finally add a kernel to the additional.packages file.

Here is a quick way to see what kernels are available in the maverick amd64 release.

$ pwd
mirror

curl -s \
http://gb.archive.ubuntu.com/ubuntu/dists/maverick/main/binary-amd64/Packages.gz \
    | gunzip -d | grep -E 'Package: linux-image'

  Package: linux-image
  Package: linux-image-2.6.31-14-generic
  Package: linux-image-2.6.31-14-server
  Package: linux-image-2.6.31-14-virtual
  Package: linux-image-2.6.31-302-ec2
  Package: linux-image-ec2
  Package: linux-image-generic
  Package: linux-image-server
  Package: linux-image-virtual

Select one of the packages and add it to the local packages list.

$ pwd
maverick-amd64-bootstrap/mirror

cat >> additional.packages <<EOF
linux-image-server
grub
aptitude
EOF

Resolving Dependencies for Local Packages

The local packages list contains:

$ pwd
maverick-amd64-bootstrap/mirror

cat additional.packages
  ruby
  ruby-dev
  libopenssl-ruby
  rdoc
  ri
  irb
  build-essential
  curl
  wget
  ssl-cert
  openssh-server
  linux-image-server
  grub
  aptitude

Note it's best to add grub and the kernel image now so they are available in the mirror.

But, the dependencies of those packages are also required. The germinate command can be used to resolve those dependencies. The germinate command uses configuration files within a seeds sub-directory. See the man pages for germinate for more details.

$ pwd
maverick-amd64-bootstrap/mirror

mkdir seeds
touch seeds/blacklist
touch seeds/supported
cat > seeds/STRUCTURE <<EOF
required:
supported:
EOF

for pkg in $(cat additional.packages); do \
  echo " * $pkg"; \
done > seeds/required

Verify the seeds file.

$ pwd
maverick-amd64-bootstrap/mirror

cat seeds/required | sort
 * build-essential
 * curl
 * irb
 * libopenssl-ruby
 * linux-image-server
 * openssh-server
 * rdoc
 * ri
 * ruby
 * ruby-dev
 * ssl-cert
 * wget

tree seeds
  seeds/
  |-- STRUCTURE
  |-- blacklist
  |-- required
  `-- supported

Next run germinate to discover the dependency graph.

$ pwd
maverick-amd64-bootstrap/mirror

mkdir germinate
(cd germinate && \
  germinate -v -m http://gb.archive.ubuntu.com/ubuntu \
            -a amd64 \
            -d maverick \
            -c main \
            -s seeds \
            -S file://`pwd`/..
)

At this point mirror/germinate/required will contain a list of all the required dependencies for the packages specified in additional.packages, including the packages themselves.

maverick-amd64-bootstrap/mirror/germinate/required

  Package                      | Source                 | Why         ...
  -----------------------------+------------------------+------------ ...
  adduser                      | adduser                | ssl-cert    ...
  base-files                   | base-files             | dpkg-dev    ...
  base-passwd                  | base-passwd            | base-files  ...
  binutils                     | binutils               | gcc-4.4     ...
  ...

Next combine the packages discovered by germinate with the original debootstrap packages, into one file mirror/conf/all-packages.mirror.

$ pwd
maverick-amd64-bootstrap/mirror

cat conf/debootstrap.mirror > germinate/all-packages.mirror
for pkg in $(cat germinate/required \
  | tail -n +3 \
  | head -n -2 \
  | cut -d '|' -f 1); do\
 echo $pkg install; \
done >> germinate/all-packages.mirror
cat germinate/all-packages.mirror | sort -u > conf/all-packages.mirror

The next step is to download those packages and populate the mirror.

Populate the mirror

The mirror is populated using the command reprepro -V update maverick. There is a long man page describing all the options and commands available. For our purposes the update command is enough. The final argument is the name of the Codebase from mirror/conf/distributions.

$ pwd
maverick-amd64-bootstrap/mirror

reprepro --noskipold -V update maverick

  Created directory "./db"
  Created directory "./lists"
  ...
  Created directory "./pool"
  Created directory "./pool/main"
  Created directory "./pool/main/a"
  Created directory "./pool/main/a/adduser"
  Created directory "./pool/main/a/apt"
  ...
  Created directory "./pool/main/x/xkeyboard-config"
  Created directory "./pool/main/z"
  Created directory "./pool/main/z/zlib"
  Getting packages...
  aptmethod got 'http://...ubuntu.com/.../pool/main/.../adduser_3.110ubuntu6.dsc'
  aptmethod got 'http://...ubuntu.com/.../pool/main/.../adduser_3.110ubuntu6.tar.gz'
  ...
  Shutting down aptmethods...
  Installing (and possibly deleting) packages...
  Exporting indices...
  Created directory "./dists"
  Created directory "./dists/maverick"
  Created directory "./dists/maverick/main"
  Created directory "./dists/maverick/main/binary-amd64"
  Created directory "./dists/maverick/main/source"

Grab the updates as well, using reprepro -V update maverick-updates.

$ pwd
maverick-amd64-bootstrap/mirror

reprepro --noskipold -V update maverick-updates

cd ..
$ pwd
maverick-amd64-bootstrap

The mirror is now ready, and can be accessed using a relative file URL within the maverick-amd64-bootstrap directory:

file://`pwd`/mirror

Note if packages are missing later, it is probably an issue with one of the above steps.

Create the KVM disk

The next step is to create a 1000 MiB disk to hold the guest. I wanted to fully fit the image, and clearly define it's size as my VPS host allows for the definition of disks in MiBs.

To do this without wasting space in the partitions required a dive into the history of computers and the concepts around the master boot record (MBR).

Background:

Create 1000 MiB disk (with 1 MiB used for the MBR + gap), and 60 MiB will be used for /boot leaving the remaining 939 MiB used for /. The reason for splitting /boot into its own partition is that I would like to use pv_grub on my Host which then introduces special requirements about what drive contains /boot/grub/menu.lst.

Steps Overview:

  1. Create the disk image
  2. Create the partition table on the disk (see: parted mklabel)
  3. Create the partitions (the start and end numbers are in MB by default)
  4. Mount and format the disks

Given:

  • Sectors are numbered from 1
  • Sectors are 512B
  • The first partition starts in the last sector of the first track (2048).
  • Each partitions data starts in it second sector
Disk image size in sectors = 1000*1024*1024/512B/sec = 2048000 sec

MBR and gap
  start 1
  size  1*1024*1024 B / 512B/sector = 2048
  ------------------------------------------------
  end   2047


/boot
  start 2048
  size  60*1024*1024 B / 512B/sector = 122880
  -------------------------------------------------
  end   124927


/
  start 124928
  size  938*1024*1024 B / 512B/sector = 1923072
  -------------------------------------------------
  end   2047999

cleaned up
mount    start               end
-----    ------------------  ------------------
/boot    2048                124927
/        124928              2047999

OK armed with the sector positions create the disk and partition it using parted.

$ pwd
maverick-amd64-bootstrap

dd if=/dev/zero of=ubuntu-maverick-amd64.img bs=512 count=2048000
  2048000+0 records in
  2048000+0 records out
  1048576000 bytes (1.0 GB) copied

parted -s ubuntu-maverick-amd64.img mklabel msdos

Verify the disk size in sectors (note each sector is 512 bytes).

$ pwd
maverick-amd64-bootstrap

parted -s ubuntu-maverick-amd64.img unit s print

  Model:  (file)
  Disk .../ubuntu-maverick-amd64.img: 2048000s
  Sector size (logical/physical): 512B/512B
  Partition Table: msdos

  Number  Start  End  Size  Type  File system  Flags
  <none>

Create the first partition starting at sector 2048 (which includes the 512B from sector 2048), and ending 60 MiB further. See calculations from above.

$ pwd
maverick-amd64-bootstrap

parted ubuntu-maverick-amd64.img unit s mkpart primary 2048 124927
parted ubuntu-maverick-amd64.img unit s mkpart primary 124928 2047999

Note the ending sector of the first partition plus one is used as the starting point for the second partition.

Verify the partition sizes.

$ pwd
maverick-amd64-bootstrap

parted ubuntu-maverick-amd64.img unit b print
  Model:  (file)
  Disk .../maverick-amd64-bootstrap/ubuntu-maverick-amd64.img: 1048576000B
  Sector size (logical/physical): 512B/512B
  Partition Table: msdos

  Number  Start      End          Size        Type     File system  Flags
   1      1048576B   63963135B    62914560B   primary
   2      63963136B  1048575999B  984612864B  primary

Verify the sizes.

$ irb

  irb(main):001:0> 62914560/1024/1024.0
  => 60.0
  irb(main):002:0> 984612864/1024/1024.0
  => 939.0

  irb(main):003:0> 1048576000/1024/1024.0
  => 1000.0

The partitions are correct. Finally enable the first partitions boot flag.

$ pwd
maverick-amd64-bootstrap

parted ubuntu-maverick-amd64.img set 1 boot on

Mount the disks using a loopback device

I'm going to use /dev/loop5 to mount the image as it is normally available and unused.

NOTE: The next few steps use root privileges.

$ pwd
maverick-amd64-bootstrap

sudo losetup /dev/loop5 ubuntu-maverick-amd64.img

Check the disk using fdisk you can see that the disk starts at the first cylinder. Note the boot flag is on for partition 1.

$ pwd
maverick-amd64-bootstrap

sudo fdisk -lu /dev/loop5
  Disk /dev/loop5: 1048 MB, 1048576000 bytes
  4 heads, 32 sectors/track, 16000 cylinders, total 2048000 sectors
  Units = sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disk identifier: 0x000b1220

        Device Boot      Start         End      Blocks   Id  System
  /dev/loop5p1   *        2048      124927       61440   83  Linux
  /dev/loop5p2          124928     2047999      961536   83  Linux

Next Create the file systems on the disk

The trick to creating the file systems is to use kpartx to dynamically create devices for the two partitions on the disk.

In this step format the partitions and label them so that they can be mapped by LABEL in /etc/fstab.

$ pwd
maverick-amd64-bootstrap

sudo kpartx -l /dev/loop5
  loop5p1 : 0 122880 /dev/loop5 2048
  loop5p2 : 0 1923072 /dev/loop5 124928

sudo kpartx -a /dev/loop5
ls -l /dev/mapper/loop5*
  brw-rw---- 1 root disk 252, 0 2010-04-10 17:39 /dev/mapper/loop5p1
  brw-rw---- 1 root disk 252, 1 2010-04-10 17:39 /dev/mapper/loop5p2

sudo mkfs.ext2 -m 0 /dev/mapper/loop5p1
sudo tune2fs /dev/mapper/loop5p1 -L 'boot'

sudo mkfs.ext3 /dev/mapper/loop5p2
sudo tune2fs /dev/mapper/loop5p2 -L 'root'

Now we have a disk image ready to install Ubuntu onto.

Time to run debootstrap and install Maverick

Starting from maverick-amd64-bootstrap, make sure the the two partitions on ubuntu-maverick-amd64.img are available via /dev/mapper.

$ pwd
maverick-amd64-bootstrap

sudo losetup -a
  /dev/loop5: [0806]:3420728 (.../maverick-amd64-bootst*)

ls -l /dev/mapper/loop5*
  brw-rw---- 1 root disk 252, 0 2010-04-10 17:41 /dev/mapper/loop5p1
  brw-rw---- 1 root disk 252, 1 2010-04-10 21:32 /dev/mapper/loop5p2

OK now let's mount those partitions under maverick-amd64-bootstrap in a directory maverick-vm and install via deboostrap.

$ pwd
maverick-amd64-bootstrap

mkdir -p maverick-vm
sudo mount /dev/mapper/loop5p2 maverick-vm
sudo mkdir -p maverick-vm/boot
sudo mount /dev/mapper/loop5p1 maverick-vm/boot

Now run debootstrap to install Ubuntu. Note you can be off line a this point as the mirror will be used to source packages.

$ pwd
maverick-amd64-bootstrap

sudo debootstrap --arch=amd64 maverick maverick-vm file://`pwd`/mirror
  I: Retrieving Release
  I: Retrieving Packages
  I: Validating Packages
  I: Resolving dependencies of required packages...
  I: Resolving dependencies of base packages...
  I: Checking component main on file:///...maverick-amd64-bootstrap/mirror...
  ...
  I: Configuring initramfs-tools...
  I: Base system installed successfully.
  • Fantastic if it worked.

Dealing with issues

First check the debootstrap log. Here is an example of one failure I had.

$ pwd
maverick-amd64-bootstrap

cat maverick-vm/debootstrap/debootstrap.log 
chroot: cannot run command `/sbin/ldconfig': No such file or directory

I ultimately tracked this down to bash missing in the debootrap installation list.

Some commands to help rewind the process, if you need to.

$ pwd
maverick-amd64-bootstrap

sudo umount maverick-vm/boot
sudo umount maverick-vm
kpartx -d /dev/loop5
losetup -d /dev/loop5

Kernel installation

Before starting the VM we need to install the kernel from the mirror. This requires the current hosts /dev to be bound in the vm.

$ pwd
maverick-amd64-bootstrap

sudo mount --bind /dev maverick-vm/dev

mkdir -p maverick-vm/tmp/mirror
sudo mount --bind `pwd`/mirror maverick-vm/tmp/mirror

The mirror and /dev should now be available in the chrooted environment.

Now choose either method A or B.

A) Offline Method Using the Mirror

Backup and switch the apt sources list to the mirror.

$ pwd
maverick-amd64-bootstrap

sudo cp maverick-vm/etc/apt/sources.list maverick-vm/etc/apt/sources.list.bak
sudo chroot maverick-vm /bin/bash -c 'cat > /etc/apt/sources.list<<EOF
deb file:///tmp/mirror maverick main
EOF'
sudo chroot maverick-vm /bin/bash -c 'apt-get -y update'
sudo chroot maverick-vm /bin/bash -c 'apt-get -y --force-yes install aptitude'

Online Method

Switch the mirror to GB otherwise I had issues with the GPG keys...

$ pwd
maverick-amd64-bootstrap

sudo cp maverick-vm/etc/apt/sources.list maverick-vm/etc/apt/sources.list.bak
sudo chroot maverick-vm /bin/bash -c 'cat > /etc/apt/sources.list<<EOF
deb http://gb.archive.ubuntu.com/ubuntu maverick main
EOF'

sudo /bin/bash -c 'cat /etc/resolv.conf > maverick-vm/etc/resolv.conf'

sudo chroot maverick-vm /bin/bash -c 'aptitude update'
  /bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_GB.UTF-8)
  Get:1 http://gb.archive.ubuntu.com maverick Release.gpg [189B]
  Get:2 http://gb.archive.ubuntu.com maverick Release [65.9kB]
  Get:3 http://gb.archive.ubuntu.com maverick/main Packages [1353kB]
  Fetched 1419kB in 18s (75.7kB/s)
  Reading package lists... Done

  Current status: 6436 new [+6436].

Note I used my existing resolv.conf to allow the chrooted environment to get on-line.

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'apt-get -y update'
sudo chroot maverick-vm /bin/bash -c 'apt-get -y --force-yes install aptitude'
sudo chroot maverick-vm /bin/bash -c 'aptitude search linux-image \
        | grep -E "linux-image-(.*)-server"'

  p linux-image-2.6.31-14-server  - Linux kernel image for version 2.6.31 on x86_64

Additional Packages

At this point any other additional packages can be installed.

$ pwd
maverick-amd64-bootstrap

ADDITIONAL_PACKAGES=`cat mirror/additional.packages | tr '\n' ' '`
sudo env PKGS="$ADDITIONAL_PACKAGES" chroot maverick-vm \
    /bin/bash -c 'apt-get -y --force-yes install ${PKGS}'

Kernel Install

And finally install 'linux-image-server', using DEBIAN_FRONTEND non-interactive mode to avoid Grub launching into a text based configuration screen.

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'DEBIAN_FRONTEND=noninteractive \
        apt-get -y --force-yes install linux-image-server'

  Reading package lists... Done
  Building dependency tree
  Reading state information... Done
  Reading extended state information
  Initialising package states... Done
  The following NEW packages will be installed:

    grub-common{a} grub-pc{a} libfreetype6{a} linux-image-2.6.31-14-server
    os-prober{a} wireless-crda{a}

  0 packages upgraded, 6 newly installed, 0 to remove and 0 not upgraded.
  Need to get 30.8MB of archives. After unpacking 119MB will be used.
  Writing extended state information... Done
  Get:1 http://...archive.ubuntu.com maverick/main wireless-crda 1.10
  Get:2 http://...ubuntu.com maverick/main linux-image-2.6.31-14-server 2.6.31-14.48
  ...
  Initializing package states... Done
  Writing extended state information... Done

Clean up

Revert the apt sources list and unbind the mirror.

$ pwd
maverick-amd64-bootstrap

sudo cp maverick-vm/etc/apt/sources.list.bak maverick-vm/etc/apt/sources.list

sudo umount maverick-vm/tmp/mirror
rmdir maverick-vm/tmp/mirror

Leaving on /dev bound.

Configure: fstab, network, hosts, etc.

Here we will create some files containing basic configuration for the VM.

/etc/fstab

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'cat > /etc/fstab<<EOF
# /etc/fstab: static file system information.
#
# Use vol_id --uuid to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>                  <dump>  <pass>
proc            /proc           proc    defaults                   0       0
LABEL=boot      /boot           ext2    relatime                   0       2
LABEL=root      /               ext3    relatime,errors=remount-ro 0       1
EOF'

Guest configuration

In my VM I assign a static IP address because DHCP does not work with my wireless bridge interface.

The following is the minimal number of attributes to set to get the VM up and running and reachable from the network.

KVM_HOSTNAME=vm-001
KVM_DOMAIN=example.com
KVM_FQDN=${KVM_HOSTNAME}.${KVM_DOMAIN}

KVM_SEARCH_DOMAIN="${KVM_DOMAIN}"
KVM_NAME_SERVER=10.20.80.10
KVM_IP_STATIC=10.20.80.20
KVM_IP_NETMASK=255.255.255.0
KVM_IP_GATEWAY=10.20.80.1

/etc/environment

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'cat >> /etc/environment <<EOF
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
LANGUAGE="en_US:en"
LANG="en_US.UTF-8"
EOF'
sudo chroot maverick-vm /bin/bash -c 'locale-gen "en_US.UTF-8"'
sudo chroot maverick-vm /bin/bash -c 'dpkg-reconfigure locales'

/etc/network/interfaces

$ pwd
maverick-amd64-bootstrap

sudo env KVM_IP_STATIC=$KVM_IP_STATIC   \
         KVM_IP_NETMASK=$KVM_IP_NETMASK \
         KVM_IP_GATEWAY=$KVM_IP_GATEWAY \
         sh -c 'cat > maverick-vm/etc/network/interfaces<<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# DHCP eth0
#auto eth0
#iface eth0 inet dhcp

# Static eth0
auto eth0
iface eth0 inet static
      address ${KVM_IP_STATIC}
      netmask ${KVM_IP_NETMASK}
      gateway ${KVM_IP_GATEWAY}
EOF'

cat maverick-vm/etc/network/interfaces

Check that the interfaces file contains the IP addresses you expect.

/etc/resolv.conf

If you use a static IP address you will also need to create a resolve.conf

$ pwd
maverick-amd64-bootstrap

sudo env KVM_DOMAIN=$KVM_DOMAIN \
         KVM_SEARCH_DOMAIN="$KVM_SEARCH_DOMAIN" \
         KVM_NAME_SERVER=$KVM_NAME_SERVER \
         sh -c 'cat > maverick-vm/etc/resolv.conf<<EOF
domain ${KVM_DOMAIN}
search ${KVM_SEARCH_DOMAIN}
nameserver ${KVM_NAME_SERVER}
EOF'

cat maverick-vm/etc/resolv.conf

/etc/hosts

Now assign the host name.

$ pwd
maverick-amd64-bootstrap

sudo env KVM_HOSTNAME=$KVM_HOSTNAME \
            sh -c 'cat > maverick-vm/etc/hostname<<EOF
${KVM_HOSTNAME}
EOF'

sudo env KVM_HOSTNAME=$KVM_HOSTNAME KVM_FQDN=$KVM_FQDN \
            sh -c 'cat > maverick-vm/etc/hosts<<EOF
127.0.0.1 localhost.localdomain localhost
127.0.0.1 ${KVM_FQDN} ${KVM_HOSTNAME}
EOF'

cat maverick-vm/etc/hostname

/etc/apt/apt.conf/99-minimal-vm-config

Don't install extra packages, when installing using aptitude.

sudo sh -c 'cat > /etc/apt/apt.conf.d/99-vm-no-extras-please<<EOF
APT::Install-Recommends "false";
APT::Install-Suggest "false";
EOF'

Grub boot loader

Write the grub configuration to the MBR of the image disk.

$ pwd
maverick-amd64-bootstrap

KVM_INITRD=`(cd maverick-vm/boot && ls init*)`
KVM_KERNEL=`(cd maverick-vm/boot && ls vmlinuz*)`

sudo env KVM_KERNEL=$KVM_KERNEL KVM_INITRD=$KVM_INITRD \
            sh -c 'cat > maverick-vm/boot/grub/menu.lst<<EOF
default 0
timeout 10
title Ubuntu Maverick
root (hd0,0)
kernel /${KVM_KERNEL} root=LABEL=root ro
initrd /${KVM_INITRD}
EOF'

cat maverick-vm/boot/grub/menu.lst

Copy the stage1, stage2, e2fs_stage1_5 Grub files into /boot/grub. These files are part of the grub package.

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'DEBIAN_FRONTEND=noninteractive apt-get \
                                         -y --force-yes install grub'
sudo cp maverick-vm/usr/lib/grub/x86_64-pc/stage1 maverick-vm/boot/grub/
sudo cp maverick-vm/usr/lib/grub/x86_64-pc/stage2 maverick-vm/boot/grub/
sudo cp maverick-vm/usr/lib/grub/x86_64-pc/e2fs_stage1_5 maverick-vm/boot/grub/

ATTENTION:

In order to load the grub configuration into the MBR we must map (hd0) to the disk image ubuntu-maverick-amd64.img. This is to AVOID WRITING OVER our real MBR.

$ pwd
maverick-amd64-bootstrap

sudo grub --device-map=/dev/null <<EOF
device (hd0) ubuntu-maverick-amd64.img
root (hd0,0)
setup (hd0)
quit
EOF
...
  succeeded
    Running "install /grub/stage1 (hd0) (hd0)1+17 p 
                (hd0,0)/grub/stage2 /grub/menu.lst"... succeeded

Preparing to launch for the first time.

Create a very temporary root password that can be used to log into the VM Guest.

$ pwd
maverick-amd64-bootstrap

sudo chroot maverick-vm /bin/bash -c 'echo "root:gotcha12" | chpasswd'

Unmount everything on the temporary host.

$ pwd
maverick-amd64-bootstrap

sudo umount maverick-vm/boot
sudo umount maverick-vm/dev
sudo umount maverick-vm/
sudo kpartx -d /dev/loop5
sudo losetup -d /dev/loop5

Now for the glory! Launching the VM

At this point the network will not work because it needs to be bridged to the host network adaptor. But at least the VM should come up and you should be able to log in as root.

$ pwd
maverick-amd64-bootstrap

kvm -drive file=ubuntu-maverick-amd64.img,index=0,media=disk -m 256m

Check to see if you if the VM can boot, then shut it down and setup the network interface on the host.

Login as root on the VM and run the command poweroff.

login: root
password: gotcha12

$ poweroff

Wireless Bridge for Laptop Hosts (e.g. my dev environment)

Hints:

The idea is for the host to provide tap0 interface to the KVM guest to use for connectivity. I have chosen to bridge my wireless interface to the tap0 interface based on the instructions above.

Before creating a bridge IP forwarding must be enabled.

Is IP forwarding enabled? This can be verified by looking in the /proc file system. Note a value of ip_forward=1 means IP forwarding is enabled.

$ cat /proc/sys/net/ipv4/ip_forward
0

Make Sure IP Forwarding is Enabled

IP forwarding can be turned on temporarily using:

$ sudo sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

This change can be made permanent by changing:

/etc/sysctl.conf
...
  net.ipv4.ip_forward=1

Now setup the bridge/tapped interface.

$ On the HOST

route -n
  Kernel IP routing table
  Destination     Gateway         Netmask         Flags Metric Ref    Use Iface
  10.20.80.0      0.0.0.0         255.255.255.0   U     2      0        0 wlan0
  0.0.0.0         10.20.80.1      0.0.0.0         UG    0      0        0 wlan0

sudo tunctl -u $USER -t tap0
  Set 'tap0' persistent and owned by uid 1000

sudo ip addr add 10.20.80.50 dev tap0
sudo ip link set tap0 up
sudo ip link set tap0 promisc on

/sbin/ifconfig tap0
  tap0      Link encap:Ethernet  HWaddr ce:0a:d7:98:70:14  
            inet addr:10.20.80.50  Bcast:0.0.0.0  Mask:255.255.255.255
            inet6 addr: fe80::cc0a:d7aa:fa23:7014/64 Scope:Link
            UP BROADCAST RUNNING PROMISC MULTICAST  MTU:1500  Metric:1
            RX packets:0 errors:0 dropped:0 overruns:0 frame:0
            TX packets:0 errors:0 dropped:25 overruns:0 carrier:0
            collisions:0 txqueuelen:500 
            RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

sudo route add -net 10.20.80.50 netmask 255.255.255.255 tap0
sudo parprouted -d wlan0 tap0

Now open a new terminal and change back to the directory maverick-amd64-bootstrap.

Note it seems like I always have to ping the Guest once from the host before the host provides access to the network from within the Guest. Running parprouted with -debug helps.

If at all possible connect to another host and see if the address set using ip addr add is reachable. If it's not then the "bridge" is not working.

Now launch the KVM guest with a tapped network: -net tap.ifname.

$ pwd
maverick-amd64-bootstrap

kvm -drive file=ubuntu-maverick-amd64.img,index=0,media=disk \
    -net tap,ifname=tap0,script=no,downscript=no -net nic \
    -m 256m

Now try a few commands in the VM to make sure the network is up.

From the host

$ pwd
maverick-amd64-bootstrap

ping 10.80.20.20

This is get the tap interface working.

From the guest

$ pwd
maverick-amd64-bootstrap

Host
ping 10.80.20.50

Gateway
ping 10.80.20.1

Name server
ping 10.80.20.10

Outside
ping google.com

Aside: Killing off the tapped interface.

sudo ifconfig tap0 down
sudo tunctl -u $USER -d tap0
sudo pkill parprouted

Closing and Next Steps

  1. Change the root password
  2. Configure the VM so that if it auto configures itself against a chef or puppet master