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:

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 tus group_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 de upstart 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 llamamos upstart. Aquí es como se invoca este role en nuestro playbook de message.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 de groups de servidores. Así tienes control total de cuales group_vars se deployan a todos los groups, sin tener definir los group_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