Building cloud images with Ansible

By Lester Wade | August 02, 2013

Introduction

I work for Eucalyptus and spend time in both the private and public cloud.  When working with customers and users THE first roadblock to using the cloud is usually getting an image with some custom bits created, registered and then running.  More often than not this is starting entirely from scratch, in the case of a fresh Eucalyptus install there are no images registered.  The user can either build their first image from scratch or download something from eustore, which is an online catalog for starter images (which can then be edited and bundled).

Fairly often I need to be able to quickly generate a cloud image which contains some custom bits of software and other bits n’ bobs.  This got me thinking.  What about using Ansible to perform an image build? The beautiful thing about Ansible is that its so easy to pick up and that the YAML-formatted playbooks (set of tasks) are so easy to maintain, particularly amongst a group of folk from different technical backgrounds (e.g. a team!). I know for a fact that some of the tools like boxgrinder, kiwi etc. put users off due to the learning curve.  Wouldn’t it be cool if we could walk through image creation in a playbook and make use of particular modules to help us build an image. Maybe write a module for the disk image creation (size, type etc.), use a module to format the image, use another to install grub perhaps? Ultimately we could get a very nice framework of tasks which are interlocked with functional modules to perform the tricky or distro-specific bits.  This could end up really being quite elegant.  Combine this with modules to upload to cloud providers and you have a fully-fledge image orchestration engine.  Wouldn’t that be neat …..

Anyhow, I decided to first write a very simple playbook which is essentially script-like in the nature of its tasks, it isn’t idempotent (at this point) but it does make good use of the ansible chroot plugin, which allows a user to perform actions within a chroot environment without having to rely on some shell and command funkiness.  I focused on RHEL to start with, specifically building an image in a format suitable for Eucalyptus clouds.

Image building (RHEL-based)

Below is the resultant playbook, in short it performs the following steps:

  1. Creates a sparse image file
  2. Gives the image a disk label
  3. Creates an ext3 filesystem on the image
  4. Loopback attaches the image file and mounts it
  5. Installs the base OS (CentOS 6) into this mount point
  6. Sets up some required mountpoints (proc, sys, dev)
  7. Switches to use the chroot plugin
  8. Installs additional packages and configures the rest of the environment as appropriate (add extra stuff here)

This is how it looks, it could do with a sprinkling of with_items and some idempotency and other module usage.

- hosts: local
  connection: local
  tasks:

  - name: Create a disk image
    command: dd if=/dev/zero of=/tmp/myimage.img count=2000000

  - name: Create disk label
    command: /sbin/parted /tmp/myimage.img mklabel msdos

  - name: Create filesystem
    command: /sbin/mkfs.ext3 -F /tmp/myimage.img -L rootdisk

  - name: Find loopback
    shell: losetup -f
    register: loopback

  - name: Loopback attach
    command: losetup ${loopback.stdout} /tmp/myimage.img

  - name: Mount
    command: mount ${loopback.stdout} /mnt

  - name: Install the release RPM
    command: rpm -i --root=/mnt http://mirror.centos.org/centos/6/os/x86_64/Packages/centos-release-6-4.el6.centos.10.x86_64.rpm

  - name: Install packages
    command: yum -y --installroot=/mnt/ groupinstall Base

  - name: Install some extras
    command: yum -y --installroot=/mnt/ install vim openssh-server dhclient curl ntp

  - name: Create mountpoints
    command: mkdir -p /mnt/{proc,etc,dev,var}/{cache,log,lock/rpm}

  - name: Mount proc
    command: mount -t proc none /mnt/proc

  - name: Mount dev
    command: mount -o bind /dev /mnt/dev

- hosts: local-chroot
  user: root
  tasks:

  - name: Change some service states
    service: name={{ item }} enabled=no
    with_items:
    - abrt-ccpp
    - abrt-oops
    - abrtd
    - ip6tables
    - iptables
    - kdump
    - lvm2-monitor
    - ntpd
    - sshd

  - name: Set up network and turn off zeroconf
    template: src=templates/network.j2 dest=/etc/sysconfig/network owner=root group=root

  - name: Template network configuration file
    template: src=templates/ifcfg.j2 dest=/etc/sysconfig/network-scripts/ifcfg-eth0 owner=root group=root

  - name: Template fstab
    template: src=templates/fstab.j2 dest=/etc/fstab owner=root group=root

  - name: Copy EPEL release RPM
    copy: src=files/epel-release.rpm dest=/tmp/epel-release.rpm

  - name: Install EPEL release RPM
    command: yum -y install /tmp/epel-release.rpm

  - name: Install rc.local
    copy: src=files/rc.local dest=/etc/rc.d/rc.local owner=root group=root

  - name: Set permissions
    file: path=/etc/rc.d/rc.local owner=root group=root mode=0755

Notice the use of the chroot plugin, the second play targets this chroot environment. It requires that the mount point be specified in the inventory file, like so:

[local-chroot]
/mnt ansible_connection=chroot

The result is a working image, see here:

[root@emea-demo-01 ~]# euca-describe-instances i-E174437B
 RESERVATION r-10EE3F89 427616426802 default
 INSTANCE i-E174437B emi-376C3CC9 X.X.X.X euca-172-30-53-79.eucalyptus.internal running admin 0 m1.medium 2013-08-02T15:10:08.713Z cluster01 eki-DE1A36B6 eri-BB0C3904 monitoring-disabled X.X.X.X 172.30.53.79 instance-store
 TAG instance i-E174437B euca:node 192.168.250.11

[root@emea-demo-01 ~]# ssh -i creds/eucalyptus/admin/admin.key root@X.X.X.X
 Last login: Fri Aug 2 08:12:03 2013 from Y.Y.Y.Y
 -bash-4.1# hostname
 euca-172-30-53-79.eucalyptus.internal

Anyone can add to this and its easy to wield, it should be easy to add steps to actually bundle and upload the image to AWS/Eucalyptus for registration. I'm hoping that over the coming months I'll get to look into this approach more closely by extending some modules or writing some supporting modules for building images. Time permitting of course :)

You can find this image building playbook here: https://github.com/lwade/ansible-playbooks

EC2 AMI Module

Since we're on the topic of images, its worth mentioning this module.  New for Ansible 1.3 we have an ec2_ami module contributed by Evan Duffield and iAquire.  This module chiefly deals with the ability to bundle an EBS-backed instance into an EBS-backed AMI and register it.  This is analagous to ec2-create-image. You can use it like so:

 
- hosts: local
  tasks:

  - name: provision instance
    local_action: ec2_ami instance_id=i-8431a7c9 wait=yes name=newbundle region=eu-west-1

Here is the resultant image:

IMAGE ami-65435a11 048212016277/newbundle 048212016277 available private [marketplace: 7w73f3vx0zywcfq1izrshkpjl] x86_64 machine aki-71665e05 ebs paravirtual xen
 BLOCKDEVICEMAPPING EBS /dev/sda snap-c17ff5ec 8 false standard 

Get Started with a Eucalyptus AWS-compatible Private Cloud

Try Eucalyptus for free on our machines or yours with our hosted Eucalyptus Community Cloud (ECC) or by downloading our FastStart automated installer.