Thursday, February 11, 2016

Some notes on Ansible playbooks and roles

Some quick notes I jotted down while documenting our Ansible setup. Maybe they will be helpful for people new to Ansible.

Ansible playbooks and roles


Playbooks are YAML files that specify which roles are applied to hosts of certain type.

Example: api-servers.yml

$ cat api-servers.yml
---

- hosts: api
 sudo: yes
 roles:
   - base
   - tuning
   - postfix
   - monitoring
   - nginx
   - api
   - logstash-forwarder

This says that for each host in the api group we will run tasks defined in the roles listed above.

Example of a role: the base role is one that (in our case) is applied to all hosts. Here is its directory/file structure:

roles/base
roles/base/defaults
roles/base/defaults/main.yml
roles/base/files
roles/base/files/newrelic
roles/base/files/newrelic/newrelic-sysmond_2.0.2.111_amd64.deb
roles/base/files/pubkeys
roles/base/files/pubkeys/id_rsa.pub.jenkins
roles/base/files/rsyslog
roles/base/files/rsyslog/50-default.conf
roles/base/files/rsyslog/60-papertrail.conf
roles/base/files/rsyslog/papertrail-bundle.pem
roles/base/files/sudoers.d
roles/base/files/sudoers.d/10-admin-users
roles/base/handlers
roles/base/handlers/main.yml
roles/base/meta
roles/base/meta/main.yml
roles/base/README.md
roles/base/tasks
roles/base/tasks/install.yml
roles/base/tasks/main.yml
roles/base/tasks/newrelic.yml
roles/base/tasks/papertrail.yml
roles/base/tasks/users.yml
roles/base/templates
roles/base/templates/hostname.j2
roles/base/templates/nrsysmond.cfg.j2
roles/base/vars
roles/base/vars/main.yml

An Ansible role has the following important sub-directories:

defaults - contains the main.yml file which defines default values for variables used throughout other role files; note that the role’s files are checked in to GitHub, so these values shouldn’t contain secrets such as passwords, API keys etc. For those types of variables, use group_vars or host_vars files which will be discussed below.

files - contains static files that are copied over by ansible tasks to remote hosts

handlers - contains the main.yml file which defines actions such as stopping/starting/restarting services such as nginx, rsyslog etc.

meta - metadata about the role; things like author, description etc.

tasks - the meat and potatoes of ansible, contains one or more files that specify the actions to be taken on the host that is being configured; the main.yml file contains all the other files that get executed

Here are 2 examples of task files, one for configuring rsyslog to send logs to Papertrail and the other for installing the newrelic agent:

$ cat tasks/papertrail.yml
- name: copy papertrail pem certificate file to /etc
 copy: >
   src=rsyslog/{{item}}
   dest=/etc/{{item}}
 with_items:
   - papertrail-bundle.pem

- name: copy rsyslog config files for papertrail integration
 copy: >
   src=rsyslog/{{item}}
   dest=/etc/rsyslog.d/{{item}}
 with_items:
   - 50-default.conf
   - 60-papertrail.conf
 notify:
    - restart rsyslog

$ cat tasks/newrelic.yml
- name: copy newrelic debian package
 copy: >
   src=newrelic/{{newrelic_deb_pkg}}
   dest=/opt/{{newrelic_deb_pkg}}

- name: install newrelic debian package
 apt: deb=/opt/{{newrelic_deb_pkg}}

- name: configure newrelic with proper license key
 template: >
   src=nrsysmond.cfg.j2
   dest=/etc/newrelic/nrsysmond.cfg
   owner=newrelic
   group=newrelic
   mode=0640
 notify:
    - restart newrelic

templates - contains Jinja2 templates with variables that get their values from defaults/main.yml or from group_vars or host_vars files. One special variable that we use (and is not defined in these files, but instead is predefined by Ansible) is inventory_hostname which points to the hostname of the target being configured. For example, here is the template for a hostname file which will be dropped into /etc/hostname on the target:

$ cat roles/base/templates/hostname.j2
{{ inventory_hostname }}

Once you have a playbook and a role, there are a few more files you need to take care of:

  • hosts/myhosts - this is an INI-type file which defines groups of hosts. For example the following snippet of this file defines 2 groups called api and magento.

[api]
api01 ansible_ssh_host=api01.mydomain.co
api02 ansible_ssh_host=api02.mydomain.co

[magento]
mgto ansible_ssh_host=mgto.mydomain.co

The api-servers.yml playbook file referenced at the beginning of this document sets the hosts variable to the api group, so all Ansible tasks will get run against the hosts included in that group. In the hosts/myhosts file above, these hosts are api01 and api02.

  • group_vars/somegroupname - this is where variables with ‘secret’ values get defined for a specific group called somegroupname. The group_vars directory is not checked into GitHub. somegroupname needs to exactly correspond to the group defined in hosts/myhosts.

Example:

$ cat group_vars/api
ses_smtp_endpoint: email-smtp.us-west-2.amazonaws.com
ses_smtp_port: 587
ses_smtp_username: some_username
ses_smtp_password: some_password
datadog_api_key: some_api_key
. . . other variables (DB credentials etc)


  • host_vars/somehostname - this is where variables with ‘secret’ values get defined for a specific host called somehostname. The host_vars directory is not checked into GitHub. somehostname needs to exactly correspond to a host defined in hosts/myhosts.

Example:

$ cat host_vars/api02
insert_sample_data: false

This overrides the insert_sample_data variable and sets it to false only for the host called api02. This could also be used for differentiating between a DB master and slave for example.

Tying it all together

First you need to have ansible installed on your local machine. I used:

$ pip install ansible

To execute a playbook for a given hosts file against all api server, you would run:

$ ansible-playbook -i hosts/myhosts api-servers.yml

The name that ties together the hosts/myhosts file, the api-servers.yml file and the group_vars/groupname file is in this case api.

You need to make sure you have the desired values for that group in these 3 files:
  • hosts/myhosts: make sure you have the desired hosts under the [api] group
  • api-server.yml: make sure you have the desired roles for hosts in the api group
  • group_vars/api: make sure you have the desired values for variables that will be applied to the hosts in the api group

Launching a new api instance in EC2

I blogged about this here.

Updating an existing api instance


Make sure the instance hostname is the only hostname in the [api] group in the hosts/myhosts file. Then run:

$ ansible-playbook -i hosts/myhosts api-servers.yml


No comments:

Modifying EC2 security groups via AWS Lambda functions

One task that comes up again and again is adding, removing or updating source CIDR blocks in various security groups in an EC2 infrastructur...