Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active April 1, 2024 07:38
Show Gist options
  • Save smoser/635897f845f7cb56c0a7ac3018a4f476 to your computer and use it in GitHub Desktop.
Save smoser/635897f845f7cb56c0a7ac3018a4f476 to your computer and use it in GitHub Desktop.
cloud-init ubuntu nocloud example with network config

Boot Ubuntu providing it network config in NoCloud Datasource

Ubuntu images can be booted with an additional disk that provides user-data, meta-data and network configuration. This is described some in the upstream documentation.

But this example allows for more easily tinkering.

See a screencast of this on asciinema.org.

What is here

A brief description of files here.

Usage

Heres an example walking through how to use this.

  • Verify dependencies

    $ ./check-dependencies
    All good. (cloud-localds genisoimage qemu-img qemu-system-x86_64 wget)
    
  • Download an image

    Original images are compressed qcow2. get-image downloads an original image and converts it to 'raw' format. This is not strictly necessary, but booting a compressed qcow2 directly means that all disk reads go through decompression.

    $ ./get-image xenial
    downloading <...>
    dist: xenial-server-cloudimg-amd64-disk1.img
    raw:  xenial-server-cloudimg-amd64.raw
    
  • Build a seed image from provided files

    cloud-localds creates an iso9660 filesystem with expected content. Specifically the filesystem label is 'cidata', and the top level directory has 'meta-data', 'user-data', and 'network-config' files. network-config is optional.

    $ cloud-localds -v --network-config=network-config-v1.yaml \
        seed.img user-data.yaml meta-data.yaml
    wrote seed.img with filesystem=iso9660 and diskformat=raw
    
  • Create a qcow2 disk named 'disk.img' backed by the raw image

    By using a qcow image backed by the raw, we can avoid modifying the original.

    $ qemu-img create -f qcow2 -b xenial-server-cloudimg-amd64.raw disk.img
    
  • Boot the image

    By default, boot will run qemu in vga graphics mode, and create a window on your system. It will boot to a login prompt and you can login with username ubuntu and password passw0rd.

    I like to boot with qemu's -nographic option, which will display the full boot in a terminal which is better for demos. See a full boot log here

    Some things to look at once you've logged in

    $ cat /run/cloud-init/result.json
    {
     "v1": {
      "datasource": "DataSourceNoCloud [seed=/dev/sr0][dsmode=net]",
      "errors": []
     }
    }
    
    $ grep -v '^[#]' /etc/network/interfaces.d/50-cloud-init.cfg
    auto lo
    iface lo inet loopback
    
    auto interface0
    iface interface0 inet static
        address 192.168.1.10/24
        gateway 192.168.1.254
    
    $ grep INFO /var/log/cloud-init.log 
    2017-11-03 18:10:18,785 - stages.py[INFO]: Loaded datasource DataSourceNoCloud - DataSourceNoCloud [seed=/dev/sr0][dsmode=net]
    2017-11-03 18:10:18,964 - stages.py[INFO]: Applying network configuration from ds bringup=False: {'config': [{'type': 'physical', 'mac_address': '52:54:00:12:34:00', 'name': 'interface0', 'subnets': [{'netmask': '255.255.255.0', 'gateway': '192.168.1.254', 'type': 'static', 'address': '192.168.1.10'}]}], 'version': 1}
    2017-11-03 18:10:22,954 - cc_apt_configure.py[INFO]: No custom template provided, fall back to builtin
    
#!/bin/sh
Usage() {
cat <<EOF
Usage: ${0##*/} disk.img seed.img [qemu [additional [args]]]
EOF
}
fail() { echo "$@" 1>&2; exit 1; }
set -f
[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit; }
[ $# -ge 2 ] || { Usage 1>&2; exit 1; }
disk=$1
seed=$2
shift 2
[ "$1" = "--" ] && shift
[ -f "$disk" ] || fail "$disk: not a file"
# provide 'format=' correctly so qemu doesnt complain. (qcow2 or raw)
fmt=$(qemu-img info "$disk" | awk -F: '$1 == "file format" { print $2 }')
fmt=${fmt# }
# add -m 768 if no '-m' present.
args=" $* "
[ "${args##* -m}" = "-m" ] || set -- "$@" -m 768
MAC=${MAC:-52:54:00:12:34:00}
set -- \
qemu-system-x86_64 -enable-kvm \
-netdev type=user,id=net00 \
-device virtio-net-pci,netdev=net00,mac=${MAC} \
-drive file=$disk,id=bootdisk,if=none,format=$fmt,index=0 \
-device virtio-blk,drive=bootdisk \
-drive file=$seed,id=seed,if=none,format=raw,index=1 \
-device virtio-blk,drive=seed \
"$@"
echo "$@" 1>&2
"$@"
# vi: ts=4 expandtab
#!/bin/sh
MISSING=""
FOUND=""
checkdep() {
local exe="$1" package="$2" upstream="$3"
if command -v "$1" >/dev/null 2>&1; then
FOUND="${FOUND:+${FOUND} }$exe"
return "0"
fi
MISSING=${MISSING:+${MISSING }$package}
echo "missing $exe."
echo " It can be installed in package: $package"
[ -n "$upstream" ] &&
echo " Upstream project url: $upstream"
return 1
}
checkdep cloud-localds cloud-image-utils http://launchpad.net/cloud-utils
checkdep genisoimage genisoimage
checkdep qemu-img qemu-utils http://qemu.org/
checkdep qemu-system-x86_64 qemu-system-x86 http://qemu.org/
checkdep wget wget
if [ -n "$MISSING" ]; then
echo
[ -n "${FOUND}" ] && echo "found: ${FOUND}"
echo "install missing deps with:"
echo " apt-get update && apt-get install ${MISSING}"
else
echo "All good. (${FOUND})"
fi
# vi: ts=4 expandtab
#!/bin/sh
Usage() {
cat <<EOF
Usage: ${0##*/} release
release is like 'xenial' or 'artful'.
- Downloads an image from cloud-image.ubuntu.com
- converts it to raw format (from qcow2)
EOF
}
fail() { echo "$@" 1>&2; exit 1; }
rel="$1"
[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; }
[ -n "$rel" ] || { Usage 1>&2; fail "Must give release"; }
arch="amd64"
burl="${_PROTO:-https}://cloud-images.ubuntu.com/daily/server"
fname=$rel-server-cloudimg-amd64.img
ofname="$fname"
# trusty and xenial have a '-disk1.img' while
# other releases have just 'disk.img'
case "$rel" in
precise|trusty|xenial) ofname="$rel-server-cloudimg-amd64-disk1.img";;
esac
raw="${fname%.img}.raw"
if [ ! -f "$fname" ]; then
url="$burl/$rel/current/$ofname"
echo "downloading $url to $fname"
wget "$url" -O "$fname.tmp" &&
mv "$fname.tmp" "$fname" || exit
rm -f "$raw"
fi
if [ ! -f "$raw" ]; then
echo "converting $fname to raw in $raw"
qemu-img convert -O raw "$fname" "$raw.tmp" &&
mv "$raw.tmp" "$raw" || exit
rm -f "$pfname"
fi
echo "dist: $ofname"
echo "raw: $raw"
# vi: ts=4 expandtab
instance-id: 506f2788-9741-4ed8-af53-c6a21383d09a
version: 1
config:
- type: physical
name: interface0
mac_address: "52:54:00:12:34:00"
subnets:
- type: static
address: 192.168.1.10
netmask: 255.255.255.0
gateway: 192.168.1.254
version: 2
ethernets:
interface0:
match:
mac_address: "52:54:00:12:34:00"
set-name: interface0
addresses:
- 192.168.1.10/255.255.255.0
gateway4: 192.168.1.254
#cloud-config
password: passw0rd
chpasswd: { expire: False }
ssh_pwauth: True
@jamesbeedy
Copy link

@smoser this is great! thank you!

@brennancheung
Copy link

Thank you! I wish I found this earlier. It's been nearly 2 days trying to get cloud-init working on my local Ubuntu box with kvm. It finally works. I didn't know I had to put networking in a different section and when building the seed.img.

@dcdh
Copy link

dcdh commented Dec 9, 2020

It helps me a lot. Many thanks !

@zogness
Copy link

zogness commented Apr 26, 2022

qemu-img create -f qcow2 -b xenial-server-cloudimg-amd64.raw disk.img
I beg your pardon but why would you name the qcow output file "disk.img" when you're explicitly creating a qcow image file?

@electropolis
Copy link

network config doesn't work
RuntimeError: Unknown network config version: None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment