Ansible Part 1: J'ansible, Tu ansibles, il...

Ansible Part 1: J'ansible, Tu ansibles, il...

Published May 15, 2015 in Deployment, System administration - Last update on July 6, 2015.

Ansible is my favourite deployment and configuration management tools. After tested direct concurrency, Puppet and Chef, my choice naturally went to Ansible.

  • 1st it is in Python
  • 2nd it has a clear YAML syntax
  • 3rd it is simple.

What's the stack

My blog uses a classical Django webstack: Nginx + uWSGI + Django + MySQL. Because I'm not billionnaire, I gathers all services in 1 host, but my playbook allow to split into serveral hosts and an app cluster.

I run my playbook with ansible version 1.7.2 and this work is entirely avaiable at Github.com under commit #44994117ee9c8d2e96cea109038e3e09f25d305b. There will be change for future ansible's versions, experiments in my weblog.

 

Main file

---
- hosts: all
  roles:
  - {role: common, tags: common}
  - {role: database, tags: database}
  - {role: cache, tags: cache}
  - {role: middleware, tags: middleware}
  - {role: frontend, tags: frontend} 

I don't think there is more simple, it defines roles and assign them a tag with the same name. It allows me to choice parts to launch.

My playbooks tree - roles for each services

As describe in doc, roles are useful magic tools for auto-include. So my playbook gathers "sub-playbooks" for each services:

  • Common: System settings common to all hosts
  • Database: MariaDB install and application's database and user creation
  • Cache: Redis setup
  • Middleware: Django app configuration with uWSGI
  • Frontend: Nginx configuration as frontend for uWSGI
~/myblog/extras/ansible $ tree
.
├── hosts
├── main.yml
├── vars.yml └── roles ├── cache │   ├── handlers │   │   └── main.yml │   └── tasks │   └── main.yml ├── common │   ├── handlers │   │   └── main.yml │   ├── tasks │   │   └── main.yml │   └── vars │   └── main.yml ├── database │   ├── handlers │   │   └── main.yml │   ├── tasks │   │   └── main.yml │   ├── templates │   │   └── my.cnf.j2 │   └── vars │   └── main.yml ├── frontend │   ├── files │   │   └── robots.txt │   ├── handlers │   │   └── main.yml │   ├── tasks │   │   └── main.yml │   ├── templates │   │   └── nginx-site.conf.j2 │   └── vars │   └── main.yml └── middleware ├── handlers │   └── main.yml ├── tasks │   └── main.yml ├── templates │   ├── myblog.cfg.j2 │   └── uwsgi.ini.j2 └── vars └── main.yml

  

Variables

I made my playbook as flexible as possible. It must support a single-host install or splitted architecture with serveral workers. There are several types of variables:

  • Constants made for DRY writing, users mustn't edit them
  • Inventory variables coupled in ./hosts
  • User-defined variables in ./vars.yml 

Inventory

My inventory file gathers hosts classed by section where each section matches with a role. Every host must be defined with a its settings which are generaly the adress where to bind the service.

For a single-host I use the following file:

[all]
192.168.0.1 mysql_bind_addr=127.0.0.1 uwsgi_bind_addr=127.0.0.1 redis_bind_addr=127.0.0.1

A splitted service configuration could be like below:

[database]
192.168.0.1 mysql_bind_addr=192.168.0.1

[cache]
192.168.0.2 redis_bind_addr=192.168.0.2

[middleware]
192.168.0.3 uwsgi_bind_addr=192.168.0.3

[frontend]
192.168.0.3

The binded addresses is mandatory for configure how nodes will interact with its backend, example where is my database for Django or where are my middlewares for Nginx. 

How did I ...

Deal with single or multiple hosts architecture

 I design my blog as a cloud native application and it's supposed to be easily scalable. So I let me the possibility to scale up or down with ansible. Ansible allow to list hosts by categories with the global variable group, a dictionnary of hosts by roles, for example groups['middleware'] returns a list of IP/hostnames. And if I use a single host, there is groups['all'].

My playbook support both methods with the following trick:

  (groups.get('all', []) + groups.get('middleware', []))

Simply sum groups['all'] and groups['middleware'] together and if one doesn't exists, replace by an empty list.

Share static files between middleware and frontend

Django has manage.py collectstatic for create a single directory with all static files. The trouble is, data aren't shared from middleware but from frontend. There is Ansible synchronize but it can't make operations between 2 remote hosts, so a manual rsync is the solution.

- name: Update static files
  command: rsync -e "ssh -o StrictHostKeyChecking=no" -acogtvz --modify-window=3600 --delete-after --force --no-motd --chown=www-data:www-data root@{{ item }}:{{ static_root }} /var/www/
  with_items: (groups.get('all', []) + groups.get('middleware', []))[0]
  register: update_static
  changed_when: "update_static.stdout.count('\n') > 4"

Protect my private data

All sensitive data are referenced by variables. User must set it in ./vars.yml.

References

Comments

No comments yet.

Post your comment

Comment as . Log out.