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:
- Creates a sparse image file
- Gives the image a disk label
- Creates an ext3 filesystem on the image
- Loopback attaches the image file and mounts it
- Installs the base OS (CentOS 6) into this mount point
- Sets up some required mountpoints (proc, sys, dev)
- Switches to use the chroot plugin
- 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