Commit 456684c9 authored by Alvaro Hernandez's avatar Alvaro Hernandez
Browse files

[202012-firecracker_cloud_image_automation]



Source code for new blog post based on automation to launch
Firecracker-powered VMs, using standard Ubuntu Cloud images, and that
are initialized via cloud-init to provide a user with sudo that can ssh
to the VMs.
Signed-off-by: Alvaro Hernandez's avatarÁlvaro Hernández <aht@ongres.com>
parent 4233b334
......@@ -5,3 +5,6 @@ target/
.project
.classpath
.settings/
202012-firecracker_cloud_image_automation/keypairs/
202012-firecracker_cloud_image_automation/disks/
202012-firecracker_cloud_image_automation/images/
#!/bin/bash
source variables
sudo ip link add name $FIRECRACKER_BRIDGE type bridge
sudo ip addr add $VMS_NETWORK_PREFIX.1/24 dev $FIRECRACKER_BRIDGE
sudo ip link set dev $FIRECRACKER_BRIDGE up
sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null
sudo iptables --table nat --append POSTROUTING --out-interface $EGRESS_IFACE -j MASQUERADE
sudo iptables --insert FORWARD --in-interface $FIRECRACKER_BRIDGE -j ACCEPT
#!/bin/bash
source variables
mkdir -p $KEYPAIR_DIR
ssh-keygen -t ed25519 -q -N "" -f $KEYPAIR_DIR/$DEFAULT_KP
#!/bin/bash
## This script downloads and generates a suitable ext4 image from existing cloud
## images. For simplicity it currently only downloads from Ubuntu images, but it
## should not be a big effort to adapt to other cloud images.
source variables
function download() {
echo "Downloading $2..."
curl -s -o $1 $2
}
function download_if_not_present() {
[ -f $1 ] || download $1 $2
}
function generate_image() {
echo "Generating $IMAGE_ROOTFS..."
truncate -s $IMAGE_SIZE $IMAGE_ROOTFS
mkfs.ext4 $IMAGE_ROOTFS > /dev/null 2>&1
local tmppath=/tmp/.$RANDOM-$RANDOM
mkdir $tmppath
sudo mount $IMAGE_ROOTFS -o loop $tmppath
sudo tar -xf images/$UBUNTU_VERSION/download/$image_tar --directory $tmppath
sudo umount $tmppath
rmdir $tmppath
}
function extract_vmlinux() {
echo "Extracting vmlinux to $KERNEL_IMAGE..."
local extract_linux=/tmp/.$RANDOM-$RANDOM
curl -s -o $extract_linux https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux
chmod +x $extract_linux
$extract_linux images/$UBUNTU_VERSION/download/$kernel > $KERNEL_IMAGE
rm $extract_linux
}
# Download components
mkdir -p images/$UBUNTU_VERSION/download
image_tar=$UBUNTU_VERSION-server-cloudimg-amd64-root.tar.xz
download_if_not_present \
images/$UBUNTU_VERSION/download/$image_tar \
https://cloud-images.ubuntu.com/$UBUNTU_VERSION/current/$image_tar
kernel=$UBUNTU_VERSION-server-cloudimg-amd64-vmlinuz-generic
download_if_not_present \
images/$UBUNTU_VERSION/download/$kernel \
https://cloud-images.ubuntu.com/$UBUNTU_VERSION/current/unpacked/$kernel
initrd=$UBUNTU_VERSION-server-cloudimg-amd64-initrd-generic
download_if_not_present \
images/$UBUNTU_VERSION/download/$initrd \
https://cloud-images.ubuntu.com/$UBUNTU_VERSION/current/unpacked/$initrd
# Generate image, kernel and link initrd
[ -f $IMAGE_ROOTFS ] || generate_image
[ -f $INITRD ] || ln -s download/$initrd $INITRD
[ -f $KERNEL_IMAGE ] || extract_vmlinux
#!/bin/bash
source variables
function curl_args() {
curl --unix-socket $socket \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
$*
}
function firecracker_http_file() {
curl_args -X $1 'http://localhost/'$2 --data-binary "@"$3
}
function create_tap() {
local device=$1
ip addr show $device > /dev/null 2>&1
if [ $? -ne 0 ]
then
sudo ip tuntap add dev $device mode tap
sudo ip link set dev $device up
fi
}
function create_vm_taps() {
local tap_metadata=$1
local tap_main=$2
create_tap $tap_metadata
create_tap $tap_main
sudo ip link set $tap_main master $FIRECRACKER_BRIDGE
}
function script_exit() {
echo -ne "\n\t$1\n\n" >&2
exit 1
}
function launch_vm() {
local instance_number=$1
local tmpfile=/tmp/.$RANDOM-$RANDOM
local socket=$FIRECRACKER_SOCKET.$instance_number
local instance_id="id-"$RANDOM-$RANDOM
# Start firecracker daemon
(
rm -f $socket
$FIRECRACKER --api-sock $socket &> /dev/null &
pid=$!
mkdir -p $FIRECRACKER_PID_DIR
echo $pid > $FIRECRACKER_PID_DIR/$pid
)
# Wait for API server to start
while [ ! -e $socket ]; do
sleep 0.1s
done
# VM config
firecracker_http_file PUT 'machine-config' conf/firecracker/instance-config.json
# Drives
mkdir -p disks
root_fs="./disks/"$( basename $IMAGE_ROOTFS).$instance_id
cp $IMAGE_ROOTFS $root_fs
cat conf/firecracker/drives.json | \
./tmpl.sh __ROOT_FS__ $root_fs \
> $tmpfile
firecracker_http_file PUT 'drives/rootfs' $tmpfile
# Networking
tap_number_base=$(( ($instance_number - 1) * 2 ))
tap_metadata="tap"`printf "%02d" $tap_number_base`
tap_main="tap"`printf "%02d" $(( $tap_number_base + 1 ))`
create_vm_taps $tap_metadata $tap_main
cat conf/firecracker/network_interfaces.eth0.json | \
./tmpl.sh __TAP_METADATA__ $tap_metadata \
> $tmpfile
firecracker_http_file PUT 'network-interfaces/eth0' $tmpfile
mac_octet=`printf '%02x' $(( $instance_number + 1 ))`
cat conf/firecracker/network_interfaces.eth1.json | \
./tmpl.sh __MAC_OCTET__ $mac_octet | \
./tmpl.sh __TAP_MAIN__ $tap_main \
> $tmpfile
firecracker_http_file PUT 'network-interfaces/eth1' $tmpfile
# Boot configuration
instance_ip=$VMS_NETWORK_PREFIX"."$(( $instance_number + 1 ))
network_config_base64=$( \
cat conf/cloud-init/network_config.yaml | \
./tmpl.sh __INSTANCE_IP__ $instance_ip | \
./tmpl.sh __MAC_OCTET__ $mac_octet | \
./tmpl.sh __GATEWAY__ $VMS_NETWORK_PREFIX".1" | \
gzip --stdout - | \
base64 -w 0
)
cat conf/firecracker/boot-source.json | \
./tmpl.sh __KERNEL_IMAGE__ $KERNEL_IMAGE | \
./tmpl.sh __INSTANCE_ID__ $instance_id | \
./tmpl.sh __NETWORK_CONFIG__ $network_config_base64 | \
./tmpl.sh __INITRD__ $INITRD \
> $tmpfile
firecracker_http_file PUT 'boot-source' $tmpfile
# Metadata
cat conf/cloud-init/meta-data.yaml | \
./tmpl.sh __INSTANCE_ID__ $instance_id | \
./tmpl.sh __HOSTNAME__ $instance_id | \
jq --raw-input --slurp '{ "latest": { "meta-data": . }}' \
> $tmpfile
firecracker_http_file PUT 'mmds' $tmpfile
# User data
cat conf/cloud-init/user-data.yaml | \
./tmpl.sh __SSH_PUB_KEY__ "`cat $KEYPAIR_DIR/$DEFAULT_KP.pub`" | \
jq --raw-input --slurp '{ "latest": { "user-data": . }}' \
> $tmpfile
firecracker_http_file PATCH 'mmds' $tmpfile
# Cleanup
rm $tmpfile
# Start VM
firecracker_http_file PUT 'actions' conf/firecracker/instance-start.json
[ $? -eq 0 ] && echo "Instace $instance_id started. SSH with ssh -i $KEYPAIR_DIR/$DEFAULT_KP fc@$instance_ip"
}
# Main
for i in `seq 1 $NUMBER_VMS`
do
launch_vm $i
done
#!/bin/bash
source variables
for i in `find $FIRECRACKER_PID_DIR -type f`
do
kill `cat $i`
rm $i
done
#!/bin/bash
source variables
instance_count=0
for i in `seq 0 $(( $NUMBER_VMS - 1 ))`
do
tap_metadata_id=`printf "%02d" $(( $i * 2 ))`
tap_main_id=`printf "%02d" $(( $i * 2 + 1 ))`
sudo ip link delete tap${tap_metadata_id}
sudo ip link delete tap${tap_main_id}
done
sudo ip link delete $FIRECRACKER_BRIDGE
rm -rf disks
rm -rf images
rm -rf keypairs
# Firecracker automation for cloud images + initialization via cloud-init
This directory contains some scripts to help automate the process of creating
virtual machines with [Firecracker](https://firecracker-microvm.github.io/).
Regular cloud images can be used, the scripts present prepare them for usage
with Firecracker. Finally, basic instance initialization is performed via
cloud-init.
Edit the `variables` file to suit your needs.
Follow the scripts in the specified order. Some basic Linux CLI tools are
required like `curl`, `jq`, `tar` with `xz` support, `ssh-keygen`, `iptables`
and possibly others.
A Linux bridge will be created in the host and will be NATTed to the main
Internet egress device on the host to give connectivity to the created VMs,
which should also be able to connect among them. Default network range for the
created VMs is `172.26.0.0/24`.
This is not a proper software project, it's just for demonstrating purposes.
Modify scripts at your convenience. It has only been tested with `bionic`
[Ubuntu cloud images](https://cloud-images.ubuntu.com/), but in principle should
work with minor modifications with any Linux cloud image that supports
initialization via `cloud-init`.
instance-id: __INSTANCE_ID__
local-hostname: __HOSTNAME__
version: 2
ethernets:
eth0:
match:
macaddress: "AA:FF:00:00:00:01"
addresses:
- 169.254.0.1/16
eth1:
match:
macaddress: "EE:00:00:00:00:__MAC_OCTET__"
addresses:
- __INSTANCE_IP__/24
gateway4: __GATEWAY__
nameservers:
addresses: [ 8.8.4.4, 8.8.8.8 ]
#cloud-config
users:
- name: fc
gecos: Firecracker user
shell: /bin/bash
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- __SSH_PUB_KEY__
{
"kernel_image_path": "__KERNEL_IMAGE__",
"boot_args": "reboot=k panic=1 pci=off ds=nocloud-net;s=http://169.254.169.254/latest/ network-config=__NETWORK_CONFIG__",
"initrd_path": "__INITRD__"
}
{
"drive_id": "rootfs",
"path_on_host": "__ROOT_FS__",
"is_root_device": true,
"is_read_only": false
}
{
"vcpu_count": 2,
"mem_size_mib": 1024,
"ht_enabled": false
}
{
"iface_id": "eth0",
"guest_mac": "AA:FF:00:00:00:01",
"host_dev_name": "__TAP_METADATA__",
"allow_mmds_requests": true
}
{
"iface_id": "eth1",
"guest_mac": "EE:00:00:00:00:__MAC_OCTET__",
"host_dev_name": "__TAP_MAIN__"
}
#!/bin/bash
# This script helps "render templates" of configuration files replacing variables
sed 's?'$1'?'"$2"'?' <&0
NUMBER_VMS=4
UBUNTU_VERSION=bionic
FIRECRACKER=`which firecracker`
FIRECRACKER_PID_DIR=/tmp/.firecracker
FIRECRACKER_SOCKET=/tmp/.firecracker.socket
FIRECRACKER_BRIDGE=fcbr0
EGRESS_IFACE=`ip route get 8.8.8.8 |grep uid |sed 's/.* dev \([^ ]*\) .*/\1/'` # or hardcode to specific device if wanted
VMS_NETWORK_PREFIX=172.26.0 # for scripts simplicity, it is always assumed here a /24 network
IMAGE_SIZE=8G
IMAGE_ROOTFS=images/$UBUNTU_VERSION/$UBUNTU_VERSION.rootfs
KERNEL_IMAGE=images/$UBUNTU_VERSION/$UBUNTU_VERSION.vmlinux
INITRD=images/$UBUNTU_VERSION/$UBUNTU_VERSION.initrd
KEYPAIR_DIR=keypairs
DEFAULT_KP=kp
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment