====== 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]]