Using Ansible to Launch Instances on OpenStack

Continuous Integration work flows are the most popular use case for OpenStack clouds according to the latest OpenStack User Survey. Ansible is one of the leading tools for driving continuous integration (CI) and the two projects compliment each other nicely. In this article, we’ll walk through the process of deploying a new virtual machine and running an Ansible playbook against it in an OpenStack cloud.

Start simple

Configuration management in Ansible is built around a construct called the "playbook." In its simplest form, a playbook is a YAML file which describes a list of tasks to be performed against a set of hosts, which are defined in the Ansible inventory. A playbook is made up of one or more "plays," which are used to group the tasks.

An example is given below; a simple playbook that has a single play – a simplified version of the webserver example in the Ansible Playbook Introduction.

- hosts: webservers remote_user: centos sudo: yes tasks: - name: ensure apache is at the latest version yum: name=httpd state=latest - name: make sure apache is running service: name=httpd state=running

This playbook tells Ansible to log into the hosts defined as "webservers" in its inventory as the remote user "centos", become the root user via sudo, and then perform two tasks. The first task installs the "http" package and the second task ensures that the "httpd" service is running. Tasks in Ansible are idempotent and when the playbook is run, it will tell you which tasks required a change to the system and which ones didn’t.

To run this first play, manually launch a CentOS image in your OpenStack tenant and assign a floating IP to it. Ensure that you can log into the instance without a password using your SSH key as the "centos" user. Then create a simple hosts file similar to the one below, replacing 192.168.0.10 with your floating IP:

[webservers] 192.168.0.10

Run the example playbook like so:

$ ansible-playbook -i hosts webserver.yml

Where "hosts" is the name of the hosts file that you’ve created and "webserver.yml" is a file containing the playbook defined above. For more information on the structure and format of an Ansible hosts file, see the Ansible Inventory documentation (http://docs.ansible.com/ansible/intro_inventory.html).

You should see that the tasks succeed and that the Apache HTTP Server is installed and running on the instance. If the tasks fail, check that you’ve got your SSH keys configured correctly, the instance can access the Internet to download packages, you have connectivity to the instance, and so on.

Launching an Instance

Now that we’ve tested that we can run a playbook against a pre-provisioned OpenStack instance, we’ll write a new playbook which provisions the instance automatically. We’ll be using the nova_compute module available in Ansible 1.9. Starting with Ansible 2.0, this module has been deprecated in favor of the much improved

os_server module.

The following play demonstrates basic usage of the nova_compute module.

- name: Deploy on OpenStack hosts: localhost gather_facts: false tasks: - name: Deploy an instance nova_compute: state: present login_username: demo login_password: secret login_tenant_name: demo auth_url: http://127.0.0.1:5000/v2.0/ name: webserver image_name: centos-7-x86_64-genericcloud key_name: root wait_for: 200 flavor_id: 2 auto_floating_ip: yes nics: - net-id: fa6af4e6-c44e-439c-a91c-03bcae55e587 meta: hostname: webserver.localdomain

There are a couple of things to note with this example. The first and most important is that every task in an Ansible play needs to run on some host. In this example, we’re specifying that the tasks should be run against "localhost", which means that the virtual instance will be launched using the Python client on the local machine. In Ansible, we refer to this as a "local action". The nova_compute task is typically a local action, but doesn’t have to be. Whichever machine you’re going to run the task on needs to have the Python Nova Client library installed ( python-novaclient on Red Hat distributions).

The second thing to note is that we’re turning off fact gathering (gather_facts = False). This isn’t necessary, but speeds up the execution quite a bit. Another trick to speed up execution is to specify that your connection to localhost is "local" with a line like the following in your ansible hosts file:

localhost ansible_connection=local

This will keep Ansible from trying to SSH into your local machine before launching the instance.

The task itself is pretty simple. It contains all of the variables that you would expect to specify to launch an instance. Edit them to match your environment and run the playbook with the following command. Since we’re only running the task against localhost, we no longer have to specify an Ansible inventory file with the "-i" flag:

ansible-playbook openstack_deploy.yaml

Assuming that you’ve specified the correct network ID, image name, key name, and so on, you should see the following output and have an instance named "webserver" running in your tenant now.

PLAY [Deploy on OpenStack] **************************************************** TASK: [Deploy an instance] **************************************************** changed: [localhost] PLAY RECAP ******************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0

As I mentioned above, Ansible tasks are typically designed to be idempotent. The nova_compute module is also designed this way – running this playbook multiple times will not launch multiple instances. Also note that the instance can be terminated by changing the "state" attribute from "present" to "absent" and running the playbook again.

Putting it all together

Now that we can launch an instance in OpenStack with a play, the final step is to configure it using the play from the first section. First, we’ll use the add_host module to add the provisioned instance into the Ansible inventory after we provision it. The add_host module was designed to dynamically add hosts into the in-memory inventory after they’ve been provisioned by a previous task. An example playbook with the two plays put together follows.

- name: Deploy on OpenStack hosts: localhost gather_facts: false tasks: - name: Deploy an instance nova_compute: state: present login_username: demo login_password: secret login_tenant_name: demo auth_url: http://127.0.0.1:5000/v2.0/ name: webserver image_name: centos-7-x86_64-genericcloud key_name: root wait_for: 200 flavor_id: 2 auto_floating_ip: yes nics: - net-id: fa6af4e6-c44e-439c-a91c-03bcae55e587 meta: hostname: webserver.localdomain register: nova - name: Add CentOS Instance to Inventory add_host: name=webserver groups=webservers ansible_ssh_host={{ nova.public_ip[0] }} - hosts: webservers remote_user: centos sudo: yes tasks: - name: ensure apache is at the latest version yum: name=httpd state=latest - name: make sure apache is running service: name=httpd state=running

Once again, modify the attributes in the nova_compute task to match your environment. These task is the same as the one we used in the last section, except that we’re now registering the output of the task so that we can access the floating IP address of the instance as a variable. The second task in the "Deploy on OpenStack" play is where we dynamically add the host to the inventory and put it in the "webservers" group.

The last play in the playbook is the same the play that we started the tutorial with. It installs and starts apache on all of the systems in the webservers group, which now includes the instance that we provisioned on OpenStack. Run this example the same way as the last one:

$ ansible-playbook openstack_webserver.yaml

You should get some output similar to the following:

PLAY [Deploy on OpenStack] **************************************************** TASK: [Deploy an instance] **************************************************** ok: [localhost] TASK: [Add CentOS Instance to Inventory] ************************************** ok: [localhost] PLAY [webservers] ************************************************************* GATHERING FACTS *************************************************************** ok: [webserver] TASK: [ensure apache is at the latest version] ******************************** changed: [webserver] TASK: [make sure apache is running] ******************************************* changed: [webserver] PLAY RECAP ******************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 webserver : ok=3 changed=2 unreachable=0 failed=0

Note that there are now two hosts which are listed in the play recap – the tasks which launch the instance are run on localhost and the tasks which configure the webserver are run on the provisioned instance. To test, try to access the floating IP over HTTP in your browser. You should see the Apache HTTP Server test page.

The bigger picture

This simple interaction between Ansible and OpenStack can be used as a starting point for much more complex work flows in the CI process. An ideal process would look something like the following.

A developer checks in a new feature and test into the trunk of a software project.

The CI tool sees the change and initiates a run of the test playbook.

The playbook launches one or more instances on OpenStack and deploys and configures the new version of the application.

The playbook also initiates a run of automated integration tests against the new instances.

The playbook tears down the test environment after the results are recorded.

Ansible also has modules for components like load balancers and work flows can be constructed further down the code pipeline which perform canary deployments of changes when they pass automated testing. For more information on Ansible and the list of available modules, see the Ansible documentation at http://docs.ansible.com.

Michael Solberg contributed this original post.

Superuser is always interested in opinion pieces and how-tos. Please get in touch: [email protected]

Cover Photo // CC BY NC