Introduction

Whether you are testing a new clustered application, validating configuration management playbooks or studying for the RHCE, it's convenient to have access to a set of Linux virtual machines. This is the beauty of the cloud. You can launch fresh new instances and off you go.

If you have an host with adequate resources running KVM, you could have the same ability, locally, to spin up a few Linux virtual machines to create your own lab in a box.

We will use a script and some cloud images from CentOS to create virtual machines in just a few seconds.


Prerequisites

This guide assumes you are running a recent version of Fedora with KVM installed. You will also need a few packages:

sudo dnf install libvirt-client virt-install genisoimage


Setting up the Directory

Create the following directory structure:

mkdir -p ~/virt/images

We will put the VM installation script in ~/virt and all the virtual machine image files will go in the ~/virt/images directory.


Installation Script

This script originally came from Marek Goldmann's post. I've since modified it further to fit my needs.

I run the script as my normal user and not as root. This means that I'm using the qemu:///session URI to connect to libvirt. Since libvirt and qemu are new enough on Fedora, a tap device gets created and connected to the default bridge, virbr0, which allows the guests to communicate with each other as they will be on the same subnet.

See the contents of /etc/qemu/bridge.conf and the output of ip address show for the tap networking device.

Now, copy the following script and save it as ~/virt/virt-install-centos.

The script is also available as a gist.

#!/bin/bash

# Take one argument from the commandline: VM name
if ! [ $# -eq 1 ]; then
    echo "Usage: $0 <node-name>"
    exit 1
fi

# Check if domain already exists
virsh dominfo $1 > /dev/null 2>&1
if [ "$?" -eq 0 ]; then
    echo -n "[WARNING] $1 already exists.  "
    read -p "Do you want to overwrite $1 [y/N]? " -r
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        echo ""
    else
        echo -e "\nNot overwriting $1. Exiting..."
        exit 1
    fi
fi

# Directory to store images
DIR=~/virt/images

# Location of cloud image
IMAGE=$DIR/CentOS-7-x86_64-GenericCloud.qcow2

# Amount of RAM in MB
MEM=768

# Number of virtual CPUs
CPUS=1

# Cloud init files
USER_DATA=user-data
META_DATA=meta-data
CI_ISO=$1-cidata.iso
DISK=$1.qcow2

# Bridge for VMs (default on Fedora is virbr0)
BRIDGE=virbr0

# Start clean
rm -rf $DIR/$1
mkdir -p $DIR/$1

pushd $DIR/$1 > /dev/null

    # Create log file
    touch $1.log

    echo "$(date -R) Destroying the $1 domain (if it exists)..."

    # Remove domain with the same name
    virsh destroy $1 >> $1.log 2>&1
    virsh undefine $1 >> $1.log 2>&1

    # cloud-init config: set hostname, remove cloud-init package,
    # and add ssh-key 
    cat > $USER_DATA << _EOF_
#cloud-config

# Hostname management
preserve_hostname: False
hostname: $1
fqdn: $1.example.local

# Remove cloud-init when finished with it
runcmd:
  - [ yum, -y, remove, cloud-init ]

# Configure where output will go
output: 
  all: ">> /var/log/cloud-init.log"

# configure interaction with ssh server
ssh_svcname: ssh
ssh_deletekeys: True
ssh_genkeytypes: ['rsa', 'ecdsa']

# Install my public ssh key to the first user-defined user configured 
# in cloud.cfg in the template (which is centos for CentOS cloud images)
ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBil2QzORhDcnKiVVNpO5daOSYVp8nshcIc7aTEkdlqCRir2Oni8BEStK7x7bvh0jrp9KptlHPeos87fQs//VXEb1FEprL2c6fPWmVdtjmYw3yzSkaFKMksL7FdUoEiwF6t8pQAg2mU0Qj9emSHBKg5ttdGqNoSvXc92k7iOzgauda7jdNak+Dx9dPhR3FJwHMcZSlQHO4cweZcK63bZitxlFkJ/FJdry/TBirDhRcXslOJ3ECU2xiyRXJVPs3VNLjMdOTTAoMmZj+GraUBbQ9VIqe683xe02sM83th5hj2C4gW3qXUoFkNLfKAMRxXLRMEwI3ABFB/AAUhACxyTJp giovanni@throwaway
_EOF_

    echo "instance-id: $1; local-hostname: $1" > $META_DATA

    echo "$(date -R) Copying template image..."
    cp $IMAGE $DISK

    # Create CD-ROM ISO with cloud-init config
    echo "$(date -R) Generating ISO for cloud-init..."
    genisoimage -output $CI_ISO -volid cidata -joliet -r $USER_DATA $META_DATA &>> $1.log

    echo "$(date -R) Installing the domain and adjusting the configuration..."
    echo "[INFO] Installing with the following parameters:"
    echo "virt-install --import --name $1 --ram $MEM --vcpus $CPUS --disk
    $DISK,format=qcow2,bus=virtio --disk $CI_ISO,device=cdrom --network
    bridge=virbr0,model=virtio --os-type=linux --os-variant=rhel6 --noautoconsole"

    virt-install --import --name $1 --ram $MEM --vcpus $CPUS --disk \
    $DISK,format=qcow2,bus=virtio --disk $CI_ISO,device=cdrom --network \
    bridge=virbr0,model=virtio --os-type=linux --os-variant=rhel6 --noautoconsole

    MAC=$(virsh dumpxml $1 | awk -F\' '/mac address/ {print $2}')
    while true
    do
        IP=$(grep -B1 $MAC /var/lib/libvirt/dnsmasq/$BRIDGE.status | head \
             -n 1 | awk '{print $2}' | sed -e s/\"//g -e s/,//)
        if [ "$IP" = "" ]
        then
            sleep 1
        else
            break
        fi
    done

    # Eject cdrom
    echo "$(date -R) Cleaning up cloud-init..."
    virsh change-media $1 hda --eject --config >> $1.log

    # Remove the unnecessary cloud init files
    rm $USER_DATA $CI_ISO

    echo "$(date -R) DONE. SSH to $1 using $IP, with  username 'centos'."

popd > /dev/null

You will generate an SSH key pair if you haven't already done so. Be sure to change the SSH RSA public key in the script to your own.


Getting the Cloud Images

You can find CentOS cloud images at http://cloud.centos.org. Let's download a compressed CentOS 7 cloud image to save time and bandwidth, then decompress it:

cd virt/images
wget http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2.xz
xz --decompress CentOS-7-x86_64-GenericCloud.qcow2.xz

Make sure that the IMAGE variable in the script is pointing to the image:

IMAGE=$DIR/CentOS-7-x86_64-GenericCloud.qcow2


Build VMs

Now, just run the virt-install-centos command and pass it the name of the virtual machine you want to build:

./virt-install-centos testvm1
Sat, 14 May 2016 01:02:04 -0400 Destroying the testvm1 domain (if it exists)...
Sat, 14 May 2016 01:02:04 -0400 Copying template image...
Sat, 14 May 2016 01:02:04 -0400 Generating ISO for cloud-init...
Sat, 14 May 2016 01:02:04 -0400 Installing the domain and adjusting the configuration...
[INFO] Installing with the following parameters:
virt-install --import --name testvm1 --ram 768 --vcpus 1 --disk
    testvm1.qcow2,format=qcow2,bus=virtio --disk testvm1-cidata.iso,device=cdrom --network
    bridge=virbr0,model=virtio --os-type=linux --os-variant=rhel6 --noautoconsole

Starting install...
Creating domain...                                                                |    0 B  00:00:00     
Domain creation completed.
Sat, 14 May 2016 01:02:17 -0400 Cleaning up cloud-init...
Sat, 14 May 2016 01:02:17 -0400 DONE. SSH to testvm1 using 192.168.124.138, with  username 'centos'.

The VM was ready in under 15 seconds. This is fast because the cloud image is small, so copying it to create a new VM is quick. Also, the cloud image is already prebuilt. The cloud-init service runs at boot time, looks for a configuration on a virtual CD-ROM device and finishes the configuration of the VM, like setting up SSH keys, before reaching the login prompt.


Log into the VM

CentOS cloud images have the username centos. Log in as this user to the new VM:

ssh centos@192.168.124.138
The authenticity of host '192.168.124.138 (192.168.124.138)' can't be established.
ECDSA key fingerprint is SHA256:Z2sSRXqyrc6udLHHZN2goh0eZolDmhOU7bpCJiQoaKA.
ECDSA key fingerprint is MD5:57:88:78:4a:bb:d0:01:d2:1b:f3:93:93:39:63:b4:af.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.124.138' (ECDSA) to the list of known hosts.
[centos@testvm1 ~]$ 
[centos@testvm1 ~]$ sudo su -
[root@testvm1 ~]# 


Conclusion

The script uses cloud-init to "inject" a startup configuration to the virtual machine. The image disk expands to 8GB by default. You can expand this manually by shutting down the VM and virt-resize tool, which requires the libguestfs-tools package.

To remove the VM, shut it down and just delete its directory. For example:

virsh shutdown testvm1
rm -rf ~/virt/images/testvm1

The script also works with CentOS 6 and Fedora 23 cloud images. If you are using the latter, you may want to rename the script to virt-install-fedora.

Update (16 Jun 2017): Check out an improved version of the script on GitHub.


Comments

comments powered by Disqus