Introducción a Ansible: Conceptos Básicos y Primeros Pasos

Series - ansible-series

Antes de comenzar, quería contarles que hay una promoción en DigitalOcean donde te dan un crédito de USD 100.00 durante 60 días para que puedas probar los servicios que este Proveedor Cloud ofrece. Lo único que tienes que hacer es suscribirte a DigitalOcean con el siguiente botón:

DigitalOcean Referral Badge

O a través del siguiente enlace: https://bit.ly/digitalocean-itsm



Comencé a utilizar Ansible en 2018, luego de hacer esa transición de trabajar con dos o tres servidores en ciertos proyectos a cientos de ellos, o manejar proyectos en la que los servidores tenían basicamente los mismos requerimientos. Me di cuenta del potencial de esta herramienta y comencé a asistir a conferencias (el RedHat Ansible Automates que tuvo lugar en Santiago de Chile en 2019) y ver cursos en YouTube.

Muchas empresas a principios de los 2000 aquejaban esta problemática de no poder gestionar y mantener muchos servidores de una manera eficiente, y que algunas utilizaban otras herramientas como Chef o Puppet y las que no, pues con scripts shell. En aquella época no había nada simple y directo para poder gestionar las configuraciones, especialmente si es la de instalar software por ejemplo.

Ansible en sus comienzos partió como una herramienta para ejecutar comandos en muchos servidores, la palabra Ansible viene del libro de ciencia ficción Ender’s Game, que es un dispositivo que permitia comunicarse de manera instantánea con otros dispositivos alrededor del universo, por lo que la idea de Ansible es comunicarse desde nuestra maquina local o desde un CI y ejecutar acciones sobre servidores remotos todo sucediendo al mismo tiempo.

Ansible fue adquirida por RedHat en 2015, por lo que integra mucho del ecosistema de RedHat, lo cual hace tiene sentido porque RedHat integra todo lo que concierne a Linux y Computación en la Nube.

Ansible es una herramienta que nos permite automatizar la configuración de servidores, aprovisionamiento en la nube, despliegue de aplicaciones entre muchas otras tareas relacionadas al ámbito DevOps.

Ansible facilita la gestión ya que en lugar de tener conocimientos en algún lenguaje de scripts como bash, basicamente podemos tomar un shell script y convertirlo en una simple receta de Ansible (llamada Playbook) y ejecutarla en varios servidores de manera sencilla.

El objetivo de Ansible es ser una herramienta simple, rápida y completa, fácil de aprender, especialmente si venimos del mundo shell scripts como método básico de automatización; y además ser una navaja suiza, y lo veremos una vez instalemos Ansible que nos proporcionará unos 4000 módulos para realizar cualquier tarea, sea en Linux, MacOS, Windows, Redes, etc.

Es eficiente, ya que no tenemos que instalar ningún agente en el servidor remoto, ya que se comunica a través de SSH, siempre y cuando el servicio OpenSSH se encuentre instalado y ejecutándose con el puerto 22 (u otro) abierto.

Es seguro por el hecho que toda la comunicación va encriptada por SSH y se necesitarían un par de llaves publica/privada para poder realizar la conexión.


Hay muchas maneras de instalar Ansible, desde los paquetes oficiales hasta por pip, sin embargo, yo recomendaria instalarlo via pip para tener la version mas actualizada a no ser que tengas una distribución rolling release:

1
sudo pip3 install ansible

Para comprobar que se encuentra correctamente instalado:

1
ansible --version

Deberia darnos esta salida:

1
2
3
4
5
6
7
8
9
ansible [core 2.12.4]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.10/site-packages/ansible
  ansible collection location = /home/ansible/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.4 (main, Mar 23 2022, 23:05:40) [GCC 11.2.0]
  jinja version = 3.0.3
  libyaml = True

Además nos da ciertas informaciones que debemos tener en cuenta, como por ejemplo, la ubicación del binario, la ruta del archivo de configuración (ansible.cfg), la version de python que se está utilizando, etc.


Generamos una llave SSH:

1
2
3
mkdir ~/.ssh
cd ~/.ssh
ssh-keygen

Ajustamos los permisos tanto de la carpeta .ssh como del par de llaves:

1
2
3
chmod 700 ~/.ssh
chmod 700 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa.pub

Copiamos la llave publica al servidor:

1
ssh-copy-id -i ~/.ssh/id_rsa.pub user@192.168.0.10

Agregamos nuestra llave privada a nuestro repositorio de llaves (asi no la tendremos que invocar explicitamente desde cualquier comando de Ansible.

1
ssh-add ~/.ssh/id_rsa

Ahora quiero ejecutar un comando del lado del servidor, por lo que será suficiente obtener su dirección IP o en su defecto su nombre FQDN. Si queremos ejecutar comandos sobre servidores en cualquier proveedor de nube pública, debemos asegurarnos que dentro del Security Group esté abierto el puerto 22.

Lo que tenemos que hacer para que Ansible reconozca el/los servidor(es), necesitamos crear un archivo de inventario (inventory) el cual puede ser llamado inventario a secas o hosts. Este archivo le dice a Ansible los servidores con los cuales se estarán trabajando.

1
touch inventory

Abrimos el archivo con nuestro editor de confianza:

1
2
[ejemplo]
192.168.0.10

Las llaves significan que todas las direcciones ip o nombres de servidor que sigan, serán agrupados en [ejemplo], por lo que las tareas que se ejecuten serán en ese grupo de servidores.

De esta manera, le decimos a Ansible que bajo el grupo ejemplo hay un servidor bajo la dirección IP 192.168.0.10 por lo que ahora Ansible conoce este servidor.

Para comprobar la conexión con el servidor:

1
ansible -i inventory ejemplo -m ping -u ansible

-i: le dice a ansible donde se encuentra el archivo de inventario

-m: se le indica a Ansible que utilice el modulo ping para comprobar la conexion

-u: el usuario del servidor remoto.

Nos debería dar la siguiente salida:

1
2
3
4
5
6
7
192.168.0.10 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Que nos muestra que la conexión ha sido establecida correctamente, ademas de otras informaciones, como la version de Python instalada en el servidor remoto (python3), que no fue cambiado nada en el servidor (changed: false) y que cuando fue ejecutado el ping, el servidor respondió con un pong

En caso de querer autenticarnos con contraseña, debemos agregar el parámetro --ask-pass. Aunque lo mejor es siempre autenticarse con llave privada, es mucho mas seguro y rápido que tener contraseñas.

A pesar que Ansible es una herramienta de configuración en la cual no necesitamos un agente instalado para recibir los cambios, si es necesario que el/los servidor(es) remotos tengan instalado Python, esto no debería ser problema alguno ya que aproximadamente el 95% de los servidores deben tener como mínimo Python instalado.

Se hace un poco tedioso tener que colocar donde se encuentra el inventario, el usuario del servidor remoto y mas cuando lo que se trata es de automatizar tareas repetitivas y aburridas. Vamos a crear un archivo llamado ansible.cfg , en la que vamos a sobreescribir las opciones por defecto de Ansible (cuya configuración por defecto se encuentra en /etc/ansible/ansible.cfg), asi colocaremos donde se encuentra nuestro inventario y el usuario remoto:

1
2
3
[defaults]
INVENTORY = inventory
ansible_user = ansible

Con lo que ya podríamos ejecutar de nuevo un ping a nuestro servidor y tendríamos el mismo resultado:

1
ansible ejemplo -m ping

Si bien hacer un ping a nuestro servidor para comprobar la conexión es buena idea, no es un comando a posteriori no nos proporciona mucha información que digamos, y para esto, están los comandos ad-hoc.

Un comando ad-hoc ejecuta un comando dentro del servidor y el resultado del mismo lo vamos a ver por nuestro stdout:

1
ansible ejemplo -a "free -h"

-a: argumento que ejecuta un comando ad-hoc

En este comando en particular, estamos diciéndole a Ansible que ejecute el comando free -m en el servidor remoto y que el resultado nos los muestre en el stdout de nuestra terminal:

1
2
3
4
192.168.0.10 | CHANGED | rc=0 >>
              total        used        free      shared  buff/cache   available
Mem:           15Gi       1,0Gi       8,0Gi        14Mi       6,4Gi        14Gi
Swap:         8,0Gi          0B       8,0Gi

Hay algo particular, y es que no le hemos indicado a Ansible que modulo utilizar para que nos ejecute el comando free -h y es que, si omitimos el argumento -m, ejecutará el modulo command por defecto, el cual se encarga de ejecutar estos comandos por nosotros. También se pueden utilizar los distintos y ricos módulos de Ansible a través de la linea de comandos.


Un playbook nos es mas que nuestra receta con las tareas que vamos a ejecutar en el servidor, seria como invocar comandos ad-hoc pero de una manera mas ordenada y secuenciada. Es en formato yaml (clave valor) y que cualquier humano puede entender (bueno a veces)

Yaml tiene un formato bastante peculiar, y es que hay que tener mucho cuidado con el indentado. Yo recomendaria instalar algún plugin de VSCode o u editor de confianza asi te evitas tener que estar perdiendo el tiempo buscando el porqué no se ejecuta el playbook.

Listos?

Creemos entonces nuestro archivo:

1
touch playbook.yml

Y comencemos a escribir tareas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
- hosts: all
  become: true
  tasks:
    - name: Asegurando que NTP está instalado.
      apt: 
        name: ntp
        state: present

    - name: Asegurando que NTP se esta ejecutando.
      service:
        name: ntp
        state: started
        enabled: true

Con - hosts: all le estamos indicando a Ansible en cuales servidores vamos a ejecutar esta receta, en nuestro caso le hemos puesto para que las tareas se ejecuten en todos los servidores indicados en el inventario (aun si estos se encuentran agrupados)

Con become: true queremos que nuestras tareas se ejecuten con permisos sudo (o root), ya que estas tareas se van a instalar software (en este caso el paquete ntp) e inicializar el servicio.

Luego viene la directiva tasks, seguido de un guion y el nombre de la tarea. Recomiendo ampliamente documentar de que se trata cada tarea, asi se lleva un mejor control de que exactamente es lo que esta sucediendo en el servidor remoto.

Luego viene apt, y aqui me voy a detener un poco. Recordemos cuando estábamos ejecutando un -m ping en el comando para comprobar la conexión, apt es el nombre del modulo de Ansible que se va a encargar de instalarnos el paquete ntp. Seguido del nombre del paquete (ntp) y luego el state que seria como el estado deseado en el que queremos esa tarea (con present le estaríamos diciendo a Ansible que instale ese paquete en el servidor) Un modulo de Ansible puede tener multiples states, como present, absent (para desinstalar o borrar), etc.

Segunda tarea: Iniciar el servicio de NTP, cambia solamente el enabled: true que es similar cuando activamos un servicio con systemctl enable ntp y en el state lo inicia de una vez. La combinación de ambos argumentos seria como decir systemctl enable --now ntp

Si bien es cierto, en distribuciones basadas en Debian, al instalar un servicio (apache, nginx, mysql, ntp, etc) el gestor de paquetes nos ahorra el tener que activarlos a mano, en otras basadas en RedHat, OpenSUSE o Archlinux tenemos que hacer un paso adicional para configurar y activar, por lo que no estaria de más hacer la comprobación y que el servicio quede levantado.

Ejecutemos nuestro playbook ahora:

1
ansible-playbook playbook.yml -K

-K: nos pedirá que ingresemos el password de sudo (root) del servidor remoto.

Y podremos ver los cambios que va a realizar a partir de la siguiente salida:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PLAY [all] *************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [192.168.0.10]

TASK [Asegurando que NTP está instalado.] ******************************************************************************
changed: [192.168.0.10]

TASK [Asegurando que NTP se esta ejecutando.] **************************************************************************
ok: [192.168.0.10]

PLAY RECAP *************************************************************************************************************
192.168.0.10              : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Detallando un poco más, se ejecutaron ambas tareas, pero fijémonos en la linea final, donde dice ok=3 y changed=1

De esta manera podemos concluir que se ejecutaron 3 procesos el cual 1 cambió. Volvamos a ejecutar nuestro playbook y vemos que sucede:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PLAY [all] *************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [192.168.0.10]

TASK [Asegurando que NTP está instalado.] ******************************************************************************
ok: [192.168.0.10]

TASK [Asegurando que NTP se esta ejecutando.] **************************************************************************
ok: [192.168.0.10]

PLAY RECAP *************************************************************************************************************
192.168.0.10              : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Vemos ahora que changed=0 pero ¿por qué?

Ansible maneja un concepto que se llama Idempotencia que no es mas que la propiedad para realizar una acción determinada varias veces y aun así conseguir el mismo resultado que se obtendría si se realizase una sola vez.

Si ejecutáramos el mismo playbook varias veces no veríamos cambios, ya que con la primera ejecución se instalo el software y se comprobó que el servicio estaba corriendo. Esto nos ahorra recursos al momento de ejecutar tareas que de por si ya están completadas y Ansible lo que hace es una pequeña verificación: ¿Está NTP instalado? ¿Si? → Siguiente tarea.

Hay una pequeña gran diferencia que en vez de hacerlo por el modulo apt, instalemos ntp por el modulo command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- hosts: all
  become: true
  tasks:
    - name: Asegurando que NTP está instalado.
      command: apt install -y ntp

    - name: Asegurando que NTP se esta ejecutando.
      service:
      name: ntp
      state: started
      enabled: true

Y es que cuando ejecutemos nuestro playbook, siempre va a reportar changed=1 y ejecutará la instalación (asi esté instalado) las veces que corramos el playbook. Esto se debe Ansible no sabe o no identifica que hay un cambio, asi como tanto el modulo command como modulo shell está diseñado para aquellos casos en que no haya un modulo predeterminado con el cual ejecutar una tarea especifica, por lo que recomiendo agotar la documentación a ver si hay algún modulo que se adapte a nuestras necesidad y si no lo hay, el modulo command nos ayudará.

Espero les haya gustado esta Introducción a Ansible, ¡hasta la proxima!


  • Introducción a Ansible: Conceptos Básicos y Primeros Pasos

Si te pareció útil este artículo y el proyecto en general, considera brindarme un café :)

Buy me a coffeeBuy me a coffee