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