Ansible is a big favourite of mine as anyone that knows me will tell you and has become one of the biggest players in the DevOps world, inevitably if you’re going to use it at any real scale you’ll need to start thinking about tags. Tags are an essential part of life in the cloud, given the scale and complexity we can encounter they really become the only way to track ownership, billing and other arbitrary metadata. Managing them on a per-resource basis is not only cumbersome it’s the height of inefficiency. In this post we’ll take a look at how to use pre-constructed dictionaries of tags to assign the same metadata to all resources in a given environment.
A Note on Clouds and Input
Most Ansible modules already allow for the input of tags on provision, our aim here is to remove the manual input of the same tags for provisioning each different instance of every different resource and replace that with a single standard set.
We’ll be working with AWS in these examples, though the same logic also applies to GCP and Azure and applies to the vast majority of resources which can be provisioned on all platforms as they all accept tags in a Key: Value pair format. AWS does however present an oddity with Ansible in terms of handling tags after provisioning in that tags can be manipulated without modifying their resource (see the AWS EC2 Tag module for an example).
Defining Tags as a Dictionary
So let’s take a look at how we might define some tags in Ansible with some data that we might expect to use in a typical environment. First we’ll create a YAML file named vars.yaml which we’ll use to inject input variables to our playbooks:
#--vars.yaml --- default_tags: Administrator: Welsh Department: IT CostCentre: ABC123 ContactPerson: andy@tinfoilcipher.co.uk ManagedByAnsible: True ...
In the above example we’re creating a single dictionary named default_tags which contains a set of Key: Value pairs which will act as our tag data.
Provisioning Resources
Now that we have defined a dictionary lets create some resources. For the sake of simplicity we’ll just make some VPCs. As we see below instead of defining individual tags we can simply pass the “{{ default_tags }}” variable to the tags argument:
--- - name: Create AWS Components hosts: localhost gather_facts: false connection: local tasks: - name: Create VPCs amazon.aws.ec2_vpc_net: name: "{{ item[0] }}" cidr_block: "{{ item[1] }}" region: eu-west-1 tags: "{{ default_tags }}" tenancy: dedicated with_together: - ['VPC-Dev', 'VPC-Test', 'VPC-Prod'] - ['10.1.0.0/16', '10.2.0.0/16', '10.3.0.0/16'] ...
We can then run our playbook and pass in the extra variable file using the –extra-vars argument:
sudo ansible-playbook build_aws.yaml --extra-vars="@vars.yaml" # PLAY [Create AWS Components] **************************************************************** # TASK [Create VPCs] ************************************************************************** # changed: [localhost] => (item=[u'VPC-Dev', u'10.1.0.0/16']) # changed: [localhost] => (item=[u'VPC-Test', u'10.2.0.0/16']) # changed: [localhost] => (item=[u'VPC-Prod', u'10.3.0.0/16']) # PLAY RECAP ********************************************************************************** # localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Looking at the console we can see that our VPCs have provisioned and their tags have been correctly applied:
Merging Multiple Tag Sets
Another scenario we might encounter is where we want to want to apply two or more sets of tags to resources in the same environment but limit the application of these tags to specific provisioned resources. Ansible’s combine filter allows us to manage this additional functionality on a per-module basis.
Let’s modify our vars.yaml to include an additional dictionary named optional_tags:
#--vars.yaml --- default_tags: Administrator: Welsh Department: IT CostCentre: ABC123 ContactPerson: andy@tinfoilcipher.co.uk ManagedByAnsible: True optional_tags: DevelopmentEnvironment: True SecondaryContact: welsh@tinfoilcipher.co.uk ...
In the playbook below we can then change the input to the tags argument to merge both of these dictionaries using the combine filter and create some VPC Subnets using both sets of tags:
--- - name: Create AWS Components hosts: localhost gather_facts: false connection: local tasks: - name: Create Subnets amazon.aws.ec2_vpc_subnet: state: present vpc_id: vpc-04b75b47f5d954322 cidr: "{{ item }}" tags: "{{ default_tags | combine(optional_tags) }}" with_items: - '10.1.1.0/24' - '10.1.2.0/24' - '10.1.3.0/24' ...
Executing the playbook again, we can see that our subnets have been created and both dictionaries of tags have been applied: