Testing Ansible roles against LXC containers

The problem

Testing Ansible roles usually involves a little Vagrant setup, or a Docker based solution which means running a daemon with root priveleges on your machine that has had its fair share of security issues.

Docker is still probably the best approach for Mac-bound folks, but for those running dev envs or CI servers on Linux LXC offers a nice alternative.

The solution

We can actually use a play to spin up our continers and Ansible's dynamic inventory to target them in future plays, meaning that we can use a playbook to set up our tests.

Our playbook will start out as usual, targetting only our test hosts, which will usually be localhost with ansible_connection=local set.

- hosts: test-hosts
  become: true
  become_user: root
    - lxc-test

Now we create the container and store its info into a register.

    - name: Create lxc container
      lxc_container: template=ubuntu state=started name=ansible_test
      register: container_info

We need to install ssh on the container and put our SSH key on it before we can run plays against it (Ansible doesn't have an LXC connection type yet.)

    - name: Ensure SSH daemon installed on lxc machine
        name: ansible_test
        container_command: sudo apt-get install --yes openssh-server python

    - name: Ensure public key is installed
        name: ansible_test
        container_command: |
          ! [ -f /root/.ssh/authorized_keys ] &&
            mkdir -p /root/.ssh/ &&
            echo {{lookup('file', 'files/id_rsa.pub')}} >/root/.ssh/authorized_keys

The container info will get updated with an IP address when the container's network has come up, at which point we can proceed with connecting to it via SSH

    - name: Wait for container network
      when: container_info|changed
      pause: seconds=10

    - name: Get container IP
      lxc_container: name=ansible_test
      register: container_info

Now we can add the host to our inventory.

    - name: Register container in inventory
      add_host: name="{{ container_info.lxc_container.ips.0 }}" group=containers

And finally we can run our roles against it.

- hosts: containers
  user: root
    - common
    - common

This is really nice for quick smoke-testing, and your container can always be reverted back to its initial state with an lxc-destroy -f -n ansible_test.

This can also be used to get a container set up so that Serverspec specs can be run against it, and doing it from Ansible rather than Ruby gives you a way outside your test code to set up a temporary sandbox for playing with your role's finished state when you need to poke around in a shell.