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
- Using Ansible 2.0 to launch a server on OpenStack - September 9, 2016
- Using Ansible for continuous integration on OpenStack - March 18, 2016