====== Ansible: Conceptos avanzados ======
Sección perteneciente al [[informatica:ciberseguridad:cursos:curso_ansible_automatizacion_it|curso Ansible Automatización IT]]
===== ¿Qué es YAML y por qué es importante conocerlo? =====
YAML es un formato de serialización de datos , legible por humanos, que está inspirado en lenguajes como XML.
YAML es un superconjunto del formato JSON, que permite añadir estructuras más complejas e incluso comentarios.
En el mundo Ansible, todos los playbooks están escritos en YAML.
Reglas generales:
* Extensión ''.yaml'' (recomendada) o ''.yml''
* Las tabulaciones no están permitidas, solo los espacios.
* Es necesario identar (sangrar) el código uno o más espacios.
* Todas las claves/propiedades son //case-sensitive// (se distingue mayúsculas de minúsculas).
* Los comentarios empiezan con un símbolo ''#''.
Ejemplo de un playbook en YAML:
hosts: webservers
sudo: yes
vars:
app_name: PleaseDeployMe
repo_url: https://github.com/username/reponame.git
repo_remote: origin
repo_version: master
webapps_dir: /deployed
virtualenv_root: /deployed/PleaseDeployMe/mac
tasks:
- name: git pull project
git: repo={{repo_url}} dest={{webapps_dir}}/{{app_name}} version=master
==== Tipos de datos ====
=== Cadenas de texto ===
---
foo: Esta es una cadena de texto
Los tres guiones indican el inicio del fichero.
Para meter caracteres especiales, hay que meterlos entre comillas:
---
foo: Esta es una cadena de texto "\n" estoy en una línea nueva.
=== Booleanos ===
''True'' es lo mismo que ''On''; ''False'' es lo mismo que ''Off''.
---
foo: True
bar: False
light: On
TV: Off
=== Números ===
Soporta números decimales, hexadecimales...
---
foo: 12345
bar: 0x12d4
plop: 023332
=== Array / listas ===
---
items: [1, 2, 3, 4, 5]
names: ["one", "two", "three", "four", "five"]
También se podría escribir en el siguiente formato:
items:
- 1
- 2
- 3
- 4
- 5
=== Diccionarios ===
---
foo: {thing1: huey, thing2: louie, thing3: dewey}
Los diccionarios se pueden anidar con otros elementos:
---
foo:
bar:
- bar
- rgb
- plob
El diccionario ''bar'' tiene dentro una lista de 3 elementos.
===== Variables =====
Permiten almacenar y reusar información. Las variables se procesan en orden, de mayor a menor preferencia:
- Variables por línea de comandos
- Variables en tareas
- Variables en roles y secciones ''include''
- Variables creadas con la directiva ''register''
- Variables en los inventarios
- Variables en las //plays//
- Host facts
- Variables por defecto en los roles
En la documentación oficial de Ansible se habla hasta de 15 niveles de prioridad.
Reglas a la hora de definir variables en Ansible:
* Deben comenzar con letra
* Pueden contener letras, números y guiones bajos.
===== Tareas, Plays y Playbooks I =====
Una tarea es la aplicación de un módulo en un playbook para realizar una acción específica sobre un equipo.
Algunas tareas:
* ''file'': un directorio debe existir.
* ''yum'': un paquete tiene que estar instalado.
* ''service'': un servicio tiene que estar ejecutándose.
* ''template'': se quiere cargar una configuración en un template.
* ''get_url'': se quiere descargar un fichero de una URL.
* ''git'': se quiere clonar el código fuente desde un repositorio.
Ejemplo de la sección de tareas en un fichero YAML de Ansible:
tasks:
- name: httpd package is present
yum:
name: httpd
state: latest
- name: latest index.html file is present
copy:
scr: files/index.html
dest: /var/www/html/
- name: restart httpd
service:
name: httpd
state: restarted
==== Plays y Playbooks ====
Una //play// consiste en un conjunto ordenado de tareas que se ejecutan en los hosts seleccionados sobre un inventario. Un //playbook// es un fichero que contiene una o más //plays//:
A continuación vemos un ejemplo de un Playbook con una sola play:
---
- name: install and start apache
hosts: web
become: yes
vars:
http_port: 80
tasks:
- name: httpd package is present
yum:
name: httpd
state: latest
- name: latest index.html file is present
copy:
scr: files/index.html
dest: /var/www/html/
Ansible permite importar playbooks con la palabra clave ''import_playbook''.
Ejemplo de nuestro primer playbook (''primer_playbook.yaml''):
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: functionando
Lo ejecutamos:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml
También podríamos añadirle la opción ''-v'' para obtener más información de la salida:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -v
Poner ejemplo de la salida del comando anterior.
Podemos revisar la sintaxis del Playbook sin ejecutarlo:
ansible-playbook primer_playbook.yaml --syntax-check
===== Tareas, Plays y Playbooks II =====
Vamos a coger el playbook de la sección anterior y complicarlo un poco más añadiéndole otra play:
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
gather_facts: false
tasks:
- name: listar directorios
command: ls
- name: instalar nginx
apt: name=nginx state=present
Antes de ejecutar, revisamos la sintaxis del fichero:
ansible-playbook primer_playbook.yaml --syntax-check
Si todo va bien, ejecutamos el playbook:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -v
Vemos que falla porque no el usuario no tiene permisos para instalar el paquete ''nginx''. Podemos indicar que necesitamos elevar privilegios con ''become: yes'':
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
tasks:
- name: listar directorios
command: ls
- name: instalar nginx
apt: name=nginx state=present
En la máquina destino, tenemos que evitar que pida la contraseña el usar ''sudo''. Para ello, hay que editar el fichero ''/etc/sudoers'' y la línea que comienza por ''%sudo'' dejarla de la siguiente manera:
%sudo ALL=(ALL) NOPASSWD: ALL
Modificamos más el playbook para incluir variables:
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: absent
tasks:
- name: listar directorios
command: ls
- name: instalar nginx
apt: name=nginx state={{ state }}
Lo que añadamos entre llaves (''{{}}'') se sustituirá por el valor que le hayamos dado. En este caso ''state'' es ''absent'', así que es lo mismo que si hubiéramos puesto:
apt: name=nginx state=absent
Si ejecutamos ahora ese playbook, desinstalará nginx.
===== Qué son los handlers y para qué se usan =====
Los **handlers** son tareas especiales que se ejecutan al final de una play si son notificadas por otra tarea cuando ocurre un cambio.
tasks:
- name: httpd package is present
yum:
name: httpd
state: latest
notify: restart httpd
- name: latest index.html file is present
copy:
src: files/index.html
dest: /var/www/html
handlers:
- name: restart httpd
service:
name: httpd
state: restarted
Los handlers se definen al final del Playbook
Seguimos trabajando con nuestro playbook:
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
- name: instalar nginx
apt: name=nginx state="{{ state }}"
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: reiniciar nginx
service:
name: nginx
state: restarted
Lo ejecutamos:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv
Si observamos el resultado, vemos que el servidor web nginx se reinicia, pero esto no nos interesa. Queremos que se reinicie solo cuando sea necesario. Aquí entran en escena los **handlers**:
# Este es nuestro primer playbook
- name: primera play
hosts: all
gather_facts: false # para que no traiga los facts por defecto
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Si lanzamos ahora el playbook, conseguiremos que solo se reinice nginx cuando se instale.
Vamos a pasar variables por línea de comando con la opción ''-e'':
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv -e "state=absent"
Ansible, en lugar de usar la varible ''state'' definida en el playbook, aplica la que pasamos por la línea de comandos. Por tanto, se eliminará nginx.
===== Condicionales =====
Ansible permite incluir condiciones a la hora de ejecutar las tareas basándose en la evaluación de una variable, fact u otra tarea previa en tiempo de ejecución.
---
- name: install Apache webserver
hosts: all
tasks:
- name: Install Apache on Ubuntu Server
apt: name=apache2 state=present
become: yes
when: ansible_os_family == "Debian" and ansible_distribution_version == "18.04"
Trabajando con nuestro ''primer_playbook.yaml'':
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
when: ansible_os_family == "RedHat"
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Poner ejemplo de salida
Veremos ahora el uso de condicionales utilizando una variable que hemos usado como salida de una tarea:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Poner ejemplo de salida de ejecución del anterior playbook
===== Tags =====
Los //tags// permiten ejecutar un subconjunto de tareas en un playbook.
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Lo ejecutamos añadiendo la opción ''%%--%%tags'':
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv --tags "listar"
Poner ejemplo de salida del comando anterior.
También podríamos incluir //tags// en una play entera:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv --tags "segunda_play"
Poner ejemplo de salida del comando anterior.
===== Templates I =====
Ansible integra el motor de //templating// [[https://jinja.palletsprojects.com/en/2.11.x/|jinja2]] para:
* Fijar y modificar variables
* Utilizar lógica condicional
* Generar ficheros de configuración utilizando variables
Insertar una variable:
INTERFACES="{{ dhcp_interface }}"
Iterar sobre una lista:
search ws.nsrc.org
{$ for host in use_dns_servers %}
nameserver {{ host }}
{% endfor %}
Se puede probar jinga online por ejemplo desde este [[http://jinja.quantprogramming.com/|Jinja2 live parser]] o [[https://j2live.ttl255.com/|este otro]]
Jinja2 permite utilizar [[https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_filters.html|filtros]] para transformar información almacenada en variables.
===== Templates II =====
Usaremos jinja2 con Ansible. Vamos a crear un fichero ''template_ejemplo.j2'':
Hola!
Nginx en la versión {{ version }} se está ejecutando en {{ servidor }}
Modificamos nuestro Playbook para añadir una tercera play:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: no
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
tasks:
- name: imprimir template
template:
src: template_ejemplo.js # fichero con template Jinja2
dest: /home/ansible/archivo.txt
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Revisamos sintaxis:
ansible-playbook --syntax-check primer_playbook.yaml
Lanzamos el playbook centrándonos en la última play que hemos hecho:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv --tags "tercera_play"
Si todo ha funcionado bien, se habrá creado el fichero ''/home/ansible/archivo.txt'' con el contenido:
Hola!
Nginx en la versión 5.13 se está ejecutando en Ubuntu.
Vamos a añadir más variables en la tercera play:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: no
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Creamos la plantilla ''template_ejemplo_2.j2'':
La lista de coches contiene las siguientes marcas:
{% for item in coches %}
{{ item }}
{% endfor %}
Revisamos sintaxis:
ansible-playbook --syntax-check primer_playbook.yaml
Lanzamos el playbook centrándonos en la última play que hemos hecho:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv --tags "tercera_play"
En el equipo destino tendremos un nuevo archivo ''/home/ansible/archivo_2.txt'':
La lista de coches contiene las siguientes marcas:
Mercedes
Nissan
Renault
Ford
Si no queremos el espacio tras cada elemento, usaríamos un guion antes del cierre del foreach:
{%- endfor %}
Si queremos que se transforme el texto a todo mayúscula, podemos usar el filtro ''upper'':
La lista de coches contiene las siguientes marcas:
{% for item in coches %}
{{ item | upper }}
{% endfor %}
Finalmente, veremos inyección de variables en código HTML. Creamos el fichero ''template_ejemplo_3.j2'':
Nginx se está ejecutando en {{ ansible_hostname }}
En nuestro Playbook vamos a añadir una tercera tarea en la tercera play:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: ls
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: yes # para tener permisos para escribir en la carpeta del servidor web
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
- name: mostrar un nuevo fichero index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Revisamos sintaxis:
ansible-playbook --syntax-check primer_playbook.yaml
Lanzamos el playbook centrándonos en la última play que hemos hecho:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv --tags "tercera_play"
===== Bloques y gestión de errores =====
Los bloques crean grupos lógicos de tareas y además permiten gestionar errores.
Para ignorar errores podemos usar la etiqueta ''ignore_errors'':
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios
command: lz # Ponemos un comando incorrecto a propósito
register: contenido # registro de la salida de 'ls'
tags:
- listar
ignore_errors: yes
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: yes # para tener permisos para escribir en la carpeta del servidor web
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
- name: mostrar un nuevo fichero index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Al ejecutar, dará error cuando llegue a "listar directorios", pero continuará la ejecución porque le hemos dicho que los ignores.
Vamos a crear dos bloques en nuestro playbook:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios y comprobar ficheros
block:
- name: listar directorios
command: lz # Ponemos un comando incorrecto a propósito
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
- name: instalar nginx y copiar index
block:
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: yes # para tener permisos para escribir en la carpeta del servidor web
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
- name: mostrar un nuevo fichero index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Vamos a hacer también una gestión más inteligente de los errores con la etiqueta ''rescue'':
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios y comprobar ficheros
block:
- name: listar directorios
command: lz # Ponemos un comando incorrecto a propósito
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
rescue:
- name: listar directorios
commando: ls
- name: instalar nginx y copiar index
block:
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: yes # para tener permisos para escribir en la carpeta del servidor web
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
- name: mostrar un nuevo fichero index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Cuando ejecutamos el playbook, al llegar al bloque "listar directorios", da error, entonces salta a la sección ''rescue'' y ejecuta lo que está definido ahí.
También podríamos usar la etiqueta ''always'' si queremos que algo se ejecute haya errores o no:
...
tasks:
- name: listar directorios y comprobar ficheros
block:
- name: listar directorios
command: lz # Ponemos un comando incorrecto a propósito
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
rescue:
- name: listar directorios
commando: ls
always:
- name: mensaje de always
debug:
msg: "Este código se ejecuta siempre"
===== Bucles =====
Los bucles se usan para realizar una tarea múltiples veces, como por ejemplo crear muchos usuarios, instalar varios paquetes, etc.
Se emplea la palabra reservada ''loop''.
Vamos a modificar la primera play de nuestro playbook de ejemplo:
# Este es nuestro primer playbook
- name: primera play
hosts: all
# gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: echo items
command: echo "{{ item }}"
loop:
- uno
- dos
- name: segunda play
hosts: servidor_web
become: yes
tags: segunda_play
gather_facts: false
vars:
state: present
tasks:
- name: listar directorios y comprobar ficheros
block:
- name: listar directorios
command: lz # Ponemos un comando incorrecto a propósito
register: contenido # registro de la salida de 'ls'
tags:
- listar
- name: comprobar ficheros
debug:
msg: "El directorio no está vacío"
when: contenido.stdout != ""
rescue:
- name: listar directorios
commando: ls
- name: instalar nginx y copiar index
block:
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: copiar index
copy:
src: index.html
dest: /var/www/html
- name: tercera play
hosts: all
become: yes # para tener permisos para escribir en la carpeta del servidor web
tags: tercera_play
vars:
version: "5.13"
servidor: "Ubuntu"
coches: ["Mercedes", "Nissan", "Renault", "Ford"]
tasks:
- name: imprimir template
template:
src: template_ejemplo.j2 # fichero con template Jinja2
dest: /home/ansible/archivo.txt
- name: ejemplo loop con templates
template:
src: template_ejemplo_2.j2
dest: /home/ansible/archivo_2.txt
- name: mostrar un nuevo fichero index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Al ejecutar el playbook, veremos en la salida que se muestra esos dos elementos (uno y dos) para cada uno de los equipos del inventario.
Añadimos más cosas:
(...)
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: echo items
command: echo "{{ item }}"
loop:
- uno
- dos
- name: listar inventario
debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
(...)
Mostrar salida del comando anterior
Vamos a usar ahora diccionarios:
# Este es nuestro primer playbook
- name: primera play
hosts: all
become: yes
gather_facts: true
tasks:
- name: comprobar conexión
ping:
data: funcionando
- name: echo items
command: echo "{{ item }}"
loop:
- uno
- dos
- name: listar inventario
debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
- name: crear usuarios
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'user1', groups: 'sudo'}
- { name: 'user2', groups: 'sudo'}
(...)
Revisamos sintaxis:
ansible-playbook --syntax-check primer_playbook.yaml
Lanzamos el playbook centrándonos en la última play que hemos hecho:
ansible-playbook -i /home/pepito/inventario -u ansible_user --key-file /home/pepito/.ssh/id_rsa primer_playbook.yaml -vv
Si vamos al equipo/s destino, se habrían creado los usuarios ''user1'' y ''user2'' (en Linux podemos mirar el contenido del fichero ''/etc/passwd''.
Si quisiéramos eliminar los usarios, cambiaríamos el valor de ''state'' a ''absent'':
(...)
- name: crear usuarios
user:
name: "{{ item.name }}"
state: absent
groups: "{{ item.groups }}"
loop:
- { name: 'user1', groups: 'sudo'}
- { name: 'user2', groups: 'sudo'}
(...)
===== Qué son los Roles en Ansible =====
Los roles permiten organizar, paquetizar y reusar nuestro código de forma más práctica.
Tendríamos una estructura de carpetas donde incluiremos los roles que queramos.
* ''site.yaml''
* ''roles/''
* ''common/''
* ''files/'': archivos a desplegar en el equipo objetivo.
* ''templates/''
* ''tasks/''
* ''handlers/''
* ''vars/''
* ''defaults/''
* ''meta/'': permite incluir dependencias entre roles
* ''apache/''
* ''files/''
* ''templates/''
* ''tasks/''
* ''handlers/''
* ''vars/''
* ''defaults/''
===== Creando nuestro primer Rol =====
Crearemos la carpeta principal ''roles'':
mkdir roles
Dentro de ella, vamos a crear el rol ''nginx'', que será otra carpeta más:
mkdir nginx
Dentro de ''nginx'', crearemos el resto de carpetas que espera ansible:
mkdir defaults files handlers tasks templates
Por tanto, si nos vamos al directorio padre, tendríamos la siguiente estructura de carpetas:
├── roles
│ └── nginx
│ ├── defaults
│ ├── files
│ ├── handlers
│ ├── tasks
│ └── templates
Partimos del siguiente playbook:
# Este es nuestro primer playbook
- name: primera play
become: yes
hosts: servidor_web
tags: primera_play
gather_facts: false
vars:
state: present
tasks:
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: crear index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
- name: copiar script
copy:
src: simple_script.sh
dest: /tmp/simple_script.sh
handlers:
- name: reiniciar nginx
service:
name: nginx
state: restarted
Vamos a mover los distintos elemenos de ese playbooks a donde deben estar. Por ejemplo, las tareas irán a ''roles/nginx/tasks/main.yaml'':
---
- name: instalar nginx
apt: name=nginx state="{{ state }}"
notify: reiniciar nginx # Mismo nombre que el handler
- name: crear index
template:
src: template_ejemplo_3.j2
dest: /var/www/html/index.html
mode: 0755
- name: copiar script
copy:
src: simple_script.sh
dest: /tmp/simple_script.sh
Colocaremos ahora el template que usamos en el Playbook (''template_ejemplo_3.j2'') en ''roles/nginx/templates/''
Hacemos lo mismo con el script ''simple_script.sh'', pero a la ruta ''roles/nginx/files''.
Ahora es el turno de los handlers. Añadiremos un fichero ''main.yaml'' con el contenido:
---
- name: reiniciar nginx
service:
name: nginx
state: restarted
Nos quedan las variables, crearemos el fichero ''roles/nginx/defaults/main.yaml'':
---
state: present
Nos falta incluir este nuevo rol en algún sitio. En la carpeta raíz, creamos uno llamado, por ejemplo, ''nginx_rol.yaml'':
---
- hosts: web # Solo aplicamos a uno de los hosts
become: yes
roles:
- nginx # Rol que vamos a incluir
Algo tarde, pero no es mal momento para comentar que podemos crear un fichero de configuración de Ansible: ''ansible.cfg'':
[defaults]
inventory = /home/pepito/inventario
remote_user = ansible
private_key_file = /home/pepito/.ssh/id_rsa
Ahora es más cómodo lanzar el playbook de la siguiente manera:
ansible-playbook nginx_role.yaml -vv
===== Recursos =====
* [[https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html|YAML syntax]]
* [[https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html|Using variables]]