Use etiquetas de Ansible para ahorrar tiempo en las ejecuciones de libros de jugadas

Como usuario frecuente de Ansible, siempre busco formas de simplificar mis playbooks y ahorrar tiempo al depurar playbooks. Una de mis características favoritas para escribir playbooks robustos de Ansible es su compatibilidad con etiquetas. Este artículo presenta etiquetas, revisa algunos escenarios de etiquetas comunes y describe un uso más avanzado.

Índice

¿Qué son las balizas?

Palabras clave son metadatos que puede adjuntar a tareas en un libro de jugadas de Ansible. Le permiten apuntar selectivamente a ciertas tareas en tiempo de ejecución, diciéndole a Ansible que realice (o no realice) ciertas tareas. Si bien normalmente ejecuta un libro de jugadas completo de Ansible de principio a fin, puede ser extremadamente útil para completar tareas específicas en un libro de jugadas bajo demanda.

Como ejemplo, el libro de jugadas a continuación define dos etiquetas: hello y goodbye. Puede usar estas etiquetas para controlar cómo Ansible ejecuta el libro de jugadas:

---

- hosts: localhost
  gather_facts: False
  tasks:

    - name: Hello tag example
      debug:
        msg: "Hello!"
      tags:
        - hello

    - name: No tag example
      debug:
        msg: "How are you?"

    - name: Goodbye tag example
      debug:
        msg: "Goodbye!"
      tags:
        - goodbye

Puede decirle a Ansible que ejecute solo etiquetas con el -t Donde --tags bandera:

$ ansible-playbook basic_example.yml --tags hello

PLAY [localhost]
*******************************************

TASK [Hello tag example]
*******************************************
ok: [localhost] => {
    "msg": "Hello!"
}

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

También puede decirle a Ansible que ignore las tareas con ciertas etiquetas usando el --skip-tags bandera:

$ ansible-playbook basic_example.yml --skip-tags goodbye

PLAY [localhost]
*******************************************

TASK [Hello tag example]
*******************************************
ok: [localhost] => {
    "msg": "Hello!"
}

TASK [No tag example]
*******************************************
ok: [localhost] => {
    "msg": "How are you?"
}

PLAY RECAP
*******************************************
localhost: ok=2    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0  

Finalmente, puede enumerar las etiquetas en su libro de jugadas usando el --list-tags bandera:

$ ansible-playbook basic_example.yml --list-tags

playbook: basic_example.yml

  play #1 (localhost): localhost	TAGS: []
      TASK TAGS: [goodbye, hello]

Asegúrese de que una tarea se ejecute siempre (o nunca)

Al usar etiquetas en sus libros de jugadas, encontrará que a veces tiene tareas que siempre desea completar, incluso cuando usa --tags Donde --skip-tags. Por ejemplo, imagine un libro de jugadas que instala, configura y verifica el software de su empresa. Tiene una tarea que obtiene la última versión de la aplicación para instalar al realizar una solicitud a un punto final web. Utiliza etiquetas para distinguir cada paso, en caso de que desee ejecutar una determinada parte del libro de jugadas para ahorrar tiempo. Sin embargo, desea que la tarea de versión de software se ejecute siempre porque contiene información que es relevante para todas las demás tareas. Si un usuario apunta solo a tareas marcadas con configure, el libro de jugadas siempre debe ejecutar la tarea de versión de software para obtener información crítica. Puedes lograr esto con el always etiqueta:

---

- hosts: appservers
  gather_facts: False
  tasks:

    - name: Obtain latest software version from API
      uri:
        url: "http://config-server.example.com:8080/api/v1/appVersion"
      register: app_version
      delegate_to: localhost
      tags:
        - always

    - name: Install software
      package:
        name: "{{ app_version.json.version }}"
        state: present
      tags:
        - install

    - name: Configure software
      template:
        src: templates/app_config.json.j2
        dest: /etc/app/app_config.json
        mode: 0644
        owner: root
        group: root
      tags:
        - configure

    - name: Verify software
      command: /usr/local/sbin/test_app.sh {{ app_version.json.version }}
      tags:
        - verify

Cuando no se transmite ninguna baliza, todo el libro de jugadas se ejecuta, como se esperaba:

$ ansible-playbook -i inventory.ini always_runs.yml

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Install software]
*************************************************************
ok: [appserver.example.com]

TASK [Configure software]
*************************************************************
ok: [appserver.example.com]

TASK [Verify software]
*************************************************************
changed: [appserver.example.com]

PLAY RECAP
*************************************************************
appserver.example.com      : ok=4    changed=1    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0   

Cuando el configure se pasa, la tarea de configuración se ejecuta como se esperaba. La tarea de versión de software también se ejecuta porque tiene la always etiqueta:

$ ansible-playbook -i inventory.ini always_runs.yml -t configure

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Configure software]
*************************************************************
ok: [appserver.example.com]

PLAY RECAP
*************************************************************
appserver.example.com      : ok=2    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0 

Ansible también proporciona la never etiqueta, que hace lo contrario de always. Garantiza que una tarea nunca se ejecutará a menos que el usuario la apunte específicamente. Imagine que no desea que se ejecute la tarea de verificación a menos que el usuario del libro de jugadas la llame específicamente. el never la etiqueta soluciona este problema:

- name: Verify software
  command: /usr/local/sbin/test_app.sh {{ app_version.json.version }}
  tags:
    - verify
    - never

Incluso si no se especifica ninguna etiqueta, la tarea de verificación no se ejecuta porque tiene la never etiqueta:

$ ansible-playbook -i inventory.ini always_runs.yml

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Install software]
*************************************************************
ok: [appserver.example.com]

TASK [Configure software]
*************************************************************
ok: [appserver.example.com]

PLAY RECAP
*************************************************************
appserver.example.com      : ok=3    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0

Una tarea etiquetada siempre se ejecuta si se llama explícitamente:

$ ansible-playbook -i inventory.ini always_runs.yml -t verify

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Verify software]
*************************************************************
changed: [appserver.example.com]

PLAY RECAP
*************************************************************
appserver.example.com      : ok=2    changed=1    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0  

Herencia de etiquetas

Escribir un solo libro de jugadas monolítico es raro. Probablemente tenga libros de jugadas que importen otros libros de jugadas, incluyan tareas de otros archivos o utilicen una estructura de roles para organiza tu código. Comprender cómo se transmiten las etiquetas en este jerarquía de las importaciones es importante porque las etiquetas se complican cuando se usan comprender Donde importaciones.

[ Learn more by signing up for the free webinar Ansible 101: An introduction to automating everything with training. ]

Ansible diferencia entre estático importaciones y dinámico comprender. Al usar una importación (como import_tasks), Ansible también aplica etiquetas asociadas a todas las tareas importadas. Por ejemplo, considere el siguiente código, que refactoriza el único libro de jugadas de la última sección en dos archivos YAML: playbook.yml y install.yml. (Omití las tareas de configuración y validación en aras de la brevedad). La principal playbook.yml usos import_tasks para disparar en las tareas en el install.yml archivar:

# playbook.yml
---
- hosts: appservers
  gather_facts: False
  tasks:

    - name: Obtain latest software version from API
      uri:
        url: "http://config-server.example.com:8080/api/v1/appVersion"
      register: app_version
      delegate_to: localhost
      tags:
        - always

    - import_tasks: install.yml
      tags:
        - install

# install.yml
---
- name: Install software
  package:
    name: "{{ app_version.json.version }}"
    state: present

Aquí está el lanzamiento del libro de jugadas:

$ ansible-playbook -i inventory.ini playbook.yml -t install

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Install software]
*************************************************************
ok: [appserver.example.com]

PLAY RECAP
*************************************************************
appserver.example.com      : ok=2    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0   

Observe cómo la tarea en install.yml corrió a pesar de no tener el install etiqueta. Esto sucede porque el install etiqueta aplicada en el principal playbook.yml es heredado por todas las tareas importadas por import_tasks. Sin embargo, si cambio import_tasks en include_tasks, obtengo un comportamiento muy diferente:

# playbook.yml
---
- hosts: appservers
  gather_facts: False
  tasks:

    - name: Obtain latest software version from API
      uri:
        url: "http://config-server.example.com:8080/api/v1/appVersion"
      register: app_version
      delegate_to: localhost
      tags:
        - always

    - include_tasks: install.yml
      tags:
        - install

El resultado:

$ ansible-playbook -i inventory.ini playbook.yml -t install

PLAY [appservers]
*************************************************************

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [include_tasks]
*************************************************************
included:
/home/[...]/ansible_tags/dynamic_imports/install.yml 
for appserver.example.com

PLAY RECAP
*************************************************************
appserver.example.com      : ok=2    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0   

Observe la install la tarea ya no se está ejecutando. Cuando una dinámica include_task se utiliza, las etiquetas se aplican sólo a include él mismo. Tareas dentro de la include no heredan etiquetas, por lo que también deben establecerse en las tareas incluidas:

# install.yml
---
- name: Install software
  package:
    name: "{{ app_version.json.version }}"
    state: present
  tags:
    - install

Agregar etiquetas a cada tarea manualmente en un archivo incluido puede ser tedioso y propenso a errores, por lo que Ansible usa el apply palabra clave para ayudarte. Ver el Documentación para más información.

Use etiquetas para simplificar la depuración del libro de jugadas

Una excelente manera de apreciar la utilidad de las etiquetas es aprovecharlas al escribir y depurar libros de jugadas. En el siguiente ejemplo, Ansible devuelve un error con respecto a una variable no definida durante la tarea de instalación:

TASK [Install software]
*********************************

fatal: [appserver.example.com]: FAILED! => 
{"msg": "The task includes an option with an undefined variable.
The error was: 'dict object' has no attribute 'version'nnThe error
appears to be in '/home/ansible/debug_tags.yml': line 21, column 7,
but maynbe elsewhere in the file depending on the exact syntax 
problem.nnThe offending line appears to be:nnn
    - name: Install softwaren
      ^ heren"}

entonces agrego debug etiquetas a las tareas que quiero realizar para facilitar la resolución de problemas. En este caso, agregué una tarea de depuración para imprimir la variable guardada y también agregué una debug etiqueta a la tarea de instalación. Esto me permite ejecutar solo tareas específicas al solucionar problemas, lo que me ahorra tiempo ya que no tengo que ejecutar todo el libro de jugadas:

---

- hosts: appservers
  gather_facts: False
  tasks:

    - name: Obtain latest software version from API
      uri:
        url: "http://config-server.example.com:8080/api/v1/appVersion"
      register: app_version
      delegate_to: localhost
      tags:
        - always

    - name: Print out software version API response
      debug:
        msg: "{{ app_version }}"
      tags:
        - debug

    - name: Install software
      package:
        name: "{{ app_version.json.version }}"
        state: present
      tags:
        - install

El uso de esta lógica de depuración muestra rápidamente que me estoy refiriendo a la clave de diccionario incorrecta (appversion.json.version en lugar de appversion.json.softwareVersion):

$ ansible-playbook -i inventory.ini debug_tags.yml -t debug

PLAY [appservers]
************************************************************* 

TASK [Obtain latest software version from API]
*************************************************************
ok: [appserver.example.com -> localhost]

TASK [Print out software version API response]
*************************************************************
ok: [appserver.example.com] => {
    "msg": {
        "changed": false,
        "connection": "close",
        "content_length": "51",
        "content_type": "application/json; charset=utf-8",
        "cookies": {},
        "cookies_string": "",
        "date": "Thu, 14 Oct 2021 17:00:41 GMT",
        "elapsed": 0,
        "failed": false,
        "json": {
            "softwareVersion": "my-app=7.68.0-1ubuntu2.7"
        },
        "msg": "OK (51 bytes)",
        "redirected": false,
        "status": 200,
        "url": "http://config-server.example.com:8080/api/v1/appVersion"
    }
}

PLAY RECAP
*************************************************************
appserver.example.com      : ok=2    changed=0    unreachable=0
failed=0    skipped=0    rescued=0    ignored=0   

Por lo general, elimino estas etiquetas de depuración después de que mi libro de jugadas se está ejecutando y antes de confirmarlo para el control de fuente. Sin embargo, como en la sección anterior, también puede dejar tareas de depuración adicionales en sus libros de jugadas y simplemente marcarlas con el never etiqueta.

Envoltura

Encontrar formas de estructurar y ejecutar mejor sus playbooks de Ansible es esencial para desarrollar una estrategia de automatización flexible. Ansible ofrece muchas formas diferentes de lograr esto; las importaciones, las inclusiones, los roles y otros patrones le permiten escribir código sólido y reutilizable.

Las etiquetas proporcionan una forma rápida de apuntar a tareas específicas de Ansible. Sabiendo utilizar correctamente las balizas, aprovecha always y never etiquetas y comprender los matices de la herencia de etiquetas mejorará su experiencia en Ansible y le permitirá diseñar una mejor automatización.

Artículos de interés

Subir