di.png

Resumen

La configuración y el aprovisionamiento de sistemas es un proceso que conviene automatizar, especialmente cuando nos enfrentamos a la administración de una gran cantidad de equipos. Además, normalmente se trata de procesos repetitivos y farragosos en los que es muy posibile la introducción de errores. En este sentido, las herramientas de automatización de sistemas y gestión de la configuración nos asisten en estas tareas.

Ansible es una herramienta de automatización y gestión de la configuración. Con Ansible podremos gestionar decenas, cientos o miles de sistemas de forma sencilla, y además desde cualquier lugar. Su arquitectura basada en módulos permite extender sus capacidades casi de forma indefinida. Encontramos módulos para cloud (Amazon, Azure, Google, OpenStack, …​), virtualización (VMware, oVirt), bases de datos, archivos, monitorización, red, almancenamiento, control de versiones, Windows, …​). Además, su configuración es sencilla, lo que permite tener todo preparado para un proyecto de Ansible con rapidez. La configuración de Ansible sólo exige la instalacion de Python en cada uno de los equipos a administrar. El equipo que orquesta o gestiona la configuración tendrá instalado Python y Ansible.

En este seminario haremos una introducción al uso de Ansible mediante ejemplos cotidianos, trabajando con algunos de los módulos más habituales y presentando Ansible Galaxy para aumentar la productividad en nuestros proyectos.

Objetivos
  • Conocer las ventajas y la potencia que aporta Ansible en la automatización de operaciones y gestión de la configuración.

  • Aprender a usar roles y playbooks para la automatización de operaciones.

  • Conocer los módulos básicos de Ansible.

  • Usar Ansible Galaxy para la creación de playbooks.

1. Qué es Ansible

Ansible es una herramienta que permite la administración remota y de sistemas mediante código. Con Ansible podremos crear recetas para la creación de infraestructura, aprovisionamiento de recursos y despliegue de aplicaciones.

La Gestión de la configuración es una forma de gestión de cambios en un sistema siguiendo un método que permita mantener su integridad en el tiempo. Se registran y documentan las operaciones realizadas de forma que es posible saber cuándo y por qué se llevó a cabo un cambio. Además, permite saber el estado exacto de un sistema en un momento determinado.

La creación y administración de infraestrucutra como código, combinada con un sistema de control de versiones nos permite el trabajo en equipo, la prueba de configuraciones, vuelta atrás a configuraciones anteriores, y otras ventajas del uso de sistemas de control de versiones.

2. Ventajas de Ansible

  • No necesita agentes

  • Sólo requiere que esté instalado Python en las máquinas a administrar

  • Uso de módulos para enriquecer su funcionalidad y facilitar su uso

3. Requisitos de Ansible

De forma predeterminada, Ansible administra máquinas mediante el protocolo SSH. Sólo es necesario tener instalado Ansible en una máquina (la máquina de control), que es la que puede administrar baterías de máquinas remotas de forma centralizada. En las máquinas remotas sólo hace falta que esté instalado Python.

3.1. Requisitos para la máquina de control

Actualmente Ansible puede ejecutarse desde cualquier máquina con Python 2 (versión 2.7) o Python 3 (versiones 3.5 y posteriores). La máquina de control no puede ser una máquina Windows.

3.2. Requisitos de los nodos administrados

Necesitamos una forma de comunicarnos con los nodos gestionados, y se suele hacer mediante SSH. También se necesita Python 2 (versión 2.7) o Python 3 (versiones 3.5 y posteriores)

De forma predeterminada, Ansible usa el intérprete Python localizado en /usr/bin/python para ejecutar sus módulos. Sin embargo, algunas distribuciones de Linux sólo tienen un intérprete de Python 3 de forma predetermianda (/usr/bin/python3). En esos sistemas puede producirse un error como este:

"module_stdout": "/bin/sh: /usr/bin/python: No such file or directory\r\n"
you can either set the ansible_python_interpreter inventory variable (see Working with Inventory) to point at your interpreter or you can install a Python 2 interpreter for modules to use. You will still need to set ansible_python_interpreter if the Python 2 interpreter is not installed to /usr/bin/python.

4. Instalación de Ansible

4.1. Configuración de la máquina de control

  1. Instalación de Python

    Comenzaremos instalando Python. En nuestro caso instalaremos Python 2.7.

    $ sudo apt-get update
    $ sudo apt-get install -y python-minimal
  2. Instalación de Ansible

    $ sudo apt-get update
    $ sudo apt-get install software-properties-common
    $ sudo apt-add-repository --yes --update ppa:ansible/ansible
    $ sudo apt-get install ansible

    Si estamos usando OpenStack, podemos pasar en el proceso de creación de la instancia que actúa como máquina de control de Ansible el script de instalación de Python y Ansible. De esta forma, una vez creada la instancia, ya estará preparada para para actuar como máquina de control Ansible.

    #!/bin/bash
    
    echo "Instalando Python"
    apt-get update
    apt-get install -y python-minimal
    
    echo "Instalando Ansible"
    apt-get install -y software-properties-common
    apt-add-repository --yes --update ppa:ansible/ansible
    apt-get install -y ansible

    Tras la instalación podemos probar que Python y Ansible están funcionando correctamente

    $ python --version
    Python 2.7.12
    
    $ ansible --version
    ansible 2.7.5
      config file = /etc/ansible/ansible.cfg
      configured module search path = [u'/home/ubuntu/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
      ansible python module location = /usr/lib/python2.7/dist-packages/ansible
      executable location = /usr/bin/ansible
      python version = 2.7.12 (default, Nov 12 2018, 14:36:49) [GCC 5.4.0 20160609]

4.2. Configuración de las máquinas administradas

En las máquinas administradas basta con instalar Python.

$ sudo apt-get update
$ sudo apt-get install -y python-minimal

Si estamos usando OpenStack, podemos pasar en el proceso de creación de las instancias que actúan como máquinas administradas por Ansible el script de instalación de Python. De esta forma, una vez creadas las instancias, ya estarán preparadas para para actuar como máquinas administradas por Ansible.

#!/bin/bash

echo "Instalando Python"
apt-get update
apt-get install -y python-minimal

4.3. Copia de claves SSH a máquinas administradas

La comunicación entre la máquina de control y las administradas es vía SSH. Por tanto, la máquina de control deberá tener la clave privada y las máquinas administradas la clave pública (examinar el archivo ~/.ssh/authorized_keys de las máquinas administradas para ver las claves públicas autorizadas).

Para ello, copiaremos la clave desde la máquina de control hasta las máquinas administradas con ssh-copy-id.

Por ejemplo:

ssh-copy-id -i ~/.ssh/id_rsa 20.0.0.27
ssh-copy-id -i ~/.ssh/id_rsa 20.0.0.22

Si hemos creado las instancias de Ansible en OpenStack, dichas instancias ya se habrán creado con una clave pública inyectada. Sólo los clientes en los que esté su pareja de clave privada podrán iniciar sesión en dichas instancias.

Podemos crear un par de claves para la ocasión y distribuirla desde la máquina de control de Ansible a las máquinas remotas. Otra opción es copiar a la máquina de control Ansible la clave privada que empareja con la clave pública que ya tienen inyectada las instancias

4.4. Herramientas de línea de comandos instaladas con Ansible

Tras la instalación de Ansible podemos comprobar que hay varias herramientas de línea de comandos instaladas:

  • ansible: Permite la ejecución directa de comandos sobre un conjunto de hosts.

  • ansible-playbook: Ejecuta playbooks sobre un conjunto de hosts.

  • ansible-vault: Cifra el contenido de archivos con datos sensibles, como los que contienen contraseñas.

  • ansible-galaxy: Instala roles de Ansible Galaxy, una plataforma para el intercambio de roles (recetas) Ansible.

  • ansible-console: Consola de ejecución de comandos.

  • ansible-config: Gestiona la configuración de Ansible.

  • ansible-doc: Muestra documentación sobre los módulos instalados.

  • ansible-inventory: Muestra información sobre el inventario de hosts.

  • ansible-pull: Descarga playbooks desde un sistema de control de versiones y lo ejecuta en el sistema local.

5. Archivos de configuración y de inventario

En la instalación de Ansible se crea un archivo de configuración global (/etc/ansible/ansible.cfg) y un archivo de inventario global (/etc/ansible/hosts). Sin embargo, preferimos usar archivos de configuración y de inventario a nivel de cada proyecto Ansible. Esto permite usar diferentes configuraciones e inventarios en función del proyecto.

El archivo de inventario contiene la lista de máquinas a administrar. Cada máquina aparecerá en una línea y es posible crear grupos de máquinas para lanzar posteriormente scripts Ansible a grupos de máquinas.

Ejemplo 1. Ejemplo de archivo de inventario hosts.cfg usando grupos
# Inventory hosts.cfg file

[controller]
10.0.0.51

[network]
10.0.0.52

[compute]
10.0.0.53
10.0.0.54
10.0.0.55
10.0.0.56

[block]
10.0.0.51

[shared]
10.0.0.63

[object]
10.0.0.61
10.0.0.62

A modo de ejemplo podemos crear una carpeta de trabajo (p.e. cursostic). En esa carpeta guardaremos todos nuestros archivos. Comenzaremos guardando el archivo de configuración (ansible.cfg) y el de inventario (hosts.cfg). En el archivo de inventario colocaremos las máquinas a administrar

Ejemplo 2. Archivo de configuración local ansible.cfg
[defaults]

inventory      = ./hosts.cfg (1)
1 Usar el archivo de inventario situado en la misma carpeta
Ejemplo 3. Archivo de inventario hosts.cfg
# Archivo hosts.cfg de inventario
20.0.1.11
20.0.1.4

Prueba de funcionamiento

$ ansible all -m ping

20.0.1.11 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
20.0.1.4 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

6. Comandos directos

Ejemplo 4. Conocer el uso de disco de las máquinas del inventario
$ ansible all -a "df -h" (1)

20.0.1.11 | CHANGED | rc=0 >>
Filesystem      Size  Used Avail Use% Mounted on
udev            991M     0  991M   0% /dev
tmpfs           201M  3.1M  197M   2% /run
/dev/vda1        20G  2.0G   18G  10% /
tmpfs          1001M     0 1001M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs          1001M     0 1001M   0% /sys/fs/cgroup
tmpfs           201M     0  201M   0% /run/user/1000

20.0.1.4 | CHANGED | rc=0 >>
Filesystem      Size  Used Avail Use% Mounted on
udev            991M     0  991M   0% /dev
tmpfs           201M  3.1M  197M   2% /run
/dev/vda1        20G  2.0G   18G  10% /
tmpfs          1001M     0 1001M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs          1001M     0 1001M   0% /sys/fs/cgroup
tmpfs           201M     0  201M   0% /run/user/1000
1 all hace referencia a que se ejecute en todos los equipos del inventario

6.1. Ejecución de comandos como root

El argumento --become permite ejecutar comandos como root.

Ejemplo 5. Realizar operaciones como root en todos los equipos administrados
ansible all -a "apt update" --become (1)
1 --become ejecuta las operaciones como root en los equipos administrados
Ejemplo 6. Reiniciar todos los equipos del inventario
$ ansible all -a "reboot" --become
Ejemplo 7. Realizar operaciones en un grupo de equipos administrados
ansible webserver -a "apt install apache2" --become (1)
1 webserver indica el grupo de servidores del inventario sobre el que realizar operaciones. Se puede indicar una lista de grupos separados por comas.

Ansible permite el uso de módulos que amplían la funcionalidad básica proporcionada por Ansible. La página de módulos de Ansible ofrece un acceso al listado de módulos agrupados por categorías.

Por ejemplo, el módulo copy copia un archivo del sistema de archivos local al lugar indicado en las máquinas remotas.

Ejemplo 8. Copiar un archivo a las máquinas administradas
ansible all -m copy -a "src=sample.txt dest=/home/ubuntu/sample.txt"

7. Playbooks para operaciones sencillas

Antes de pasar a crear proyectos más completos que incluyan varias operaciones agrupadas en roles comencemos por la creación de playbooks con operaciones sencillas. Esto nos permitirá familiarizarnos con las tareas de Ansible.

Los playbooks y los roles, que veremos más adelante, se escriben en sintaxis YAML, descrita en el Apéndice YAML.

En los playbooks seguiremos la esctrucura siguiente:

  • Nombre del Playbook

  • Indicar si queremos recuperar información de los hosts administrados

  • Hosts sobre los que aplicar el playbook

  • Lista de tareas a realizar

Ejemplo 9. Playbook de ejemplo local.yml para mostrar información local y un mensaje por pantalla
---

- name: Basic playbook run locally (1)
  gather_facts: true (2)
  hosts: localhost (3)
  tasks: (4)
    - name: Doing a ping
      ping:

    - name: Show info
      debug:
        msg: "Machine name: {{ ansible_hostname }}"
1 Nombre del playbook
2 Obtener información de los hosts de destino. No sería necesario porque es la opción predeterminada
3 Hosts sobre los que aplicar las tareas siguientes
4 Lista de tareas a ejecutar

Los playbooks se ejecutan con ansible-playbook, que en su sintaxis más básica ejecuta el playbook que le pasemos como argumento.

Para ejecutar el playbook anterior escribiríamos el comando siguiente:

$ ansible-playbook local.yml

A continuación se muestra el resultado de ejecución del playbook

PLAY [Basic playbook run locally] **********************************************

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

TASK [Doing a ping] ************************************************************
ok: [localhost]

TASK [Show info] ***************************************************************
ok: [localhost] => {
    "msg": "Machine name: ansible-control"
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

En la ejecución de un playbook es posible obtener información de los hosts administrados. Este proceso se conoce como gather facts y de forma predeterminada se obtiene dicha información. El apéndice Obtener información de los nodos administrados ofrece información sobre esta funcionalidad.

7.1. Parámetros de interés para ejecución de playbooks

  • -i archivo_de_inventario: Permite usar un archivo de inventario específico

  • --start-at-task=tarea_de_inicio: Indica la tarea por la que comenzar a ejecutar el playbook

  • --step: Permite ejecutar el playbook paso a paso

  • --become: Ejecuta operaciones como root

El comando siguiente ejecuta paso a paso el playbook mysql.yml como root comenzando por la tarea Update package cache

$ ansible-playbook mysql.yml --become --start-at-task "Update package cache" --step

7.2. Modificación de archivos con blockinfile

El módulo blockinfile inserta, actualiza o elimina un bloque de líneas en un archivo. El texto modificado queda delemitado por líneas que actúan como marcador.

Ejemplo 10. Playbook blockinfile.yml
---

- name: Blockinfile to edit files
  gather_facts: false
  hosts: all
  tasks:
    - name: "Adding Ansible manager and managed nodes to /etc/hosts"
      blockinfile:
        name: /etc/hosts (1)
        block: | (2)
          # Manager
          20.0.1.7 manager

          # Managed-1
          20.0.1.11 managed-1

          # Managed-2
          20.0.1.4 managed-2
        marker: "# {mark} ANSIBLE MANAGED BLOCK manager and managed nodes" (3)
1 Archivo a modificar
2 Bloque de texto a incluir
3 Texto para delimitar el bloque de texto añadido

La ejecución la haremos con ansible-playbook

$ ansible-playbook blockinfile.yml --become

PLAY [Blockinfile to edit files] ***********************************************

TASK [Adding Ansible manager and managed nodes to /etc/hosts] ******************
changed: [20.0.1.4]
changed: [20.0.1.11]

PLAY RECAP *********************************************************************
20.0.1.11                  : ok=1    changed=1    unreachable=0    failed=0
20.0.1.4                   : ok=1    changed=1    unreachable=0    failed=0

Dado que Ansible es idempotente, la ejecución repetida del playbook no añadirá nuevos bloques en cada ejecución. La ejecución de un playbook de Ansible debe entenderse como este el estado deseado para las máquinas administradas. Una modificación sobre los valores del playbook supondría un cambio y al volver a ejecutar el playbook se trasladaría la modificación a las máquinas gestionadas.

A continuación se muestra un extracto del archivo /etc/hosts en las máquinas administradas como resultado de ejecutar el playbook anterior.

127.0.0.1 localhost

....

# BEGIN ANSIBLE MANAGED BLOCK manager and managed nodes (1)
# Manager (2)
20.0.1.7 manager

# Managed-1
20.0.1.11 managed-1

# Managed-2
20.0.1.4 managed-2
# END ANSIBLE MANAGED BLOCK manager and managed nodes (3)
1 Inicio del texto delimitador del bloque
2 Texto introducido
3 Fin del texto delimitador del bloque

7.2.1. Uso de un archivo de variables para los playbooks

Las variables definidas en group_vars/all.yml serán visibles para todos los playbooks del mismo directorio sin necesidad de indicar o incluir nada.

Como ejemplo, vamos a definir un archivo de variables group_vars/all.yml con el nombre y la dirección IP de un conjuntos de máquinas.

Ejemplo 11. El archivo group_vars/all.yml
manager: { name: manager, ip: 20.0.1.7 }
managed_1: { name: managed-1, ip: 20.0.1.11 }
managed_2: { name: managed-2, ip: 20.0.1.4 }

Veamos ahora una revisión del ejemplo del playbook anterior usando variables.

Ejemplo 12. Playbook de blockinfile usando variables
---

- name: Blockinfile to edit files
  gather_facts: false
  hosts: all
  tasks:
    - name: "Adding Ansible manager and managed nodes to /etc/hosts"
      blockinfile:
        name: /etc/hosts
        block: |
          # Manager
          {{ manager.ip }} {{ manager.name }} (1)

          # Managed-1
          {{ managed_1.ip }} {{ managed_1.name }} (2)

          # Managed-2
          {{ managed_2.ip }} {{ managed_2.name }} (3)
        marker: "# {mark} ANSIBLE MANAGED BLOCK manager and managed nodes"
1 Variables para la IP y nombre del nodo manager
2 Variables para la IP y nombre del nodo managed-1
3 Variables para la IP y nombre del nodo managed-2

El apéndice Uso de variables contiene información sobre la forma y los distintos lugares donde se pueden definir variables en Ansible. También se muestra cómo pedir variables en el momento de la ejecución y como iterar sobre ellas.

7.3. Uso de templates para creación de archivos personalizados

Con template podemos incluir archivos en los nodos administrados sustituyendo previamente las variables que incluyan por sus valores correspondientes.

Ejemplo 13. El archivo template de base sample-template.txt
Ejemplo de archivo personsalizado usando templates:

El nodo {{ manager.name }} tiene la IP: {{ manager.ip }}.
El nodo {{ managed_1.name }} tiene la IP: {{ managed_1.ip }}.
El nodo {{ managed_2.name }} tiene la IP: {{ managed_2.ip }}.
Ejemplo 14. Playbook template.yml
---

- name: Template to customize files
  gather_facts: false
  hosts: all
  tasks:
    - name: "Creating customized sample-template.txt in /home/ubuntu/sample-template.txt"
      template: >
        src=/home/ubuntu/cursostic/sample-template.txt
        dest=/home/ubuntu/sample-template.txt
        owner=ubuntu
        group=ubuntu
        mode=0644

El resultado en los nodos administrados:

Ejemplo de archivo personsalizado usando templates:

El nodo manager tiene la IP: 20.0.1.7.
El nodo managed-1 tiene la IP: 20.0.1.11.
El nodo managed-2 tiene la IP: 20.0.1.4.

7.4. Modificación de archivos con lineinfile

El módulo lineinfile asegura que exista una línea con un texto concreto en un archivo. Para la búsqueda se usan expresiones regulares.

Por ejemplo, cuando el nombre de la máquina no está en el archivo /etc/hosts/ aparece un mensaje molesto como el siguiente en la línea de comandos cuando cambiamos al modo superusuario con sudo su -

sudo: unable to resolve host ...

Para solucionarlo basta con añadir 127.0.0.1 seguido del nombre de la máquina al archivo /etc/hosts. A continuación veremos cómo localizar la entrada 127.0.0.1 localhost en el archivo e introducir una línea a continuación para solucionar el molesto mensaje.

Ejemplo 15. El playbook lineinfile.yml
---

- name: Lineinfile to edit files
  hosts: all
  tasks:
    - name: "Adding hostname to /etc/hosts"
      lineinfile:
        path: /etc/hosts (1)
        insertafter: '^127\.0\.0\.1' (2)
        line: 127.0.0.1 {{ ansible_hostname }} (3)
1 Archivo a modificar
2 Buscar la última línea que comienza por 127.0.0.1 para insertar una línea a continuación (insertafter)
3 Insertar la linea con 127.0.0.1 y el nombre de la máquina, obtenido en el proceso de Gathering facts y disponible en la variable ansible_hostname

7.5. Gestión de repositorios APT

El módulo apt_repository permite añadir o eliminar repositorios APT en distribuciones Ubuntu y Debian.

Ejemplo 16. El playbook apt_repository.yml
---

- name: apt_repository to manage APT repositories
  gather_facts: false
  hosts: all
  tasks:
    - name: "Add APT OpenStack repository for Ubuntu Xenial"
      apt_repository:
        repo: "deb http://ubuntu-cloud.archive.canonical.com/ubuntu xenial-updates/ocata main"

Tras ejecutar el playbook podemos comprobar que las máquinas de destino tienen el repositorio disponible mostrando el contenido del archivo

/etc/apt/sources.list.d/ubuntu_cloud_archive_canonical_com_ubuntu.list

El resultado será:

deb http://ubuntu-cloud.archive.canonical.com/ubuntu xenial-updates/ocata main

Para eliminar un repositorio se usaría el parámetro state: absent de apt_repository

Ejemplo 17. El playbook remove-apt_repository.yml
---

- name: apt_repository to manage APT repositories
  gather_facts: false
  hosts: all
  tasks:
    - name: "Add APT OpenStack repository for Ubuntu Xenial"
      apt_repository:
        repo: "deb http://ubuntu-cloud.archive.canonical.com/ubuntu xenial-updates/ocata main"
      state: absent

Tras ejecutar el playbook podemos comprobar que las máquinas de destino ya no tienen el repositorio disponible y que no existe el archivo

/etc/apt/sources.list.d/ubuntu_cloud_archive_canonical_com_ubuntu.list

7.6. Instalación de paquetes

El módulo apt se encarga de la gestión de paquetes en Ubuntu y Debian. Cuando queremos instalar una lista de paquetes definiremos la lista de paquetes y normalmente lo haremos con una variable

Ejemplo 18. El playbook apt.yml
---

- name: Blockinfile to edit files
  gather_facts: false
  hosts: all
  vars: (1)
    packages:
      - mysql-server
      - phpmyadmin

  tasks:
    - name: Install packages old style with explicit list
      apt:
        name: "{{ item }}" (2)
      with_items: (3)
        - mysql-server
        - phpmyadmin

    - name: Install packages old style using variables
      apt:
        name: "{{ item }}"
      with_items:
        - "{{ packages }}" (4)


    - name: Install packages new style with explicit list
      apt:
        name: ['mysql-server', 'phpmyadmin'] (5)

    - name: Install packages new style using variables
      apt:
        name: "{{ packages }}" (6)
1 Definición de variables en el propio playbook
2 {{ item }} representa la variable de iteración de un bucle with_items
3 Especificación de un bucle
4 Uso de una variable para suministrar los valores sobre los que iterar
5 Sintaxis compacta especificando una lista en lugar de usar un bucle
6 Sintaxis compacta usando una variable que proporciona los elementos de iteración

Para eliminar paquetes usamos el parámetro state: absent en apt.

Ejemplo 19. Playbook para eliminar un paquete (remove-apt.yml)
---

- name: Remove apt packages
  gather_facts: false
  hosts: all

  tasks:
    - name: Removing phpmyadmin
      apt:
        name: phpmyadmin
        state: absent

7.7. Ejecución de comandos en máquinas administradas

El módulo shell toma un comando como argumento y lo ejecuta en la máquina remota.

Ejemplo 20. Playbook shell.yml para copia de un archivo
---

- name: Run commands with shell
  hosts: all

  tasks:
    - name: Copy sample-template.txt to sample-template.bak
      shell: 'cp sample-template.txt sample-template.bak' (1)
      args:
        chdir: /home/ubuntu (2)
1 Comando a ejecutar
2 Directorio sobre el que ejecutar el comando

7.8. Manejo de archivos

El módulo file permite configurar atributos de archivos y directorios. También permite la creación y eliminación de archivos.

Ejemplo 21. Playbook para gestión de archivos file.yml
---

- name: Run file commands
  hosts: all
  gather_facts: false

  tasks:
    - name: Create a directory
      file: (1)
        path: /home/ubuntu/myfolder
        state: directory
        owner: ubuntu
        group: ubuntu

    - name: Delete sample-template.bak file
      file:
        path: /home/ubuntu/sample-template.bak
        state: absent (2)
1 Creación de un directorio y modificación del propietario
2 Eliminar el archivo

7.9. Reiniciar servicios

El módulo service permite la administración de servicios en nodos remotos.

Ejemplo 22. Playbook para el reinicio de servicios services.yml
---

- name: Restart services
  hosts: all
  gather_facts: false

  tasks:
    - name: Restart MySQL and Apache
      service:
        name: "{{ item }}" (1)
        state: restarted
      with_items: (2)
        - mysql
        - apache2
1 Elemento del bucle sobre el que se está iterando
2 Lista de servicios sobre los que iterar

7.10. Reiniciar una batería de servidores

Podemos usar el módulo shell para lanzar un reboot sobre los nodos adninistrados. Además, podemos combinar esta operación con el módulo wait_for_connection que espera la cantidad de segundos que le indiquemos. Una vez recuperada la conexión dentro de ese periodo, continúa la ejecución del playbook.

Ejemplo 23. Playbook reboot-and-wait.yml
---

- name: Reboot and wait
  hosts: all

  tasks:
    - name: Rebooting
      shell: sleep 2 && reboot
      async: 1
      poll: 0

    - name: Waiting for rebooting
      wait_for_connection:
        delay: 15
        sleep: 10
        timeout: 300

    - debug:
        msg: "{{ inventory_hostname }} is up and running"

7.11. Descarga condicional de archivos

El módulo fetch permite la descarga de archivos de las máquinas gestionadas al nodo manager.

Podemos combinar este módulo con la ejecución condicional que permite por ejemplo descargar el archivo sólo si la máquina remota tiene cierto nombre. La cláusula when permite la evaluación de expresiones.

A modo de ejemplo, usaremos los hechos (facts) recuperados de las máquinas remotas para obtener su nombre y ejecutar la tarea de descarga de archivos sólo si el nombre coincide con el que buscamos.

Otro uso podría ser la instalación de paquetes con yum o apt en función de si la distribución es de la familia (ansible_facts['os_family']) Red Hat o Debian, respectivamente.

Ejemplo 24. Playbook para descarga condicional de archivos conditions.yml
---

- name: Get remote files
  hosts: all

  tasks:
    - name: Get remote file checking conditions
      fetch:
        src: /etc/hosts
        dest: /home/ubuntu/hosts-from-managed-1
        flat: yes (1)
      when:
        ansible_facts['hostname'] == "ansible-managed-1" (2)
1 Descarga el archivo sin añadir el nombre de la máquina y la ruta completa del archivo. El comportamiento predeterminado descargaría el archivo en /home/ubuntu/hosts-from-managed-1/20.0.1.11/etc/hosts
2 La tarea sólo se ejecuta en aquellos hosts cuyo hostname sea el indicado

8. Organización básica de proyectos Ansible

En un modo de funcionamiento normal de Ansible las tareas no suelen estar directamente en los playbooks. En cambio, se suelen organizar las tareas en roles, y los playbooks incluirán una lista de roles a ejecutar, junto con los hosts a los que van dirigidos.

Ejemplo 25. Ejemplo de playbook basado en roles
- hosts: all (1)
  become: true
  roles: (2)
    - basic

- hosts: controller
  become: true
  roles:
    - ntp_server

- hosts: all:!controller (3)
  become: true
  roles:
    - ntp_others

- hosts: all
  become: true
  roles:
    - openstack_packages

- hosts: controller
  become: true
  roles:
    - sql_database
    - rabbitmq
    - memcached
1 Hosts sobre los que se ejecutarán los roles indicados.
2 Lista de roles a ejecutar sobre los hosts indicados
3 Ejecutar en todos los hosts excepto controller

Los roles se definen en carpetas que le dan nombre al rol. Además, los roles se crean de acuerdo a una estructura de subcarpetas establecida, que es la siguiente:

  • tasks: Incluye el archivo main.yml con la lista de tareas a ejecutar. La ejecución de una tarea puede desencadenar la ejecución de acciones (p.e. reiniciar un servicio tras modificar un archivo de configuración). La tarea notifica una acción pendiente. Las acciones notificadas se ejecutarán tras finalizar todas las tareas del rol.

  • handlers: Incluye el archivo main.yml con la lista de acciones paras las notificaciones pendientes.

  • templates: Incluye las plantillas de archivos que se desplegarán en las máquinas remotas previa sustitución de variables. Los archivos se colocarán en una estructura de carpetas similar a la que tendrán en el host de destino tomando como raíz la carpeta handlers. Por ejemplo, una plantilla para personalizar los hosts en las máquinas de destino se colocaría en handlers/etc/hosts, ya que en las máquinas de destino se coloca en (/etc/hosts).

Ejemplo 26. Ejemplo de organización de un rol
ntp_server/
├── handlers
│   └── main.yml
├── tasks
│   └── main.yml
└── templates
    └── etc
        └── chrony
            └── chrony.conf

Cuando vamos a crear un rol, podemos crear la carpeta del rol y la estructura de subcarpetas con un solo comando. El comando siguiente crearía la carpeta para el rol ntp_server y las subcarpetas para handlers, tareas y templates.

$ mkdir -p ntp_server/{handlers,tasks,templates}

Un proyecto Ansible se organizaría de esta forma:

├── ansible.cfg (1)
├── group_vars (2)
│   └── all.yml
├── hosts.cfg (3)
├── playbook-1.yml (4)
├── playbook-2.yml
├── ...
├── roles (5)
│   ├── barbican
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── etc
│   │           └── barbican
│   │               ├── barbican-api-paste.ini
│   │               └── barbican.conf
│   ├── ...
│   ├── heat
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── etc
│   │           └── heat
│   │               └── heat.conf
│   ├── ...
└── site.yml (6)
1 Archivo de configuración del proyecto (p.e. para indicar el archivo de inventario)
2 Variables accesibles a todos los playbooks
3 Archivo de inventario de hosts
4 Playbooks del proyecto
5 Roles del proyecto
6 Playbook opcional que contiene la llamada a todos los playbooks del proyecto

Si un proyecto Ansible contiene gran cantidad de playbooks, es conveniente crear un nuevo playbook que se encargue de llamarlos a todos. Esto se realiza en Ansible mediante include

Por ejemplo, site.yml contiene la llamada a todos los playbooks que realizan un despliegue complejo:

- include: playbook-basic.yml
- include: playbook-keystone.yml
- include: playbook-glance.yml
- include: playbook-nova.yml
- include: playbook-neutron.yml
...
Ejemplo 27. Ejemplo de tasks/main.yml con las tareas de un rol
- name: Install chrony
  apt:
    name: chrony
    state: latest

- name: Setup chrony on controller
  template: > (1)
    src=etc/chrony/chrony.conf
    dest=/etc/chrony/chrony.conf
    owner=root
    group=root
    mode=0644
  notify: restart chrony (2)
1 Uso de un archivo template
2 Notificación de ejecución de una acción al finalizar el rol
Ejemplo 28. Ejemplo de template templates/etc/chrony/chrony.conf
pool 2.debian.pool.ntp.org offline iburst

server {{ntp_server}} iburst (1)
allow {{management_network}}/24

keyfile /etc/chrony/chrony.keys
commandkey 1
driftfile /var/lib/chrony/chrony.drift
log tracking measurements statistics
logdir /var/log/chrony
maxupdateskew 100.0
dumponexit
dumpdir /var/lib/chrony
logchange 0.5
hwclockfile /etc/adjtime
rtcsync
1 Uso de variables. El archivo se creará en los servidores de destino con los valores asignados a las variables (p.e. ntp_server: 1.es.pool.ntp.org)
Ejemplo 29. Ejemplo de handlers/main.yml
- name: restart chrony (1)
  service:
    name: chrony
    state: restarted
1 El nombre del handler tiene que corresponder con el indicado en la cláusula notify de la tarea

9. Creación de un playbook para el despliegue de una aplicación PHP desde un repositorio GitHub

Ansible dispone de un módulo git que permite realizar operaciones git en los equipos administrados. A continuación se muestra un ejemplo de tarea para clonar un repositorio de GitHub en la carpeta /var/www/html/diariostic

- name: Clone diariostic repository
  git:
    repo: 'https://github.com/ualmtorres/diariostic.git'
    dest: /var/www/html/diariostic

Veamos un ejemplo de playbook (diariostic.yml) que se ejecutará sobre un equipo al que denominamos diariostic, que estará incluido en el archivo de inventario de hosts. El playbook incluye un rol, denominado diariostic.

Ejemplo 30. Playbook diariostic.yml
---

- name: Deploy diariostic PHP application from scratch
  hosts: diariostic
  roles:
    - diariostic

El rol diariostic descarga Apache, PHP y el repositorio de aplicación. Además, personaliza Apache para que trabaje sobre el puerto 8080 en lugar de sobre el 80.

Ejemplo 31. Rol diariostic
---

- name: Update package cache
  apt:
    update_cache: yes

- name: Install Apache and PHP
  apt:
    name: ['apache2', 'php']

- name: Clone diariostic repository
  git:
    repo: 'https://github.com/ualmtorres/diariostic.git'
    dest: /var/www/html/diariostic

- name: Change port to 8080 in /etc/apache2/ports.conf
  lineinfile:
    path: /etc/apache2/ports.conf
    regexp: '^Listen 80'
    line: 'Listen 8080'

- name: Change port to 8080 in /etc/apache2/sites-enabled/000-default.conf
  lineinfile:
    path: /etc/apache2/sites-enabled/000-default.conf
    regexp: '^<VirtualHost \*:80>'
    line: '<VirtualHost *:8080>'

- name: Restart Apache
  service:
    name: apache2
    state: restarted

Después de ejecutar el playbook con

$ ansible-playbook diariostic.yml --become

la aplicación estará disponible en la carpeta diariostic del servidor aprovisionado.

diariostic.png

10. Ansible Galaxy

Los roles son un concepto básico en Ansible. Con objeto de poder reutilizar roles en diferentes playbooks es interesante organizar los roles en carpetas independientes y tener un repositorio para cada uno de ellos.

Dada la posibilidad entonces de organizar así los roles se ha organizado una comunidad para la publicación e intercambio de roles denominada Ansible Galaxy. Cada rol en Ansible Galaxy está enlazado a su código fuente.

10.1. Instalación de un rol de Ansible Galaxy

Es conveniente disponer entonces de una carpeta donde tengamos almacenados todos los roles (p.e. roles). Después, en un nivel superior tendremos los playbooks y los archivos de inventario correspondientes a cada proyecto. Pero quizá sería mejor tener todos los playbooks y archivos de inventario en una carpeta al mismo nivel que los roles. En este caso los playbooks subirían un nivel y luego bajarían por la carpeta roles para usar los roles correspondientes.

Ejemplo 32. Organización de playbooks y roles
.
├── playbooks
│   ├── nginx-hosts.cfg
│   ├── nginx-playbook.yml
│   ├── php-hosts.cfg
│   ├── php-playbook.yml
│   ├── phpwebserver-hosts.cfg
│   └── phpwebserver-playbook.yml
└── roles
    ├── geerlingguy.git
    │   ├── ...
    ├── geerlingguy.php
    │   ├── ...
    ├── ualmtorres.apache
    │   ├── ...
    └── ualmtorres.apache2
        ├── ...

Sea roles la carpeta donde guardamos todos nuestros roles y sea geerlingguy.php el rol que queremos instalar, disponible en Ansible Galaxy. Para descargar e instalar el rol localmente escribiríamos:

$ ansible-galaxy install geerlingguy.php –p roles

Luego, en nuestra carpeta de playbooks, crearíamos el archivo de inventario de hosts para nuestro proyecto y el del playbook.

Ejemplo 33. El archivo php-hosts.cfg
20.0.1.11
20.0.1.4
Ejemplo 34. El archivo php-playbook.yml
---
- hosts: all
  become: true
  roles:
    - ../roles/geerlingguy.php

Para ejecutar este playbook desde la carpeta de playbooks basta con:

$ ansible-playbook -i nginx-hosts.cfg nginx-playbook.yml

10.2. Creación de un rol con Ansible Galaxy

Ansible Galaxy también permite la creación de roles. Esto tiene como ventaja la inicialización de una serie de carpetas y archivos que hará que nuestros roles sigan los estándares establecidos para el desarrollo en Ansible y seguidos por la comunidad de Ansible.

Para crear un rol, sobre la carpeta roles ejecutaremos el comando siguiente para crear un rol denominado ualmtorres.apache. Seguiremos como regla de nomenclatura un nombre de usuario (p.e. el nombre de usuario en Ansible Galaxy) seguido de punto (.) y el nombre del rol. Así, podríamos tener varios roles similares, pero de usuarios diferentes y usar cada uno de ellos según corresponda.

$ ansible-galaxy init ualmtorres.apache

Esto creará la estructura siguiente:

ualmtorres.apache
├── defaults (1)
│   └── main.yml
├── files (2)
├── handlers (3)
│   └── main.yml
├── meta (4)
│   └── main.yml
├── README.md (5)
├── tasks (6)
│   └── main.yml
├── templates (7)
├── tests (8)
│   ├── inventory
│   └── test.yml
└── vars (9)
    └── main.yml
1 Valores por defecto para varables usadas en el rol. Serán sobrescritas por las definidas en vars
2 Archivos requeridos para la ejecución del rol. Estos archivos, a diferencia de los situados en templates no pueden ser mmanipulados.
3 Carpeta de handlers con las tareas pendientes de ejecución generadas por notify en tareas ya ejecutadas (p.e. reiniciar servicios tras una modificación de la configuración)
4 Metadatos que usar Ansible Galaxy para publicar el rol (p.e. versión mínima de Ansible, plataformas soportadas, dependencias, …​)
5 Información descriptiva y de uso del rol
6 Tareas del rol
7 Archivos para procesar en el proceso de despliegue y que se modificarán de acuerdo a las variables que usen
8 Casos de prueba para soporte a sistemas de integración continua como Jenkins o Travis
9 Variables usadas en el rol. Sobrescriben a las que aparezcan en defaults

Por ejemplo, podemos incluir la tarea siguiente en el archivo tasks/main.yml para asegurar que Apache queda instalado.

---
# tasks file for ualmtorres.apache
- name: Install Apache
  apt: name=apache2 state=present

10.3. Organización de playbooks y roles

Con el paso del tiempo, la carpeta roles irá creciendo con los roles usados y desarrollados. Todos ellos serán reutilizados en los distintos proyectos en lo que sean últiles. A continuación se muestra un ejemplo de la organización propuesta para playbooks y roles.

.
├── playbooks (1)
│   ├── nginx-hosts.cfg
│   ├── nginx-playbook.yml
│   ├── php-hosts.cfg
│   ├── php-playbook.yml
│   ├── phpwebserver-hosts.cfg
│   └── phpwebserver-playbook.yml
└── roles (2)
    ├── geerlingguy.git (3)
    │   ├── ...
    ├── geerlingguy.php (4)
    │   ├── ...
    └── ualmtorres.apache (5)
        ├── ...
1 Carpeta para playbooks y arhivos de inventario
2 Carpeta para roles
3 Rol de instalación de Git
4 Rol de instalación de PHP
5 Rol propio de instalación de Apache

Si ahora queremos desarrollar un playbook con Apache y PHP que use los roles ualmtorres.apache y geerlingguy.php, bastaría con crear un nuevo playbook como el siguiente

Ejemplo 35. Playbook phpwebserver-playbook.yml para la instalación de un servidor web Apache y PHP
---
- hosts: all
  become: true
  roles:
    - ../roles/ualmtorres.apache
    - ../roles/geerlingguy.php

Para ejecutarlo, desde la carpeta de playbooks escribiríamos:

$ ansible-playbook -i phpwebserver-hosts.cfg phpwebserver-playbook.yml (1)
1 El archivo phpwebserver-hosts.cfg contendría la lista de hosts en la que se desea ejecutar el playbook

También se podrían sacar los archivos de inventario de la carpeta de playbooks y colocarlos en una carpeta aparte (p.e. inventory).

11. Playbook para inicializar una base de datos MySQL

En este ejemplo veremos cómo inicializar un servidor con MySQL con una base de datos precargada. El servidor MySQL lo instalaremos con un rol de Ansible Galaxy. El script de la base de datos lo descargaremos con una tarea Ansible para la descarga de archivos. La carga la haremos con una tarea Ansible del módulo MySQL para la carga de datos.

11.1. Instalación de MySQL

Un nivel por encima de nuestra carpeta de roles instalaremos el rol de MySQL de geerlingguy.

$ ansible-galaxy install geerlingguy.mysql -p roles

El archivo geerlingguy.mysq/defaults/main.yml contiene variables para la personalización de la instalación de MySQL. Cambiaremos los valores de las dos variables que establecen la contraseña del usuarios root

...
mysql_user_password: changeme
...
mysql_root_password: changeme
...

Crearemos un playbook (mysql.yml) para la instalación del rol

---

- name: MySQL Playbook
  hosts: dbserver
  roles:
    - geerlingguy.mysql

Ejecutaremos el playbook

$ ansible-playbook mysql.yml --become

Esto habrá instalado MySQL en el host dbserver. La contraseña del usuario root será changeme.

11.2. Creación de la BD

La creación de la base de datos la haremos en dos pasos. Primero descargaremos a la máquina administrada el script que contiene el código de inicialización de la base de datos. Después, importaremos el script descargado a la base de datos.

Ejemplo 36. Rol (crearbdSG) para descargar el script SQL e importarlo a la base de datos
---

- name: Download SG.sql
  get_url: (1)
    url: https://raw.githubusercontent.com/ualmtorres/docker_customer_catalog/master/init.sql
    dest: /home/ubuntu/SG.sql

- name: Import SG database
  mysql_db: (2)
    name: SG
    state: import
    target: /home/ubuntu/SG.sql (3)
1 Descargar el archivo indicado en la ruta especificada en dest
2 El módulo mysql_db permite la creación y eliminación de bases de datos, así como operaciones de importación y exportación
3 Ruta de la máquina remota en la que se encuentra el archivo a importar

A continuación, modificaremos el playbook anterior (mysql.yml) para añadir el nuevo rol

---

- name: MySQL Playbook
  hosts: dbserver
  roles:
    - geerlingguy.mysql
    - crearbdSG (1)
1 Nuevo rol añadido para la carga de datos

No es necesario ejecutar el playbook completo desde el principio. Podemos indicar que se comience a ejecutar a partir de una tarea determinada con el parámetro start-at-task

$ ansible-playbook mysql.yml --become --start-at-task "Download SG.sql"

11.3. Añadir una aplicación PHP a la base de datos SG

A continuacion podríamos crear otro playbook para añadir al host anterior un servidor Apache y un intérprete PHP. Como ejemplo, podríamos descargar un script PHP que muestra el listado de clientes de la base de datos SG.

El script está configurado sólo para una prueba de concepto y usa la cuenta de root y la contraseña en el mismo código. La base de datos se denomina SG, y se accede a través de la cuenta root y con la contraseña changeme.

---

- name: Update package cache
  apt:
    update_cache: yes

- name: Install Apache and PHP
  apt:
    name: ['apache2', 'php', 'libapache2-mod-php', 'php-mysql']

- name: Restart Apache
  service:
    name: apache2
    state: restarted

- name: Download customer_catalog
  get_url:
    url: https://raw.githubusercontent.com/ualmtorres/CustomerCatalog/master/index.php
    dest: /var/www/html/index.php

Anexo A: YAML

YAML es un lenguaje de serialización de datos legible. Permite definir tipos de datos comunes, como listas, mapas y valores escalares.

YAML es sensible a los espacios en blanco y usa indentación para el anidado de datos.

A.1. Inicio de un documento YAML

Es posible añadir dos directivas al inicio de los documentos YAML (%YAML y %TAG), aunque en la práctica no se suelen usar.

  • %YAML: Especifica la versión YAML del documento

  • %TAG: Define un tag. Los tags se usan para definir tipos de datos en documentos YAML.

Después de las dos directivas se añade una línea con tres guiones (---) para marcar el inicio del documento YAML. La mayoría de los documentos YAML comienzan directamente con los tres guiones (---) ignorando el uso de las directivas %YAML y %TAG.

A.2. Escalares

Usaremos valores escalares para cadenas y números

---
name: Michael
power_level: 9001

A.3. Listas

Podemos definir listas de dos formas: En un listado por líneas en el que cada item aparecerá indentando y con un guión, o bien de forma compacta separando los items por comas y encerrando los elementos de la lista entre corchetes

---
ansible_statements:
- Easy to learn
- Powerful
- Extensive module support
---
ansible_statements: [Easy to learn, Powerful, Extensive module support]

A.4. Mapas

Un mapa permite definir pares clave→valor. También son conocidos como arrays asociativos o hashmaps. Anteriormente ya usamos un mapa

---
name: Michael
power_level: 9001

El mapa es el todo, que está formado por pares clave→valor. De forma compacta, podemos expresar el mapa anterior como:

---
{ name: Michael, power_level: 9001 }

Pero podemos definir estructuras más complejas:

---
person:
    first_name: Michael
    last_name: Heap
    skills: (1)
        - Ansible
        - Golang
        - Python
        - PHP
    likes: [dogs, walking, programming] (2)
    favorites: (3)
        drink: Pepsi Max
        color: Red
    other: (4)
        - key: value (5)
          another: val
        - key: foo
          another: bar (6)
1 Lista
2 Lista compacta
3 Mapa
4 Mapa de listas
5 Mapa
6 Mapa

A.5. Literales

Permiten definir cadenas largas

message: >
    This is a message that is
    going to span several lines
    but is going to be placed on
    a single line when evaluated

Si usamos el operador pipe (|) respetará los saltos de línea definidos en el literal

message: |
    This is a message that is
    going to span several lines
    whilst keeping whitespace
    intact

Anexo B: Obtener información de los nodos administrados

Al lanzar la ejecución de un playbook se ejecuta una tarea que recopila información sobre los hosts sobre los que se lanza el playbook. Entre esta información se encuentra información del procesador, red, fecha y hora, variables de entorno y gran cantidad de información de los sistemas remotos.

El comando siguiente muestra la información que se recupera de los hosts remotos:

$ ansible all -m setup

Para acceder a la información recopilada usaremos la variable hostvars. El ejemplo siguiente muestra la recuperación de la dirección IP de una interfaz de red de un equipo remoto.

hostvars['myserver.com']['ansible_ens3']['ipv4']['address']

También se puede usar la notación punto (.) para navegar por los distintos elementos:

hostvars['20.0.1.4'].ansible_ens3.ipv4.address

También podemos usar ansible_facts para acceder a información de los hosts remotos.

---
- hosts: all
  tasks:
    - debug: msg="Nombre {{ ansible_facts.nodename }} Procesador {{ ansible_facts.processor }}"

Para desactivar la recopilación de información de los sistemas remotos añadiremos gather_facts: false al playbook. Esto hará que la ejecución sea más rápida en aquellos casos en que no necesitemos obtener información sobre los sistemas remotos.

---
- hosts: all
  gather_facts: false (1)
  vars_prompt:
    - name: your_name
      prompt: "What is your name?"
  tasks:
    - debug: msg="Hello {{your_name}}"
1 Desactivación de recopilación de información de hosts remotos

Anexo C: Uso de variables

Las variables en Ansible son gestionadas por el motor de plantillas Jinja2. Jinja2 propociona sustitución de variables usando la sintaxis de doble llave {{ variable }}.

En Ansible se pueden definir variables a varios niveles, cada uno con un nivel de prioridad. Las variables definidas en variables con mayor nivel de prioridad sobrescriben los valores definidos en lugares con mayor nivel de prioridad.

Niveles de prioridad crecientes de variables en Ansible
  • command line values (eg “-u user”)

  • role defaults

  • inventory file or script group vars

  • inventory group_vars/all

  • playbook group_vars/all

  • inventory group_vars/*

  • playbook group_vars/*

  • inventory file or script host vars

  • inventory host_vars/*

  • playbook host_vars/*

  • host facts / cached set_facts

  • play vars

  • play vars_prompt

  • play vars_files

  • role vars (defined in role/vars/main.yml)

  • block vars (only for tasks in block)

  • task vars (only for the task)

  • include_vars

  • set_facts / registered vars

  • role (and include_role) params

  • include params

  • extra vars (always win precedence)

C.1. Definición de variables para playbooks

En group_vars/all.yml estableceremos las variables que queremos que sean comunes a todos los playbooks.

nodes_by_name:
    controller: {name: testcontroller, type: controller, management_ip: 10.0.0.51, tunnel_ip: 10.0.1.51, provider_ip: 192.168.64.18}
    network: {name: testnetwork, type: network, management_ip: 10.0.0.52, tunnel_ip: 10.0.1.52, provider_ip: 192.168.64.19}
    compute01: {name: testcompute01, type: compute, management_ip: 10.0.0.53, tunnel_ip: 10.0.1.53}
    compute02: {name: testcompute02, type: compute, management_ip: 10.0.0.54, tunnel_ip: 10.0.1.54}
    compute03: {name: testcompute03, type: compute, management_ip: 10.0.0.55, tunnel_ip: 10.0.1.55}
    compute04: {name: testcompute04, type: compute, management_ip: 10.0.0.56, tunnel_ip: 10.0.1.56}
    block: {name: testcontroller, type: block, management_ip: 10.0.0.51, tunnel_ip: 10.0.1.51}
    object01: {name: testobject01, type: object, management_ip: 10.0.0.61, tunnel_ip: 10.0.1.61}
    object02: {name: testobject02, type: object, management_ip: 10.0.0.62, tunnel_ip: 10.0.1.62}
    shared: {name: testshared, type: shared, management_ip: 10.0.0.63, tunnel_ip: 10.0.1.63, provider_ip: 10.0.0.63}

En las tareas o en las plantillas de archivos podremos acceder a estos valores posteriormente con la notacion punto (.). Como las variables están definidas en group_vars/all.yml no tendremos que indicar nada para poder acceder a sus valores.

{{ nodes_by_name.controller.management_ip }}

C.2. Definición de variables en archivos

Ejemplo 37. Archivo variables.yml
---
username: johndoe
fullname: John Doe
Ejemplo 38. Archivo playbook-variables-en-archivo.yml
---
- hosts: all
  vars_files:
    - variables.yml
  tasks:
    - debug: msg="Username {{ username }}"
    - debug: msg="Nombre completo {{ fullname }}"

C.3. Definición local de variables

---
- hosts: all
  vars:
    username: johndoe
    fullname: John Doe
  tasks:
    - debug: msg="Username {{ username }}
    - debug: msg="Nombre completo {{ fullname }}

C.4. Paso de variables en la línea de comandos

Usaremos el parámetro --extra-vars o -e para pasar la lista de pares variable valor la línea de comandos. Esta opción sobrescribirá cualquier valor asignado previamente

$ ansible-playbook playbook-variables-en-archivo.yml -e 'username=mtorres fullname="Manuel Torres"'

De forma predeterminada los valores son pasados como cadenas. Si necesitamos pasar valores númericos, booleanos, listas u otro tipo, las variables se deben pasar como JSON

$  ansible-playbook playbook-variables-en-archivo.yml -e '{"username":"mtorres", "fullname":"Manuel Torres"}'

C.5. Petición de variables en la ejecución

Ejemplo 39. Ejemplo de petición de petición de variables en la ejecución
---
- hosts: all
  vars_prompt:
    - name: your_name
      prompt: "What is your name?"
  tasks:
    - debug: msg="Hello {{your_name}}"

C.6. Iteración sobre variables

---
- hosts: all
  become: true
  vars_files:
    - utilities.yml (1)
  tasks:
    - name: Instalar utilidades
      apt:
        name: "{{ utilities }}" (2)
        state: present
1 Archivo que contiene la lista de paquetes a instalar
2 Variable con la lista de paquetes a instalar
Iteración en versiones anteriores de Ansible

En versiones anteriores de Ansible, el recorrido de la lista de paquetes anterior se habría hecho iterando sobre la lista con una construcción `with_items_.

---
- hosts: all
  become: true
  vars_files:
    - utilities.yml
  tasks:
    - name: Instalar utilidades
      apt:
        name: "{{ item }}" (1)
        state: present
      with_items: "{{ utilities }}" (2)
1 {{ item }} es la forma usada para iterar sobre la variable indicada en `with_items
2 with_items indica la variable sobre la que iterar

Anexo D: Tareas ad-hoc habituales

D.1. Reiniciar servidores

$ ansible all -a "reboot" --become

Anexo E: Administración de OpenStack

Con Ansible podemos automatizar la creación de proyectos y usuarios, así como su configuración inicial. Esto nos ayuda en la tediosa tarea de configuración de la red del proyecto, el router de conexión a la red externa, cuotas, otros usuarios del proyecto, y demás.

Ansible dispone de gran cantidad de módulos de interacción con OpenStack. Para poder usar estos módulos tendremos que pasar los parámetros de conexión del administrador a través de variables o usando un archivo que guarde los datos de conexión.

Si optamos por usar un archivo para la conexión, el archivo se almacena en /etc/openstack/clouds.yaml en la máquina de control de OpenStack. Allí, crearemos entradas dentro del elemento clouds:

Ejemplo 40. Archivo /etc/openstack/clouds.yaml
clouds:
  openstacktest: (1)
    auth:
      auth_url: http://myopenstack.com:35357/v3 (2)
      username: admin
      password: changeme (3)
      project_name: admin
      user_domain_name: Default
      project_domain_name: Default
    identity_api_version: "3"
    image_api_version: "2"
1 Nombre que usaremos en los scripts Ansible para referirnos a esta conexión
2 Endpoint de autenticación
3 Contraseña del administrador

Usaremos un archivo de variables para configurar varios valores:

  • Datos de red de los proyectos creados, como red externa, CIDR de las redes creadas y DNS.

  • Quotas

  • Datos de proyecto y usuario (nombre de proyecto, login, password, nombre completo, email y si se aplica o no la cuota indicada -si no se aplica la cuota indicada se aplicará la cuota predeterminada).

  • Otros usuarios miembros de los proyectos. Esto es útil para introducir supervisores en los proyectos creados (p.e. los profesores de una asignatura serán incluidos en los proyectos de cada alumno).

Ejemplo 41. Archivo vars/users.yml
network: "ext-net"
cidr: 30.0.0.0/24
dns:
  - 150.214.156.2
  - 8.8.8.8

#Para la cuota predetermianda, poner state a absent
quota: {
  state: present,
  instances: 10,
  cores: 20,
  ram: 324800,
  gigabytes: 60,
  backups: 0,
  backup_gigabytes: 0,
  floatingip: 10,
  gigabytes_lvm: 60,
  snapshots: 0,
  snapshots_lvm: 0,
  volumes: 2,
  volumes_lvm: 2
}

watchers:
  - {username: "michael.jackson", role: "user"}
  - {username: "patty.smith", role: "user"}

projects:
  - {project: "jsantamaria", user: "Juan Santamaría", email: "jsantamaria@nothing.com", password: "changeme", quota: "present"} (1)
  - {project: "jgarcia", user: "Josefa García", email: "jgarcia@nothing.es", password: "changeme", quota: "present"}
  - {project: "pgomez", user: "Pedro Gómez", email: "pgomez@norhing.es", password: "changeme", quota: "absent"} (2)
1 Con quota: present indicamos que se aplican las cuotas configuradas al proyecto
2 Con quota: absent indicamos que no se aplican las cuotas configuradas al proyecto. Se aplicarán las cuotas predeterminadas
Ejemplo 42. Rol setup-new-project para la creación y configuración de proyectos y usuarios
- name: Include var file
  include_vars:
    file: users.yml

- name: Create a project
  os_project:
    cloud=openstacktest (1)
    state=present
    name={{ item.project }}
    description="Proyecto de {{ item.user }}"
    enabled=True
    domain=default
  with_items:
    - "{{ projects }}"

- name: Create the user for the project
  os_user:
    cloud=openstacktest
    state=present
    name={{ item.project }}
    password={{ item.password }}
    description={{ item.user }}
    update_password=on_create
    email={{ item.email }}
    default_project={{ item.project }}
    domain=default
  with_items:
    - "{{ projects }}"

- name: Grant user role on user in the project
  os_user_role:
    cloud=openstacktest
    user={{ item.project }}
    role=user
    project={{ item.project }}
  with_items:
    - "{{ projects }}"

- name: Grant watchers to the projects
  os_user_role:
    cloud: openstacktest
    project: "{{ item[0].project }}"
    user: "{{ item[1].username }}"
    role: "{{ item[1].role }}"
  with_nested:
    - "{{ projects }}"
    - "{{ watchers }}"
  ignore_errors: True

- name: Create the network of the project
  os_network:
    cloud=openstacktest
    state=present
    name="{{ item.project }}-net"
    project={{ item.project }}
  with_items:
    - "{{ projects }}"

- name: Create the subnet
  os_subnet:
    cloud=openstacktest
    state=present
    network_name="{{ item.project }}-net"
    name="{{ item.project }}-subnet"
    cidr={{ cidr }}
    dns_nameservers={{ dns }}
    project={{ item.project }}
  with_items:
    - "{{ projects }}"

- name: Create the router connecting the network and the subnet
  os_router:
    cloud=openstacktest
    state=present
    name="{{ item.project }}-router"
    network={{ network }}
    interfaces="{{ item.project }}-subnet"
    project={{ item.project }}
  with_items:
    - "{{ projects }}"

- name: Apply quotas
  os_quota:
    cloud: openstacktest
    name: "{{ item.project }}"
    instances: " {{ quota.instances}} "
    cores: " {{ quota.cores}} "
    ram: " {{ quota.ram }} "
    gigabytes: " {{ quota.gigabytes }} "
    backups: " {{ quota.backups }} "
    backup_gigabytes: " {{ quota.backup_gigabytes }} "
    floatingip: " {{ quota.floatingip }} "
    gigabytes_types:
      gigabytes_lvm: " {{ quota.gigabytes_lvm }} "
    snapshots: " {{ quota.snapshots }} "
    snapshots_types:
      snapshots_lvm: " {{ quota.snapshots_lvm }} "
    volumes: " {{ quota.volumes}} "
    volumes_types:
      volumes_lvm: " {{ quota.volumes_lvm }} "
  with_items:
    - "{{ projects }}"
  when: item.quota == "present"
1 Referencia al elemento de credenciales del archivo /etc/openstack/clouds.yaml en el nodo de control de OpenStack

E.1. Configuración del nodo de control de Ansible

export LC_ALL=C
The root cause is: your environment variable LC_ALL is missing or invalid somehow
If you keep getting the error in new terminal windows, add it at the bottom of your .bashrc file.

sudo apt-get install python-shade
sudo apt-get install python-pip
sudo pip install openstacksdk

Si se producen errores:
sudo rm -rf /usr/lib/python2.7/dist-packages/OpenSSL
sudo  rm -rf /usr/lib/python2.7/dist-packages/pyOpenSSL-0.15.1.egg-info
sudo pip install pyopenssl

Anexo F: Enlaces de interés

F.1. Módulos interesantes:

F.2. Ansible AWX

Ansible AWX es una interfaz web para la administración de playbooks Ansible. Se trata de la versión open source de Ansible Tower.

Para una prueba, descarga Ansible AWX