Something Like Production
Test your AMIs with Docker
Iterative Development of Config Management
I’ve recently begun using Ansible and Packer to bake “Golden Image” AMIs for
AWS. This makes deploying applications with Spinnaker
a breeze, but testing those “BaseAMIs” is difficult. Packer is unforgiving of
failed builds, unless it’s invoked with the fully-interactive --debug
flag it
instantly terminates failed instances (removing the option to debug a
partially-converged filesystem).
Testing Ansible and Packer configs with Docker makes iteration much faster. I avoid the standard Packer overhead of launching an EC2 instance, “waiting for ssh”, shutting down, creating the AMI, and all the cleanup tasks. This can save several minutes right off the bat, and combined with Docker’s filesystem cache iteration becomes much less painful.
The biggest caveat to drop-in testing Configuration Management for AMIs against
Docker containers is that they’re not exactly the same base installations. If I
find an “ubuntu trusty” Packer source_ami
from
https://cloud-images.ubuntu.com/locator/, it’s not going to contain the same
base installation as the image I get from docker pull ubuntu:trusty
.
Turning an AMI into a Docker Image
Docker images are supposed to be lightweight, single-purpose runtimes, with as
little bloat as possible. They don’t need their own installation of grub, or
the linux kernel, or sshd or vim. All of these and more are included in the AMI
distribution, and are managed by our Ansible Playbooks (e.g. cis-ubuntu-ansible
)
To get a Docker Image that matches your source_ami
, let’s start by finding
that AMI’s snapshot-id
. Open your AWS Console and search for the AMI you’d
like to image. The Block Device Mapping for /dev/sda1
shows the EBS Snapshot
containing the root filesystem for the AMI.
Find that snapshot in the AWS Console, right-click and select “Create Volume”. The defaults are probably fine here, just ensure it’s in an Availability Zone where you have an existing EC2 instance.
When the volume goes into state “Available”, you can right-click and select “Attach Volume” to assign it to an existing EC2 instance.
After the volume is attached to the EC2 instance you specified, SSH to the machine and mount the filesystem (/dev/sd* is renamed /dev/xvd* on ubuntu):
1 2 3 4 5 | heph@evenpanther:~$ sudo mount /dev/xvdf1 /mnt/ heph@evenpanther:~$ df -h /mnt Filesystem Size Used Avail Use% Mounted on /dev/xvdf1 7.8G 794M 6.6G 11% /mnt heph@evenpanther:~$ |
From here, /mnt
contains the same content as the root filesystem for the
chosen AMI. You can turn this into a Docker Image with tar
and
docker import
:
1 2 3 4 5 | heph@evenpanther:~$ sudo tar zcpP -C /mnt/ . | docker import - ubuntu:ami-d732f0b7 heph@evenpanther:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu ami-d732f0b7 a0eb6c872499 2 minutes ago 680.8 MB heph@evenpanther:~$ |
You now have a docker container that looks exactly like the AMI image you
started from. Notice it’s significantly larger than ubuntu:trusty
from
dockerhub:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | heph@evenpanther:~$ docker pull ubuntu:trusty trusty: Pulling from library/ubuntu 96c6a1f3c3b0: Pull complete ed40d4bcb313: Pull complete b171f9dbc13b: Pull complete ccfc4df4fbba: Pull complete Digest: sha256:9274d908eb6d9a3784e93290fcc49f3c5618db9e1b0174ee27f9fc75aa3c0fb0 Status: Downloaded newer image for ubuntu:trusty heph@evenpanther:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu ami-d732f0b7 a0eb6c872499 10 minutes ago 680.8 MB ubuntu trusty 0ccb13bf1954 12 days ago 188 MB heph@evenpanther:~$ |
Testing Configuration Management
Now that you have a representative base image to test, you may need to modify your Configuration Management to support execution inside a Docker container.
For ansible, I modified a few tasks to avoid execution in a Docker context, for example:
1 2 3 4 5 6 7 8 9 10 11 12 | - name: Docker doesn't use grub shell: /usr/sbin/update-grub when: ansible_virtualization_type != 'docker' - name: These packages shouldn't be added to Docker Containers apt: name="{{ item }}" state=installed with_items: - linux-image-extra-{{ansible_kernel}} - linux-image-extra-virtual - nfs-common - apparmor when: ansible_virtualization_type != 'docker' |
I install a custom /etc/apt/preferences
to prevent those those packages from
being upgraded by apt-get upgrade
.
1 2 3 4 5 6 7 | Package: libpam-systemd Pin: origin "" Pin-Priority: -1 Package: grub-pc Pin: origin "" Pin-Priority: -1 |
Then I use a simple Dockerfile to test my setup, ansible, and cleanup stages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | FROM ubuntu:ami-d732f0b7 # Prevent apt from trying to upgrade some packages that # don't work inside a docker environment (grub, kernel, etc) ADD bootstrap_files/docker/apt_preferences /etc/apt/preferences # Setup - Installs Ansible and its dependencies ADD bootstrap_files/setup.sh /tmp/ RUN /tmp/setup.sh # Ansible - Test our base-ami site.yml ADD packer/base-ami/site.yml /tmp/site.yml ADD ansible/roles /etc/ansible/roles RUN sudo ansible-playbook -v -M /tmp/ansible/ /tmp/site.yml # Cleanup - Delete logs and cache files ADD bootstrap_files/cleanup.sh /tmp/ RUN /tmp/cleanup.sh CMD ["/bin/bash"] |
From here I can validate my base-ami/site.yml
with docker build
. Any errors
will cause the build to fail, but subsequent runs will skip any successful
stages, allowing quick iteration.
1 2 3 4 5 6 7 8 9 10 | heph@evenpanther:~/packer-factory$ docker build . Sending build context to Docker daemon 548.9 kB Step 1 : FROM ubuntu:ami-d732f0b7 ... Step 10 : CMD /bin/bash ---> Running in 18086e0c8c39 ---> 73ce2146f996 Removing intermediate container 18086e0c8c39 Successfully built 73ce2146f996 heph@evenpanther:~/packer-factory$ |
It’s always a good idea to clean up when you’re done:
1 2 3 | heph@evenpanther:~$ docker rmi 73ce2146f996 Deleted: sha256:73ce214... heph@evenpanther:~$ |