jueves, 5 de agosto de 2021

Introducción a Ansible - Configuración básica y comandos ad-hoc

Aunque he publicado un par de posts sobre Ansible con anterioridad, creo que es buena idea hacer una pequeña serie para estudiar más detenidamente su funcionamiento. De esta manera veremos como podemos integrar Ansible en nuestra infraestructura y usarlo para configurar y desplegar sistemas.

Empecemos, por tanto, por la pregunta fundamental ¿que es exactamente Ansible? De forma sencilla y resumida es un software de gestión de configuración y despliegue de sistemas que permite la descripción de infraestructura como código. Esto quiere decir que mediante Ansible puedo fijar la configuración que debe tener un sistema, describiendo dicha configuración como código, para posteriormente aplicar dicha configuración a tantos sistemas como sea necesario. Esa descripción en código nos permitirá definir la plantilla que posteriormente aplicaremos a nuestros sistemas consiguiendo tener una infraestructura cuya configuración sea idéntica en todos los elementos que la forman.

Una de las ventajas de diseño de Ansible, es que solo necesita que los sistemas que vamos a controlar tengan instalado un servidor SSH, sudo debidamente configurado así como una versión más o menos moderna de Python. Gracias a esto podemos usar prácticamente cualquier sistema como consola central, en la cual estará instalado Ansible y desde donde lanzaremos las tareas para controlar toda la infraestructura.

Para no seguir con tanto rollo teórico, vamos a partir de un sistema donde instalaremos Ansible, ya sea de paquete o desde el repositorio de oficial de Github, realizando una configuración básica del mismo.

Por defecto, Ansible almacena toda su configuración en /etc/ansible siendo fundamental el fichero ansible.cfg y el fichero hosts, que contiene el inventario de sistemas bajo el control de Ansible.

Es importante que tengamos en cuenta que, cuando se ejecuta Ansible, este busca su fichero de configuración ansible.cfg de la siguiente manera:

  • Primero busca la variable de entorno Ansible_Config.
  • Luego busca el fichero ansible.cfg en el directorio actual.
  • Luego busca .ansible.cfg en el homedirectory del usuario que está ejecutando el comando ansible.
  • Por último busca en la ruta por defecto que es /etc/ansible/ansible.cfg.

Como lo ideal es usar siempre usuarios no privilegiados, vamos a crear un fichero ansible.cfg básico en el home de un usuario y dar de alta una serie de equipos, en mi caso varios contenedores, con los que realizar nuestras pruebas. El contenido inicial de estos ficheros serán similares a lo siguiente:

Fichero ansible.cfg básico.

Fichero de inventario inicial.

Como vemos en la imagen anterior, el fichero de configuración contiene secciones dentro de cada una de las cuales se establecen pares clave-valor definiendo las opciones que controlan el comportamiento de Ansible. En este caso simple, dentro de la sección defaults, establecemos la ruta y nombre del fichero que contiene el inventario de sistemas, la ruta al interprete de Python que queremos emplear en los sistemas que forman parte de nuestra infraestructura así como que no deseamos comprobar la clave SSH remota de cada uno de ellos en cada conexión. En la sección privilege_escalation, establecemos que método se empleará en los sistemas bajo control de Ansible para escalar a un usuario con privilegios, así como el nombre de dicho usuario.

En el caso del fichero de inventario, de momento, solo establecemos las direcciones IP de los sistemas bajo control de Ansible. Como podemos ver, podemos crear grupos para poder acceder a varios sistemas simultáneamente, siendo en este caso el grupo all el que contiene las direcciones IP de todos los sistemas.

Cada uno de estos sistemas debe tener, además de Python, un servidor SSH y sudo debidamente configurado para permitir la realización de operaciones privilegiadas con el usuario con el que nos conectemos de manera remota.

En mi caso, como ya he comentado, voy a utilizar varios contenedores basados en la última imagen disponible de Ubuntu en la cual añado un usuario no privilegiado, fijo su contraseña, instalo una serie de paquetes, entre ellos el servidor SSH, copio el fichero con la configuración necesaria del usuario no privilegiado para sudo y modifico el entrypoint de la imagen resultante, para que arranque el servidor SSH y lance un while infinito para asegurar que el contenedor permanece arrancado. El contenido del dockerfile para la construcción de la imagen, así como el script usado como entrypoint son los siguientes:

Dockerfile para la construcción de la imagen.

Script usado como entrypoint.

Con esto podemos construir una imagen y arrancar una serie de contenedores, cuyas direcciones IP serán las que hemos añadido al fichero de inventario, cuyo nombre es inventory.list en mi caso.

Estos contenedores sabemos que tienen un servidor SSH arrancado, una instalación de Python y un usuario no privilegiado, con una entrada específica que permite la ejecución de comandos como root pero solicitando contraseña en todos los casos. Este último punto es muy importante ya que, para poder enviar comandos u operaciones desde nuestra consola central de Ansible, será necesario que especifiquemos la contraseña del usuario, así como el nombre del mismo.

Una vez que tenemos establecida esta configuración, vamos a realizar una prueba sencilla de uso de Ansible lanzando lo que se conoce como comando ad-hoc. En Ansible, como ya veremos, podemos crear complejos ficheros de descripción de configuración y operaciones, denominados playbooks, o bien lanzar comandos individuales, denominados comandos ad-hoc. En este último caso, el primero que es recomendable lanzar, ya que nos permite comprobar si nuestra infraestructura está preparada para su control mediante Ansible, es el comando ad-hoc que emplea el módulo ping. Este comando solamente establece la conexión con el sistema o sistemas remotos y comprueba que existe una instalación funcional de Python.

El comando ad-hoc que lanzaremos será el siguiente el cual, además, nos devolverá error con todos los hosts:

ansible -m ping all

Este comando, como podemos ver, emplea el módulo ping, dado por la opción -m, sobre los hosts que forman parte del grupo all, el cual ya definimos en el inventario con las direcciones IP de todos los contenedores. El resultado de la ejecución de este comando es la siguiente:

Resultado de la ejecución del ping de Ansible.

Como vemos, el resultado es un error en todos los casos derivado del hecho de que no hemos especificado la contraseña para establecer la conexión SSH con cada uno de los contenedores. Para esto, solo es necesario añadir la opción -k al comando anterior, con lo que quedaría:

ansible -k -m ping all

siendo ahora el resultado:

Resultado de la ejecución del ping de Ansible.

Como podemos ver, el resultado ahora es muy diferente al anterior ya que, aunque de nuevo recibimos un error, podemos ver claramente que el problema es que no hemos proporcionado la password de sudo necesaria para el usuario. Recordemos que en el fichero de configuración de Ansible establecimos que debíamos escalar a un usuario con privilegios usando sudo, con lo que Ansible lanza el comando sudo con el usuario con el que se establece la conexión SSH. Para introducir la password necesaria para poder usar correctamente sudo, solo es necesario que añadamos la opción -K quedando el comando del siguiente modo:

ansible -kK -m ping all

siendo ahora el resultado el siguiente:

Resultado correcto del comando ping de Ansible.

La salida anterior confirma que los sistemas remotos están correctamente configurados para poder ser controlados por Ansible.

Ahora bien, una pregunta que queda es ¿con que usuario se está conectando Ansible cuando lanzamos los comandos? Por defecto y si no especificamos otra cosa, se conectará con el mismo usuario que esté ejecutando el comando, con lo que ese usuario debería existir en los sistemas remotos y además estar correctamente configurado para poder utilizar sudo.

Si por ejemplo usáramos el usuario root para lanzar un comando de Ansible, deberíamos especificar el usuario remoto con la opción -u, así como incluir las opciones necesarias para introducir la contraseña, quedando el comando del siguiente modo:

ansible -u develop -kK -m ping all

Comando ping con usuario correcto.

Comando ping como usuario root.




Otro punto más a tener en cuenta y que veremos con más detalle en próximas entradas, es que hemos visto que Ansible dispone de módulos que nos permiten realizar tareas específicas. En esta ocasión hemos utilizado el módulo ping, pero si quisieramos lanzar un comando ls -la de forma remota sobre uno de los sistemas usaríamos el módulo raw especificando como argumento de dicho módulo ls -la, quedando el comando más o menos así:

ansible -u develop -kK -m raw -a "ls -la" target

donde target será la dirección IP de un solo sistema o el nombre de un grupo definido en el inventario:

Ejecución de un comando ad-hoc con el módulo raw.
 
Este módulo permite el envío de comandos directamente, igual que haríamos con un comando enviado usando el cliente SSH del sistema.

Como veremos, hay una gran cantidad de módulos disponibles que nos permitirán realizar prácticamente cualquier tipo de tarea así que, para terminar esta entrada, veamos como comprobar instalar un paquete en todos los sistemas a la vez usando el módulo apt de gestión de paquetes en sistemas basados en Debian. El comando a usar es más o menos el siguiente:

ansible -u develop -kK -m apt -a "name=tcpdump state=present" all

Siendo el resultado de la ejecución el siguiente (no se muestra toda la salida):

Instalación de un paquete mediante un comando ad-hoc.
 
De este modo, con un solo comando ad-hoc, hemos instalado un paquete en varias máquinas al mismo tiempo y como veremos más adelante, estos comandos podrán pasar a formar parte de ficheros de descripción denominados playbooks, en los que cada tarea o paso realizará una acción en los sistemas objetivo controlados por Ansible.