As a developer, I had a need to spin up VMs as and when required. The more I started working on Kubernetes, the need to automate the spinning up of multiple VMs to bring up a setup for configuring a cluster. There were several options, solutions already available to automate the same on KVM. However, I had decided to quickly develop simple shell scripts which will help me achieve the same.
One of the needs was to delete any VM (domain) if already running with the same hostname and delete the associated image.
#!/bin/bash
if [ $# -lt 1 ]; then
echo "ERROR : hostname not specified"
exit 1
fi
info=`virsh list --all | grep "$1"`
if [ -z "$info" ]; then
# Not running - nothing to do
exit 0
fi
status=`echo $info | tr -s ' ' | cut -d" " -f3-`
echo " "
echo "Status : $status"
echo " "
if [ "$status" == "running" ]; then
virsh destroy $1
fi
virsh undefine $1
rm -rf /var/lib/libvirt/images/$1
rm -rf /opt/kvmmanager/templates/$1
Script for launching VM. M
#!/bin/bash
# Assign defaults to variables
hostname="node"
storage="100G"
ram=12288
ip=""
gw="10.0.0.1"
ns="10.0.0.1"
cpus=4
os="centos"
version="7.0"
domain="datachronicles.net"
bridge="br2"
flavor=""
variant=""
# Parse the values passed in the command line
while getopts "h:s:r:i:g:n:c:o:v:d:b:f:" option; do
case "${option}" in
h)hostname=${OPTARG};;
s)storage=${OPTARG};;
r)ram=${OPTARG};;
i)ip=${OPTARG};;
g)gw=${OPTARG};;
n)ns=${OPTARG};;
c)cpus=${OPTARG};;
o)os=${OPTARG};;
v)version=${OPTARG};;
d)domain=${OPTARG};;
b)bridge=${OPTARG};;
f)flavor=${OPTARG};;
esac
done
# If IP is not provided then print usage and exit
if [ -z "$ip" ]; then
echo "Usage : $0 -i <ipaddr> [-h <hostname>][-s <disksize>] [[-r <ram>][-c <cpus>] || [ -f <micro/mini/small/medium/large> ]] [-g <gateway>][-n <nameserver>][-o <os-distro>][-v <distro-version>][-d <domain>][-b <bridge-if>]"
echo " "
echo "Mandatory "
echo "----------"
echo " "
echo " IP Address: Should be 'dhcp' or IP-Address in CIDR format"
echo " "
echo "Example values"
echo "--------------"
echo " "
echo "Disk size : Example 20G or 40G"
echo " "
echo "Bridge interface : Host bridge name interface to be used"
echo " "
echo "Memory (in MB) : Example 16384"
echo " "
echo "OS-Distro : debian or ubuntu. Debaian versions - 11server, 11Mate, Ubuntu versions - 20.04, Centos - 7.0
echo " "
echo "Default values"
echo "--------------"
echo " "
echo " Hostname: node, Domain: datachronicles.net"
echo " CPUs: 4, RAM :16384, Disk size: 100G, Bridge Interface: br2"
echo " Gateway: 10.0.0.1, Nameserver: 10.0.0.1"
echo " OS-Distro: ubuntu, Version: 20.04"
echo " "
echo "Flavors"
echo "-------"
echo " micro - 1 vCPU and 3 GB RAM"
echo " mini - 2 vCPU and 6 GB RAM"
echo " small - 4 vCPU and 12 GB RAM"
echo " large - 8 vCPU and 24 GB RAM"
echo " xlarge - 16 vCPU and 48 GB RAM"
echo " "
exit 1
fi
# If flavor is specified then override values for vCPU and RAM
if [ ! -z "$flavor" ]; then
if [ "$flavor" == "xlarge" ]; then
cpus=16
ram=49152
elif [ "$flavor" == "large" ]; then
cpus=8
ram=24576
elif [ "$flavor" == "small" ]; then
cpus=4
ram=12288
elif [ "$flavor" == "mini" ]; then
cpus=2
ram=6144
else
cpus=1
ram=3072
fi
echo " "
echo "Flavor specified : $flavor. vCPUs = $cpus, RAM = $ram MB"
echo " "
fi
echo " "
echo "----------------------------------------------------------------------------------------"
echo "Processing : $hostname."
echo " "
# If base image is not available - print error message and exit
if [ ! -f /opt/kvmmanager/images/$os/$os$version.qcow2 ]; then
echo "Base image not found."
exit 1
fi
# If VM with same hostname is running then destroy the VM and delete the image associated
# Destroy vm and undefine if already running or shutdown
deletevm $hostname
# Remove old images if any
if [ -d /var/lib/libvirt/images/$hostname ]; then
rm -rf /var/lib/libvirt/images/$hostname
fi
# Clear any old working folders present
if [[ -d /opt/kvmmanager/templates/$hostname ]]; then
rm -rf /opt/kvmmanager/templates/$hostname
fi
# For now variant for Debian is debiantesting, Ubuntu is ubuntu20.04, Centos is centos7
if [ "$os" == "centos" ]; then
variant="centos07"
elif [ "$os" == "debian" ]; then
variant="debiantesting"
elif [ "$os" == "ubuntu" ]; then
variant="ubuntu20.04"
fi
# Create a folder for the VM in /var/lib/libvirt/images
mkdir -p /var/lib/libvirt/images/$hostname
# Create working folder for configurations and copy the templates
mkdir /opt/kvmmanager/templates/$hostname
if [ "$os" == "centos" ]; then
intfile=/opt/kvmmanager/templates/$hostname/ifcfg-eth0
else
intfile=/opt/kvmmanager/templates/$hostname/interfaces
fi
# Update templates with user input
if [ "$ip" == "dhcp" ]; then
if [ "$os" == "centos" ]; then
cp /opt/kvmmanager/templates/if/ifcfg-eth0-dhcp $intfile
else
cp /opt/kvmmanager/templates/if/interfaces-dhcp $intfile
fi
else
if [ "$os" == "centos" ]; then
cp /opt/kvmmanager/templates/if/ifcfg-eth0 $intfile
else
cp /opt/kvmmanager/templates/if/interfaces $intfile
fi
fi
# For centos we need to have ip and prefix identified
iponly=`echo $ip | cut -d"/" -f1`
prefix=`echo $ip | cut -d"/" -f2`
# Populate the configuration files with values provided
if [ "$ip" != "dhcp" ]; then
sed -i 's@{{ip-address}}@'"$ip"'@' $intfile
sed -i 's@{{gateway-ip}}@'"$gw"'@' $intfile
# For centos
sed -i 's@{{ip-only}}@'"$iponly"'@' $intfile
sed -i 's@{{ip-prefix}}@'"$prefix"'@' $intfile
fi
sed -i 's@{{dns-server}}@'"$ns"'@' $intfile
echo " "
# Copy the base image to specific folder created for the VM and create an image
cp /opt/kvmmanager/images/$os/$os$version.qcow2 /var/lib/libvirt/images/$hostname/
qemu-img create -f qcow2 -F qcow2 -o backing_file=/var/lib/libvirt/images/$hostname/$os$version.qcow2 /var/lib/libvirt/images/$hostname/instance.qcow2
# Resize the image to storage size
qemu-img resize /var/lib/libvirt/images/"$hostname"/instance.qcow2 $storage
# Customize the VM with provided inputs
if [ "$os" == "centos" ]; then
virt-customize -a /var/lib/libvirt/images/$hostname/instance.qcow2 --copy-in $intfile:/etc/sysconfig/network-scripts --hostname $hostname.$domain --timezone Asia/Kolkata --firstboot-command '/usr/local/bin/resizedisk'
else
virt-customize -a /var/lib/libvirt/images/$hostname/instance.qcow2 --copy-in $intfile:/etc/network --hostname $hostname.$domain --timezone Asia/Kolkata --firstboot-command '/usr/local/bin/resizedisk'
fi
virt-install --connect qemu:///system --virt-type kvm --name $hostname --ram $ram --vcpus=$cpus --os-type linux --os-variant $variant --disk path=/var/lib/libvirt/images/$hostname/instance.qcow2,format=qcow2 --import --network bridge=$bridge --noautoconsole
virsh autostart $hostname
Finally, a script to read contents of say cluster.txt, with contents related to the metadata of VMs, one per line, in a predefined fixed format
root@blrs2:~/orchestrator# cat cluster.txt
#Host,DiskSize,Memory,CPU,IP,Gateway,DNSServer,BridgeInterface
ansible,100G,40960,8,10.99.3.1/16,10.99.0.1,10.99.9.1,br2
k8snode1,100G,40960,8,10.99.3.2/16,10.99.0.1,10.99.0.1,br3
k8snode2,100G,40960,8,10.99.3.3/16,10.99.0.1,10.99.0.1,br4
k8snode3,100G,40960,8,10.99.3.4/16,10.99.0.1,10.99.0.1,br2
k8snode4,100G,40960,8,10.99.3.5/16,10.99.0.1,10.99.0.1,br3
The cluster.sh script contents
#!/bin/bash
echo " "
if [ ! -f cluster.txt ]; then
echo "cluster.txt not found."
exit 1
fi
lc=`wc -l cluster.txt | grep -v "#" | tr -s ' ' | cut -d" " -f1`
if [[ -z $lc ]]; then
echo "Nothing to orchestrate"
exit 1
fi
if [[ $lc -lt 1 ]]; then
echo "Nothing to orchestrate"
exit 1
fi
while read oneline; do
if [ -z $oneline ]; then
echo " "
else
if [[ $oneline == \#* ]]; then
echo " "
else
host=`echo $oneline | cut -d"," -f1`
disk=`echo $oneline | cut -d"," -f2`
mem=`echo $oneline | cut -d"," -f3`
cpu=`echo $oneline | cut -d"," -f4`
ip=`echo $oneline | cut -d"," -f5`
gw=`echo $oneline | cut -d"," -f6`
ns=`echo $oneline | cut -d"," -f7`
br=`echo $oneline | cut -d"," -f8`
iponly=`echo $ip | cut -d/ -f1`
/root/orchestrator/orchestrate.sh -h $host -s $disk -r $mem -c $cpu -i $ip -g $gw -n $ns -b $br
fi
fi
done < cluster.txt
# Allow VMs to become ready for ssh login - Typically max 20 seconds
echo "Waiting for VMs startup to complete..."
sleep 30
# Typically with ubuntu images, the entry in /etc/hosts for the host is 127.0.1.1 - We need
# to have the ip address assigned
echo "Updating /etc/hosts in VM(s)"
while read oneline; do
if [ -z $oneline ]; then
echo " "
else
if [[ $oneline == \#* ]]; then
echo " "
else
echo " "
host=`echo $oneline | cut -d"," -f1`
ip=`echo $oneline | cut -d"," -f5`
iponly=`echo $ip | cut -d/ -f1`
ssh-keyscan -t rsa $iponly >> /root/.ssh/known_hosts 2>/dev/null;
# In the launched VM - delete 127.0.1.1 entry in /etc/hosts
cmddel="sed -i \"/$host/d\" /etc/hosts"
ssh -n $iponly "$cmddel"
# In the launched VM - add the /etc/host entry with user provided IP and hostname
cmdadd="echo $iponly $host.datachronicles.net $host >> /etc/hosts"
ssh -n $iponly "$cmdadd"
fi
fi
done < cluster.txt