in Ansible

Deploying multiple OpenStack clouds with Ansible in a data-driven fashion

Ansible comes with a great companion of OpenStack modules:

http://docs.ansible.com/ansible/list_of_cloud_modules.html#openstack

As you can see, you can create a wide variety of common OpenStack resources (servers, networks, volumes, etc), which you use as building blocks to create a “cloud” with its applications and services (monitoring, DBs, app servers, or whatever).

Let’s say we have access to an OpenStack cloud named yaycloud, and we stored already the connection details to this cloud under our OSCC clouds.yaml.

With our new shiny cloud, we would like to upload a bootstrap key to SSH later on the servers, upload a locally available Ubuntu Trusty image and create a server to host a Nagios service.

This server would be tied to the Neutron network that the cloud admins created for us, named ‘test-net’.

The Ansible playbook could look something like this:


---
- hosts: localhost
  connection: local
  gather_facts: false
  tasks:
  - os_keypair:
      cloud: yaycloud
      name: bootstrap-key
      public_key_file: /home/ubuntu/.ssh/id_rsa.pub
  - os_image:
      cloud: yaycloud
      name: ubuntu
      filename: /home/ubuntu/trusty-server-cloudimg-amd64-disk1.img
  - os_server:
      cloud: yaycloud
      name: nagios
      image: ubuntu-trusty 
      key_name: bootstrap-key 
      flavor: m1.small 
      network: test-net

This works well when you need to create just a few OpenStack resources in the same cloud.

However, it quickly becomes unwieldy when your setup grows as you need to repeat the os_<resource> on and on for each one of the individual resources.
Also, if you plan to have resources in other clouds you may end up with different playbooks for each cloud to avoid having a cluttered single playbook that creates resources in all the different clouds you use.
This also has the problem of repeating the same Ansible code with just different configuration on different playbooks.

Ideally, it would be great if we could:

  1. Decouple the configuration from the code (your specific cloud resources configuration data from the Ansible statements)
  2. Define the resources of my cloud(s) in a declarative way and get Ansible to deploy them
  3. Be able to define as many resources as we want, on as many clouds as we like
  4. Be able to re-use expressed resources in different clouds, without repeating ourselves

As such, I’ve been working the past weeks in an Ansible role that meets the above requirements.
I present you the Ansible OpenStack Cloud Launcher!

With this role, you just have to do the following steps to deploy your clouds resources:

  1. Create a YAML file that defines your clouds and clouds resources (e.g. resources.yml)
  2. Create a super-simple playbook that calls the AOCL (Ansible OpenStack Cloud Launcher) role (e.g. test_aocl.yml)
  3. ---
    - hosts: localhost
      connection: local
      gather_facts: false
      roles:
        - { role: ansible-openstack-cloud-launcher }
    
  4. Run ansible-playbook -i ‘localhost,’ test_aocl.yml -e “@resources.yml”
  5. Profit!

As seeing is believing, this is better explained with an example :-).

Imagine we work in a startup called aoclcompany.
In this company there are a few teams that use OpenStack to do their work (it’s the obvious choice!):
OPS, QA and RnD team.

We are fortunate enough to have access to two different cloud providers, awesomecloud and yaycloud.
In the primer we have cloud admin access, allowing us to create domains, projects and users.
In the second one, the admins of yaycloud created regular non-admin accounts for each one of the teams.

In this initial deployment phase, we are given the task to create the following layout:

  1. In the cloud we have admin access, create a specific domain for each one of the teams. These domains would contain a single project and a user (that would eventually be admins, but we won’t do this at this stage), a Ubuntu Trusty image and some specific flavors that will be available to the users, different to the public cloud provider flavors
  2. In the cloud we have regular user access, all the different accounts (OPS/QA/RnD) will have their own network/subnet/router as they are not created to us by default. The provider does not have Trusty available, so each account would have its own Ubuntu Trusty image
  3. Both clouds (awesomecloud and yaycloud) will have a common bootstrap key
  4. The OPS account in yaycloud will have a machine created to host a Nagios, with HTTP/HTTPS opened
  5. The QA account in yaycloud will have a machine created to host a a Jenkins, with 8080 opened
  6. The RnD account in yaycloud will have a machine created to host a Docker registry, except in this case we don’t care about ports and everything should be opened

Now that we have the requirements, we model them in YAML in our resources.yml file:

profiles:
  - name: admin-clouds
    domains:
      - name: ops
        description: Ops team domain
      - name: qa
        description: QA team domain
      - name: rnd
        description: R&D team domain
    projects:
      - name: ops
        domain: ops
        description: Ops team project
      - name: qa
        domain: qa
        description: QA team project
      - name: rnd
        domain: rnd
        description: RnD team project
    users:
      - name: opsadmin
        password: changeme
        email: opsadmin@aoclcompany.aocl
        domain: ops
        default_project: ops
      - name: qaadmin
        password: changeme
        email: qaadmin@aoclcompany.aocl
        domain: qa
        default_project: qa
      - name: rndadmin
        password: changeme
        email: rndadmin@aoclcompany.aocl
        domain: rnd
        default_project: rnd
    flavors:
      - name: aoclcompany.xlarge
        ram: 128
        vcpus: 1
        disk: 0
      - name: aoclcompany.large 
        ram: 64
        vcpus: 1
        disk: 0
    images:
      - name: ubuntu-trusty
        filename: /home/ubuntu/trusty-server-cloudimg-amd64-disk1.img
  - name: ops
    networks:
      - name: ops-net
    subnets:
      - name: ops-subnet
        network_name: ops-net
        cidr: 192.168.0.0/24
        dns_nameservers:
          - 8.8.8.8
    routers:
      - name: ops-router
        network: public
        interfaces: ops-subnet
    security_groups:
      - name: webserver
        description: Allow HTTP/HTTPS traffic
    images:
      - name: ubuntu-trusty
        filename: /home/ubuntu/trusty-server-cloudimg-amd64-disk1.img
    security_groups_rules:
      - security_group: webserver
        protocol: tcp
        port_range_min: 80
        port_range_max: 80
        remote_ip_prefix: 0.0.0.0/0
      - security_group: webserver
        protocol: tcp
        port_range_min: 443
        port_range_max: 443
        remote_ip_prefix: 0.0.0.0/0
    servers:
      - name: nagios
        image: ubuntu-trusty
        key_name: bootstrap-key
        flavor: m1.small
        security_groups: webserver
        network: ops-net
  - name: qa
    networks:
      - name: qa-net
    subnets:
      - name: qa-subnet
        network_name: qa-net
        cidr: 192.168.1.0/24
        dns_nameservers:
          - 8.8.8.8
    routers:
      - name: qa-router
        network: public
        interfaces: qa-subnet
    security_groups:
      - name: webserver
        description: Allow HTTP/HTTPS traffic
      - name: altwebserver
        description: Allow 8080 traffic
    security_groups_rules:
      - security_group: webserver
        protocol: tcp
        port_range_min: 80
        port_range_max: 80
        remote_ip_prefix: 0.0.0.0/0
      - security_group: webserver
        protocol: tcp
        port_range_min: 443
        port_range_max: 443
        remote_ip_prefix: 0.0.0.0/0
      - security_group: altwebserver
        protocol: tcp
        port_range_min: 8080
        port_range_max: 8080
        remote_ip_prefix: 0.0.0.0/0
    servers:
      - name: jenkins
        image: cirros-0.3.4-x86_64-uec
        key_name: bootstrap-key
        flavor: m1.tiny
        security_groups: altwebserver
        network: qa-net
  - name: rnd
    networks:
      - name: rnd-net
    subnets:
      - name: rnd-subnet
        network_name: rnd-net
        cidr: 192.168.2.0/24
        dns_nameservers:
          - 8.8.8.8
    routers:
      - name: rnd-router
        network: public
        interfaces: rnd-subnet
    security_groups:
      - name: openwide
        description: Allow all traffic
    security_groups_rules:
      - security_group: openwide
        protocol: tcp
        remote_ip_prefix: 0.0.0.0/0
    servers:
      - name: docker-registry
        image: cirros-0.3.4-x86_64-uec
        key_name: bootstrap-key
        flavor: m1.tiny
        security_groups: openwide
        network: rnd-net
  - name: bootstrap-keypair
    keypairs:
      - name: bootstrap-key
        public_key_file: /home/ubuntu/.ssh/id_rsa.pub
clouds:
  - name: awesomecloud
    profiles:
      - admin-clouds
      - bootstrap-keypair
  - name: yaycloud-ops
    oscc_cloud: yaycloud-opsuser
    profiles:
      - bootstrap-keypair
      - ops
  - name: yaycloud-qa
    oscc_cloud: yaycloud-qauser
    profiles:
      - bootstrap-keypair
      - qa
  - name: yaycloud-rnd
    oscc_cloud: yaycloud-rnduser
    profiles:
      - bootstrap-keypair
      - rnd

And with it and as noted earlier, we just run the following command to deploy our clouds resources:

ansible-playbook -i ‘localhost,’ test_aocl.yml -e “@resources.yml”

The resources.yml “DSL” is very simple:
It contains a profiles and clouds list.
The profiles list will have an item for each profile you define, which is a collection of OpenStack resources.
These can be servers,networks,domains, etc. (Note, we use plurals here).
Each type of resource will have as attributes the expected attributes of the corresponding Ansible os_<resource> , so you can just use them if you are familiar with them or refer to the Ansible OpenStack modules documentation.
The attributes will be only required when the Ansible module requires them, and do a default(omit) when the attribute is optional.

The clouds list will contain an item for each one of the clouds you have previously defined on OSCC clouds.yaml.
On these items you can either re-use the profiles previously defined by name or define per-cloud specific resources.
For example:

clouds:
  - name: awesomecloud
    profiles:
      - admin-clouds
      - bootstrap-keypair
  - name: nonprofilescloud
    domains:
      - name: IT
        description: IT team domain

In the above, the first cloud re-uses the resources defined on profiles admin-clouds and bootstrap-keypair whereas the second cloud defines a specific per-cloud domain called IT.
Obviously, you can mix and match per-cloud and profiles resources (Ansible will create per-cloud resources first).

This is very powerful, because:

  1. We can define our clouds layouts in a human readable format like YAML
  2. We can re-use resources definitions by using profiles
  3. We can also have per-cloud specific resources
  4. We can have persistent infrastructure by having the resources.yml in a git repo.
    We just need to put in the ansible control machine a cron that clones the resources.yml repo and runs AOCL role with it as argument
  5. We can put the resources.yml git repo under code review in Gerrit to review infrastructure changes (because, you know, you don’t want to put state:absent on the Gerrit server resource and merge it straight-away :D)

Happy hacking!

Be Sociable, Share!

Write a Comment

Comment