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
.
Comments
No comments yet.