Ansible para Configuration Management
El contenido de este post viene de una presentación que hice en Centraal en la Ciudad de México.
Ansible es un DSL (domain specific language) escrito en Python que ayuda a configurar servidores, deployar código, manejar servicios, etc. Archivos de Ansible se escriben en YAML, con soporte para templating via Jinja2. No se instala el ejecutable, ansible-playbook
, en los target machines. Se ejecuta en tu máquina, traduce el YAML a shell commands, y los ejecuta en los target machines a través de SSH.
Ideas Claves
Infrastructure as code
Porqué?
- Hace 15 años aplicaciones corrían en un sólo servidor que se encargaba de todo. Configuration management no era importante, excepto en empresas grandes
- Hoy todas las empresas tienen servidores especializados: proxy servers, DB, message queue, API…
- Configurar todos de forma homogénea es imposible sin ayuda
- Por eso Ansible, Salt, Puppet, Chef…
Beneficios
- version control: tu código de Ansible se mete a Git
- automate deployment desde GitHub: fácilmente hacer deployment en target machines jalando cualquier branch de un repo de GitHub
- control granular: reiniciar servicios en API servers de staging, hacer deploy a message servers de producción, actualizar env_vars en API servers y message servers…
- control de todo: logging, firewalls, cron jobs…
- comodidad: no te tienes que conectar via SSH a un servidor tras otro, ejecutando código, pidiéndole a dios que no se te olvide ningún detalle
Tu código de Ansible define toda la configuración de tus servidores. Para tu producto, tu server config es igual de importante como tu application code. Con Ansible tratas este código con el mismo respeto, y lo escribes en un lenguaje mandado a hacer para esto.
Single source of truth
Con los group_vars
de Ansible, se definen tus variables en un sólo lugar:
- env_vars
- dependencias/packages (del sistema, PyPI, Node…)
- rutas (al directorio de tu aplicación, a tu virtualenv, a static files, a log files…)
- …
Los group_vars
que se incluyen en un playbook dependen de los hosts
a los cuales apunta ese playbook.
DRY
Muchos servidores se agrupan de forma natural:
- por ambiente: development vs. staging vs. production
- por función: api vs. message vs. database…
Los inventory
files te permiten apuntar a grupos, y heredar group_vars
para que éstos no se tienen que definir más de una vez. Ve este ejemplo: inventories/staging.
Case Studies (Estudios de Caso)
env_vars
Manejar env_vars puede ser muy doloroso:
- Cómo asegurar que los mismos se deployan a todos los servidores? Combinar y setear tus env_vars en el Ansible environment keyword, y hacer un rol que lee éstos, crea un archivo de
.env
, y lo copia al target machine. Así sólo tienes que definir env_vars en tusgroup_vars
, y los mismos que usan los playbooks de Ansible estarán en tus servidores. - Cómo asegurar que están encriptados, para que se puedan meter a version control y así compartirse de forma eficiente con todo el equipo? Meter env_vars secretos en archivos de group_vars encriptados, usando, por ejemplo, Ansible Vault. Sólo tienes que desencriptar estos archivos cuando quieres cambiarlos. Puedes usar un Git hook de pre-commit para rechazar commits que contienen archivos secretos sin encriptar.
- Cómo deployar env_vars a tus servidores, y asegurar que todos los procesos tengan acceso a ellos? Definir la ruta al archivo
.env
en el target machine con Ansible, y usar la misma ruta en tus scripts deupstart
para que los servicios puedan sourcear.env
cuando corren.
Services (bajo Upstart, con logging)
- upstart es un init daemon de Ubuntu, que te permite controlar servicios con un sintaxis hiper-sencillo:
sudo start|stop|restart {name_of_service}
. Nosotros creamos los scripts de upstart con un role de Ansible, que llamamosupstart
. Aquí es como se invoca este role en nuestro playbook demessage.yml
, que configura nuestros message servers.
- role: upstart
tags: [upstart]
scripts:
- "celery-flower"
- "celery-worker"
- "celery-beat"
service_namespace: "-"
replace_logrotate: true
- Tenemos varios templates de Ansible que se pasan a este role. Aquí hay el template para el script que nos deja controlar a
gunicorn
usando upstart. - El role convierte los templates en scripts de upstart y los copia a
/etc/init
del target machine, y también configura logrotate. - Con este setup, estos servicios se pueden reiniciar por Ansible fácilmente, los logs de todos están en el mismo lugar,
/var/log/upstart/{name_of_service}.log
, y los logs se rotan.
Demo
Para no tener que memorizar los comandos de ansible-playbook
, o copiar y pegarlos de un cookbook grande, torpe, y sujeto a cambios frecuentes, usamos un programa, ansible-command-generator.py, que nos genera los comandos.
Como ejemplo haremos un deploy a los servidores staging
de webapp
y message
, especificando el branch que queremos deployar. Después reiniciaremos los servicios en estos servidores.
El programa usa una librería de Python que escribí, questionnaire, para poder definir las preguntas, presentarlas, y devolver los resultados. questionnaire sirve para cualquier encuesta que quieres armar desde el shell. Funciona en Python 2 y 3, y se puede instalar con pip install questionnaire
.
Unas Mejores Prácticas
- Meter funcionalidad reutilizable y modular en
roles
- Definir
inventories
con herencia degroups
de servidores. Así tienes control total de cualesgroup_vars
se deployan a todos los groups, sin tener definir losgroup_vars
más de una vez. Ve este artículo - Crear
playbooks
distintos para servidores con funciones distintos, no ambientes distintos - Usar
tags
para no tener que correr todo dentro de un role o playbook, sino poder hacer un pick and choose de tasks y roles que quieres correr at runtime - Manejar servicios y deployment a través de Ansible
- Se creativo. Ansible tiene mucho power y puede tener un gran impacto en tu workflow
- http://docs.ansible.com/ansible/playbooks_best_practices.html