Ansible 101

ansible_logo_black_square

En la saga de Ender de Orson Scott Card, Ansible es un método de comunicación más rápido que la luz que se usa para distancias interestelares. Pero Ansible también es una herramienta de automatización que, como otras tantas (p.ej. Chef o Puppet), permite ejecutar operaciones en varias máquinas de manera sencilla. La principal diferencia de Ansible respecto a otras herramientas similares es la sencillez de su diseño y su baja curva de aprendizaje. El objetivo de este artículo es explicar lo necesario para empezar a trabajar con Ansible.

Una de las principales características de Ansible es que usa como lenguaje YAML con el sistema de templates Jinja2. YAML es un formato de marcado de datos parecido a XML pero diseñado para ser legible por humanos, cambiando las etiquetas por indentaciones a la hora de organizar los datos. Mediante uno o varios ficheros YAML describiremos las tareas que queremos ejecutar sobre una o múltiples máquinas. Además, como veremos más adelante, Ansible incluye multitud de extensiones o plugins para ahorrar trabajo a la hora de usar ciertos servicios y aplicaciones.

Otra característica importante es que no necesita instalar ningún tipo agente en las máquinas remotas para poder operar con ellas, ya que Ansible ejecuta internamente operaciones vía SSH. El único requisito es que la máquina tenga instalado Python >=2.4 y SimpleJson (que ya viene incluido a partir de Python 2.5), que en muchas distribuciones Linux (como Ubuntu) ya vienen instalados por defecto.

¿Y para qué se puede usar Ansible? Aparte de para la administración de máquinas, se ha convertido en una herramienta muy popular a la hora de desplegar aplicaciones, implementar soluciones de despliegue continuo y cualquier tipo de automatización de tareas que implique ejecución remota. Además es uno de los provisioners más usados con Vagrant y suele ser el acompañante habitual de herramientas como Docker o Amazon Web Services.

Instalación

Ansible está disponible en Github, pero tiene paquetes para Yum, Apt, Pkg, Homebrew y Pip. La instalación es sencilla, estando todos los pasos detallados en la documentación oficial: http://docs.ansible.com/intro_installation.html.

El inventario y empezando a jugar

Aunque cada vez que se lance un comando de Ansible se puede especificar un host, el comportamiento por defecto que toma éste es usar el llamado inventario, un fichero que contiene las máquinas sobre las que podemos operar. Por defecto se encuentra en el directorio /etc/ansible/hosts y usa el formato INI a la hora de identificar y agrupar las máquinas. Ejemplo:

mail.example.com

[webservers]
foo.example.com
bar.example.com

[dbservers]
one.example.com
two.example.com
three.example.com:2212
four.example.com ansible_ssh_user=root

Cada una de las líneas corresponde a una máquina. Los nombres entre corchetes sirven para agrupar las máquinas, de tal manera que podamos ejecutar operaciones a nivel de grupo. Se puede especificar tanto el puerto SSH como el usuario a usar para la conexión, tal como se indica en el anterior ejemplo (three.example.com y four.example.com respectivamente). Lo bueno del inventario es que se pueden definir rangos en el mismo para añadir máquinas fácilmente, por ejemplo:

[webservers]
www[01:50].example.com

o

[databases]
db-[a:f].example.com

Además Ansible incluye la posibilidad de crear inventarios dinámicamente a partir de servicios externos como Cobbler o Amazon EC2, pero queda fuera del ámbito de este artículo (más info aquí: http://docs.ansible.com/intro_dynamic_inventory.html).

Una vez definido nuestro inventario, podemos empezar a usar Ansible. Para los siguientes ejemplos se parte de la base que las máquinas objetivo de nuestras operaciones están accesibles mediante claves SSH. Si se prefiere indicar autenticarse mediante contraseña, se puede añadir a todos los comandos el parámetro --ask-pass.

Éste será nuestro primer comando con Ansible, que lanzará en todas las máquinas del inventario el comando ls /tmp.

$ ansible all -m command -a "ls /tmp" -f 10

Analizándolo parámetro por parámetro:

  • ansible: El nombre del comando.
  • all: Esta es la parte del inventario sobre la que queremos operarall es un valor especial que referencia a todas las máquinas del inventario. Siguiendo el ejemplo anterior este valor podría ser webservers o databases.
  • -m command: El nombre del módulo a usar. En este caso usaremos el módulo command que vale para ejecutar comandos. Hablaremos sobre ello más tarde. Si no se especifica se usa el módulo command por defecto.
  • -a “ls /tmp”: El argumento para el módulo. Esto depende de un módulo a otro, en este caso el argumento que espera command es un comando a ejecutar. El formato de cada módulo está documentado en la sección de módulos de la documentación oficial: http://docs.ansible.com/modules.html
  • -f 10: Esta es la cantidad de forks que queremos usar para ejecutar los comandos, lo que ayudará a acelerar la ejecución mediante paralelización. Por defecto este valor es 5.

Como se puede ver, los detalles de las operaciones pasan por los módulos. Ansible «sólo» es una herramienta para facilitar la ejecución de éstos.

Más ejemplos, cómo copiar un fichero:

$ ansible all -m copy -a "src=/tmp/foo dest=/tmp/foo"

En este caso utilizamos el módulo copy para copiar un fichero de nuestra máquina local a todas las máquinas de un inventario.

$ ansible webservers -m service -a "name=httpd state=started"

En este ejemplo usamos el módulo service para reiniciar todos los servidores web de las máquinas agrupadas bajo webservers.

$ ansible webservers -m yum -a "name=php5 state=present"

Y aquí se usa el módulo yum para instalar en todos nuestros webservers PHP 5.

La cantidad de módulos incluidos por defecto es muy grande (más de 200) e integran infinidad de herramientas y sistemas de todo tipo: Amazon WS, Azure, Docker, Google Cloud, Openstack, VMWare, MongoDB, MySQL, PostgreSQL, Redis, Riak, Rsync, ACL, RabbitMQ, Nagios, A10, Citrix, Jabber, HipChat, Bower, NPM, gem, pip, APT, Git, SVN, Cron, Apach2, Jira, JBoss, etc.

Cómo se puede ver Ansible es una herramienta que sin demasiada configuración previa permite ejecutar tareas bastante complejas en infraestructuras de cualquier tipo, pero esto sólo es una parte de Ansible, nos queda una de las características más relevantes de la plataforma: los playbooks.

Playbooks

Los playbooks son la manera que tiene Ansible de automatizar tareas complejas. Como dice la documentación oficial: los módulos son las herramientas para tu trabajo, y los playbooks son los planos. Se trata de ficheros YAML que permiten gestionar la configuración de la herramienta y organizar el flujo de ejecución de tareas que requieren múltiples pasos. La idea es que puedan ser versionados para poder ser compartidos y reutilizados. Veamos un ejemplo básico:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
    - name: ensure apache is at the latest version
      yum: pkg=httpd state=latest
    - name: write the apache config file
      template: src=/srv/httpd.j2 dest=/etc/httpd.conf
      notify:
        - restart apache
    - name: ensure apache is running
      service: name=httpd state=started
  handlers:
  - name: restart apache
    service: name=httpd state=restarted
  • ---: Parte obligatoria del playbook que indica su inicio.
  • hosts: Grupo de máquinas del inventario sobre la que el playbook se va a ejecutar.
  • vars: En esta parte podemos declarar variables que luego utilizaremos en nuestro playbook. Una vez declarada una variable podemos referenciarla incluyendo el nombre entre dobles llaves (p.ej. {{http_port}}). Si se quiere más detalle sobre como referenciar las variables, se puede consultar la documentación de Jinja2 http://jinja.pocoo.org/docs/dev/ y la de Ansible http://docs.ansible.com/playbooks_variables.html.
  • remote_user: Usuario remoto que va a ejecutar el playbook.
  • tasks: Aquí van las tareas que se van a ejecutar. Estas se ejecutarán en orden en todas las máquinas definidas en hosts. Cada tarea ejecutará un módulo, y Ansible se encarga de que si falla algún host el resto de ejecuciones no se vean afectadas. Cada task debe incluir un nombre (name) que aparecerá en la salida de la ejecución del playbook y el módulo y los argumentos de este siguiendo la sintaxis <módulo>: <argumento>. El módulo sería el equivalente al parámetro -m del comando ansible y el argumento al valor del parámetro -a.
  • template: Este un módulo que no se ha explicado todavía. Es similar a copy, pero procesa el fichero origen para sustituir la referencias a variables que pueda contener este con los valores definidos en vars. Esto es muy interesante, porque permite modificar ciertos valores de los ficheros sólo editando el playbook. En el ejemplo, todas las ocurrencias de {{http_port}} y {{max_clients}} que aparezcan en /srv/httpd.j2 serán sustituidas por 80 y 200 respectivamente en /etc/httpd.conf.
  • handlersnotify: Los módulos de Ansible están desarrollados para ser idempotentes. Esto significa que da igual las veces que lo ejecutemos, que siempre nos darán el mismo resultado y sólo modificarán cosas cuando deban hacerlo, por tanto cada módulo sabe cuando algo se ha modificado. Ansible implementa un sistema de triggers que permite ejecutar ciertas tareas cuando una tarea modifica algo en el sistema remoto. Estas tareas se definen bajo el atributo handlers, y se declaran en el atributo notify de una task. En el ejemplo podemos ver que si el fichero de configuración Apache cambia se reiniciará dicho servicio. De esta manera se evitan reinicios innecesarios.

La ejecución de un playbook es muy sencilla:

$ ansible-playbook playbook.yml -f 10

playbook.yml es el playbook que queremos ejecutar y el comando -f es el número de forks que queremos usar (de la misma manera que con el comando ansible).

Aunque es posible escribir un playbook en un único fichero YAML, lo mejor es separarlo en distintos ficheros para poder reutilizar ficheros y mejorar la organización. Ansible permite la inclusión de diversos playbooks en uno sólo de manera sencilla con la directiva include:

tasks:
  - include: tasks/foo.yml

Al hacer esto, en el momento de ejecución del playbook se “sustituirá” el include por el contenido del tasks/foo.yml. La directiva include tiene otras propiedades interesantes, como la sobreescritura de variables:

tasks:
  - include: tasks/foo.yml variable=hello
  - include: tasks/foo.yml variable=world

¿Por dónde seguir?

Este artículo da una visión muy por encima de lo que permite Ansible y deja fuera características muy interesantes. A continuación se deja un listado de temas relevantes a la hora de profundizar en esta herramienta:

Espero que os resulte útil como para hacer una segunda parte que entre en el detalle de los temas anteriormente listados :).