DEV Community

Spacelift team for Spacelift

Posted on • Originally published at spacelift.io

Ansible Tags Explained

Ansible is an open-source tool primarily used for infrastructure automation. It allows you to simplify repetitive tasks, application deployments, configurations, orchestration, security and compliance tasks, and cloud provisioning using YAML-based playbooks.

An Ansible best practice is to use tags to manage your playbooks. In this article, we show you the benefits of leveraging Ansible tags and how to use them for different scenarios.

What are Ansible tags?

Ansible tags provide users with granular control over the execution of specific tasks, roles, and even entire plays within a playbook. Tags are specified in the Playbook YAML file and assigned to a task or role. When using the --tags parameter in the ansible-playbook command, Ansible will execute the tagged tasks only and ignore the rest. This feature is particularly useful when you want to avoid running the entire playbook and focus instead on specific components to prevent potential disruptions in your environment. 

You might be wondering - why you can't just use conditionals to control your playbook tasks or roles. Conditionals are based on certain conditions being true for that task or role to run, and tags give you more control by allowing you to run that task or role directly from the command line. 

Benefits of using Ansible tags

Ansible tags offer many benefits and may provide huge assistance in particular situations.

With tags, you can easily debug your Ansible playbook when something isn't working properly. Placing a tag on the task you want to test and running the tagged items via the command line can help you get to the root cause without running the entire playbook. Playbooks can easily become complex, so selective execution can save time and resources.

Ansible tags are also very flexible because you can tag certain tasks or roles and do the opposite of running them. You can skip specific tasks you believe might be problematic to run at the time of execution.

Tags can also help you reuse certain components from your playbook across different scenarios, making them a powerful tool if used correctly.

How to use tags in Ansible?

Now that we know how beneficial Ansible tags can be, let's review a couple of example use cases. We can assign tags to tasks, roles, or plays within our playbook. 

Adding a tag to a task

In the following example, we are tagging tasks in our playbook. These tasks are responsible for installing Apache and starting the Apache service:

---
- name: Apache
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags: install_apache

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags: start_service
Enter fullscreen mode Exit fullscreen mode

With the following command, we can trigger only the Install Apache tagged task:

 

ansible-playbook playbook.yml --tags "install_apache"
Enter fullscreen mode Exit fullscreen mode

And if we run this one, we can just start the Apache service without involving the Install Apache tagged task:

ansible-playbook playbook.yml --tags "start_service"
Enter fullscreen mode Exit fullscreen mode

Adding a tag to a Role

Tags work in the same way for Ansible Roles.

---
- name: Role Playbook
  hosts: test
  roles:
    - role: standard_linux_package
      tags: standard_package

    - role: tomcat_app
      tags: tomcat
Enter fullscreen mode Exit fullscreen mode

You can avoid using the standard Linux package by running the following command:

ansible-playbook playbook.yml --tags "tomcat"
Enter fullscreen mode Exit fullscreen mode

How to list all tags in Ansible?

Complex playbooks will involve multiple tasks or roles, and it might be cumbersome to go through the entire playbook to discover which tags are being used for which tasks. Listing all tags in an Ansible playbook will show which parts of the playbook can be selectively executed or skipped. 

When running the playbook command, you can use the --list-tags flag to list all the tags associated with tasks, roles, and plays in your playbook.

---
- name: Apache
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags:
        - install

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags:
        - start

    - name: Configure Apache
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags:
        - configure
Enter fullscreen mode Exit fullscreen mode

Run the following command to list all the task tags available in this playbook:

ansible-playbook apache-playbook.yml --list-tags
Enter fullscreen mode Exit fullscreen mode

And you should get an output similar to the one below:

playbook: apache_playbook.yml

  play #1 (all): Apache TAGS: []
    task #1: Install Apache TAGS: [install]
    task #2: Start Apache Service   TAGS: [start]
    task #3: Configure Apache   TAGS: [configure]
Enter fullscreen mode Exit fullscreen mode

Using multiple tags in Ansible

Adding multiple tags throughout each task, role, and play can increase the flexibility of your Ansible playbook and give you more control over the execution. This can be useful when you have multiple tasks that can be grouped into one category, and you want to control some of those tasks with a different tag for another purpose. 

With the multiple tagging feature, you can run the Ansible command with the tag flag for any situation to ensure the correct tasks, roles, or plays are executed. 

In the following example, we are grouping some of these tasks as web servers and a few others as db-related. Those tasks are also being tagged with the tags we have been using so far for installing the app, starting the app service, and configuring the app:

---
- name: MyWebApp
  hosts: test
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags:
        - install
        - webserver

    - name: Install MySQL
      apt:
        name: mysql-server
        state: present
      tags:
        - install
        - database

    - name: Configure Apache
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags:
        - configure
        - webserver

    - name: Configure MySQL
      template:
        src: my.cnf.j2
        dest: /etc/mysql/my.cnf
      tags:
        - configure
        - database

    - name: Start Apache Service
      service:
        name: apache2
        state: started
      tags:
        - start
        - webserver

    - name: Start MySQL Service
      service:
        name: mysql
        state: started
      tags:
        - start
        - database
Enter fullscreen mode Exit fullscreen mode

Now, we can deploy the Apache web server without worrying about touching the database and vice versa. We have the flexibility to run the following commands for different scenarios:

ansible-playbook myapp.yml --tags "install"

ansible-playbook myapp.yml --tags "webserver"

ansible-playbook myapp.yml --tags "database"
Enter fullscreen mode Exit fullscreen mode

We can also call multiple tags in the Ansible command:

ansible-playbook example_playbook.yml --skip-tags "configure"
Enter fullscreen mode Exit fullscreen mode

How to skip tags in playbooks?

Skipping tags in playbooks can be useful when there are specific tasks you don't want to run while allowing the rest of your playbook to execute as planned. By specifying the tags to skip, you can control which tasks are omitted during execution.

For example, during a routine deployment, you might want to apply new configuration changes without restarting the service. By applying tags on the restart service task, you can use the --skip-tags flag with that tag to avoid restarting the service:

---
- name: Update Configuration
  hosts: all
  tasks:
    - name: Apply new configuration
      template:
        src: new_config.j2
        dest: /etc/myapp/config.conf
      tags: update_config

    - name: Restart myapp service
      service:
        name: myapp
        state: restarted
      tags: restart_service
Enter fullscreen mode Exit fullscreen mode

Run the following to skip the tag restart_service:

ansible-playbook update_config.yml --skip-tags "restart_service"
Enter fullscreen mode Exit fullscreen mode

You can also use tagging for specific environments (dev, qa, uat, prod) and use the --skip-tags flag to avoid running certain tasks in certain environments. 

For example, if you want to skip running specific tasks in any environment that is non-dev:

 

---
- name: App Deployment
  hosts: all
  tasks:
    - name: Install app dependencies
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - curl
      tags: install_dependencies

    - name: Deploy application code
      git:
        repo: 'https://github.com/me/app.git'
        dest: /var/www/myapp
      tags: deploy_code

    - name: Configure application
      template:
        src: app_config.j2
        dest: /etc/myapp/config.conf
      tags:
        - configure_app
        - skip_in_production

    - name: Restart application service
      service:
        name: app
        state: restarted
      tags:
        - restart_service
        - skip_in_production

    - name: Run database migrations
      command: /usr/local/bin/migrate_db.sh
      tags:
        - migrate_db
        - skip_in_production
Enter fullscreen mode Exit fullscreen mode

Specify your inventory file in the following way:

[dev]
dev.app.com

[qa]
qa.app.com

[uat]
uat.app.com

[production]
prod.app.com

Enter fullscreen mode Exit fullscreen mode

And lastly, execute to the non-dev environments as the following:

ansible-playbook manage_app.yml --limit production --skip-tags "skip_in_production"

ansible-playbook manage_app.yml --limit qa --skip-tags "skip_in_production"

ansible-playbook manage_app.yml --limit uat --skip-tags "skip_in_production"
Enter fullscreen mode Exit fullscreen mode

Depending on the situation, the --skip-tags flag can offer considerable flexibility.

💡 You might also like:

The 'Always' and 'Never' tags

Two special Ansible tags ensure consistency and stability in your playbook:

  • The 'always' tag guarantees the task will run regardless of what tag is passed in from the command line. This is useful when critical validation steps, environment setup tasks, or cleanup operation tasks need to run constantly. 
  • The 'never' tag does the opposite. It ensures that tasks will not run regardless of which tag is being passed down. This is useful for tasks that are being deprecated and should not be run anymore. You can also use the 'never' tag if you want to temporarily disable certain tasks during testing or debugging. 

The following example will deploy the application code and config file, but regardless of the tag specified, it will always clean up any temporary files.

 

---
- name: App
  hosts: all
  tasks:
    - name: Deploy application
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags: deploy_git_code

    - name: Configure application
      template:
        src: app_config.j2
        dest: /etc/myapp/config.conf
      tags: configure_app

    - name: Cleanup temporary files
      file:
        path: /tmp/myapp_temp
        state: absent
      tags: always
Enter fullscreen mode Exit fullscreen mode

The next example focuses on a situation where you want to avoid running deprecated tasks. If anyone runs this playbook with the db_update tag, the "Update database schema" task will not run because it has the never flag associated with it:

---
- name: Update Database Schema
  hosts: db_servers
  tasks:
    - name: Backup database
      command: /usr/local/bin/db_backup.sh
      tags: db_backup

    - name: Update database schema (deprecated)
      command: /usr/local/bin/db_update.sh
      tags:
        - db_update
        - never

    - name: Reboot database server
      reboot:
      tags: reboot_db
Enter fullscreen mode Exit fullscreen mode

The always and never tags can be used in the same playbook without conflict. Let's take a look at an example:

---
- name: Deploy app
  hosts: all
  tasks:
    - name: Validate environment
      command: /usr/local/bin/validate_env.sh
      tags: always

    - name: Deploy application
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags: deploy_app

    - name: Deprecated deployment step
      command: /usr/local/bin/deprecated_deploy.sh
      tags:
        - deprecated
        - never

    - name: Cleanup environment
      command: /usr/local/bin/cleanup_env.sh
      tags: always
Enter fullscreen mode Exit fullscreen mode

Advanced usage of Ansible tags

This section features examples using Ansible tags with variables, facts, roles, and imports.

Using tags on variables

You can pass in tag values through variables, which allows you to manage all your tags from a single point instead of having them spread out across the playbook.

---
- name: Playbook with Tags and Variables
  hosts: all
  vars:
    install_tag: "install_apache"

  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present
      tags: "{{ install_tag }}"
Enter fullscreen mode Exit fullscreen mode

You can also trigger variables only when the tagged value is being called via the command line. 

The following playbook ensures the web_server_port variable gets passed in only when the webserver tag is called. The db_server_port variable gets used within that specific task only when the dbserver tag is called.

- hosts: all
  vars:
    web_server_port: 80
    db_server_port: 5432

  tasks:
    - name: Install web server
      tags: webserver
      yum:
        name: httpd
        state: present

    - name: Start web server
      tags: webserver
      service:
        name: httpd
        state: started

    - name: Configure web server port
      tags: webserver
      lineinfile:
        path: /etc/httpd/conf/httpd.conf
        regexp: '^Listen '
        line: "Listen {{ web_server_port }}"
        notify: restart webserver

    - name: Install database server
      tags: dbserver
      yum:
        name: postgresql-server
        state: present

    - name: Start database server
      tags: dbserver
      service:
        name: postgresql
        state: started

    - name: Configure database server port
      tags: dbserver
      lineinfile:
        path: /var/lib/pgsql/data/postgresql.conf
        regexp: '^port = '
        line: "port = {{ db_server_port }}"
        notify: restart dbserver

  handlers:
    - name: restart webserver
      service:
        name: httpd
        state: restarted

    - name: restart dbserver
      service:
        name: postgresql
        state: restarted
Enter fullscreen mode Exit fullscreen mode

Ansible tags on facts

Ansible Facts are system properties that Ansible gathers from managed nodes when Ansible connects to them. These properties include details about the OS, installed packages, network interfaces, and more. By default, gathering facts runs at the beginning of each playbook run. 

Sometimes, running gather_facts every time the playbook runs can be time-consuming and affect the playbook's performance. With tags, you have more control over the flow of execution, allowing you to collect data only when a certain tag is specified. 

In the following example, we want only a few tasks to run if gather_facts tag is used and the rest of the tasks to run as usual (Note: gather_facts is specifically set to false since, by default, gather_facts is set to true). 

To use tags to gather Facts, you will need to use the setup module:

---
- name: Gathering facts with tags
  hosts: all
  gather_facts: false
  tasks:
    - name: Gather facts
      setup:
      tags: gather_facts

    - name: Install Apache on Ubuntu
      apt:
        name: apache2
        state: present
      when: ansible_facts['os_family'] == "Debian"
      tags:
        - install_apache

    - name: Install Apache on CentOS
      yum:
        name: httpd
        state: present
      when: ansible_facts['os_family'] == "RedHat"
      tags:
        - install_apache

    - name: Configure Apache on Ubuntu
      template:
        src: apache_ubuntu.conf.j2
        dest: /etc/apache2/apache2.conf
      when: ansible_facts['os_family'] == "Debian"
      tags:
        - configure_apache

    - name: Configure Apache on CentOS
      template:
        src: apache_centos.conf.j2
        dest: /etc/httpd/conf/httpd.conf
      when: ansible_facts['os_family'] == "RedHat"
      tags:
        - configure_apache

    - name: Create a directory
      file:
        path: /tmp/myapp
        state: directory

    - name: Copy application files
      copy:
        src: /local/path/
        dest: /tmp/myapp/
Enter fullscreen mode Exit fullscreen mode

Now, if we want to install Apache on a Debian machine, we can use the following command:

ansible-playbook apache.yml --tags "gather_facts,install_apache"
Enter fullscreen mode Exit fullscreen mode

You can also use tags to avoid running gathering_facts on production systems by using a mix of conditionals and tags. 

In the following example, we specify a variable ansible_env in our inventory file with its dedicated environment. The tag will get triggered to gather facts only when the environment is non-production:

---
- name: Gather facts conditionally
  hosts: all
  gather_facts: false
  tasks:
    - name: Gather facts in non-prod
      setup:
      tags: gather_facts_non_prod
      when: env != "production"

    - name: Deploy application
      git:
        repo: 'https://github.com/example/myapp.git'
        dest: /var/www/myapp
      tags: deploy_app
Enter fullscreen mode Exit fullscreen mode

This is how we can hardcode the environments in our inventory file:

[development]
dev.company.com env=development

[production]
prod.company.com env=production
Enter fullscreen mode Exit fullscreen mode

You can run the following command, but this will only run against the dev environment because we specified it in our inventory file:

ansible-playbook deployment_playbook.yml --tags "gather_facts_non_prod,deploy_app"
Enter fullscreen mode Exit fullscreen mode

Ansible tags on roles and imports

We covered some simple examples of using roles with tags, but in the next example, we will incorporate using imports along with our roles.

In this example, the playbook has a couple of tagged roles that run only when the specific tags are called. We want to run some extra tasks along with that role in certain situations.

We can structure our playbook to run the roles via tags and also import tasks and call those tasks via tags as well:

---
- name: Deploy App
  hosts: all
  roles:
    - { role: common, tags: ['common'] }
    - { role: webserver, tags: ['webserver'] }
    - { role: database, tags: ['database'] }
  tasks:
    - name: Import additional common tasks
      import_tasks: standard_tasks.yml
      tags: standard_tasks

    - name: Import additional web server tasks
      import_tasks: webserver_tasks.yml
      tags: webserver_tasks

    - name: Import additional database tasks
      import_tasks: database_tasks.yml
      tags: database_tasks
Enter fullscreen mode Exit fullscreen mode

Here is an example of a standard task you could import with your existing role:

---
- name: Install additional common utilities
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - htop
    - tree

- name: Configure timezone
  timezone:
    name: 'Etc/UTC'

- name: Set up NTP service
  apt:
    name: ntp
    state: present

- name: Start and enable NTP service
  service:
    name: ntp
    state: started
    enabled: yes

- name: Update system packages
  apt:
    upgrade: dist
Enter fullscreen mode Exit fullscreen mode

You can trigger a webserver deployment using the ansible role with the standard_tasks and webserver_tasks with the following command:

ansible-playbook deploy_application.yml --tags "webserver,standard_tasks,webserver_tasks"
Enter fullscreen mode Exit fullscreen mode

Ansible tags in real-world examples 

In this section, we will cover some possible real-world examples to illustrate how you can utilize tags across your Ansible playbooks.

Example 1: Database maintenance

To control the maintenance of your database and manage specific tasks within your playbook independently -  instead of running the entire playbook each time - you can use tags as shown below:

---
- name: Database Maintenance
  hosts: db_servers
  tasks:
    - name: Backup database
      command: /usr/local/bin/db_backup.sh
      tags:
        - backup

    - name: Update database schema
      command: /usr/local/bin/db_update.sh
      tags:
        - update

    - name: Restart database service
      service:
        name: postgresql
        state: restarted
      tags:
        - restart
Enter fullscreen mode Exit fullscreen mode

To perform a backup, you can run the following:

ansible-playbook db_maintenance.yml --tags "backup"
Enter fullscreen mode Exit fullscreen mode

To perform an update, run the following:

ansible-playbook db_maintenance.yml --tags "update"
Enter fullscreen mode Exit fullscreen mode

To restart the database service, run:

ansible-playbook db_maintenance.yml --tags "restart"
Enter fullscreen mode Exit fullscreen mode

Example 2: Application deployment to different environments

In this scenario, we want to condense our playbooks into a single playbook that will be responsible for deploying the application to different environments. We can control environmental deployments using tags as the following:

---
- name: App Deployment
  hosts: all
  tasks:
    - name: Install dependencies
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - python3-pip
        - git
      tags:
        - install
        - dev
        - staging
        - prod

    - name: Deploy app in dev
      git:
        repo: 'https://github.com/app/dev_app.git'
        dest: /var/www/myapp
      tags:
        - deploy
        - dev

    - name: Deploy app in staging
      git:
        repo: 'https://github.com/app/stage_app.git'
        dest: /var/www/myapp
      tags:
        - deploy
        - staging

    - name: Deploy app in prod
      git:
        repo: 'https://github.com/app/prod_app.git'
        dest: /var/www/myapp
      tags:
        - deploy
        - prod

    - name: Start app service
      service:
        name: myapp
        state: started
      tags:
        - dev
        - staging
        - prod
Enter fullscreen mode Exit fullscreen mode

You can trigger the dev deployment by running the following:

ansible-playbook app.yml --tags "install,deploy,dev"
Enter fullscreen mode Exit fullscreen mode

Similarly, you can trigger the other environments:

ansible-playbook app.yml --tags "install,deploy,staging"
ansible-playbook app.yml --tags "install,deploy,prod"
Enter fullscreen mode Exit fullscreen mode

Example 3: Rolling updates in high-availability environments

Performing rolling updates can ensure less downtime for your application. The example below will give you more control over how your application is updated.

---
- name: Rolling Update for Web Servers
  hosts: web_servers
  serial: 1
  tasks:
    - name: Take server out of load balancer
      command: /usr/local/bin/remove_from_lb.sh
      tags:
        - lb_remove

    - name: Update application code
      git:
        repo: 'https://github.com/app/myapp.git'
        dest: /var/www/myapp
      tags:
        - update_code

    - name: Restart web server
      service:
        name: apache2
        state: restarted
      tags:
        - restart

    - name: Add server back to load balancer
      command: /usr/local/bin/add_to_lb.sh
      tags:
        - lb_add
Enter fullscreen mode Exit fullscreen mode

To remove the server from a load balancer, run the following:

ansible-playbook rolling_update.yml --tags "lb_remove"
Enter fullscreen mode Exit fullscreen mode

To update the application code only, you can run:

ansible-playbook rolling_update.yml --tags "update_code"
Enter fullscreen mode Exit fullscreen mode

To restart the web server, run the following:

 

ansible-playbook rolling_update.yml --tags "restart"
Enter fullscreen mode Exit fullscreen mode

Example 4: Managing multiple services

Tags can be very useful if you want to use one playbook to manage multiple services, such as web servers, application servers, and database servers. With tags, you can control running different parts of the playbook:

---
- name: Managing Multi-Service
  hosts: all
  tasks:
    - name: Install web server
      apt:
        name: apache2
        state: present
      tags:
        - install
        - webserver

    - name: Install app server
      apt:
        name: tomcat
        state: present
      tags:
        - install
        - appserver

    - name: Install db server
      apt:
        name: postgresql
        state: present
      tags:
        - install
        - dbserver

    - name: Configure web server
      template:
        src: apache.conf.j2
        dest: /etc/apache2/apache2.conf
      tags:
        - configure
        - webserver

    - name: Configure app server
      template:
        src: tomcat.conf.j2
        dest: /etc/tomcat/tomcat.conf
      tags:
        - configure
        - appserver

    - name: Configure db server
      template:
        src: postgresql.conf.j2
        dest: /etc/postgresql/postgresql.conf
      tags:
        - configure
        - dbserver

    - name: Start web server service
      service:
        name: apache2
        state: started
      tags:
        - start
        - webserver

    - name: Start app server service
      service:
        name: tomcat
        state: started
      tags:
        - start
        - appserver

    - name: Start db server service
      service:
        name: postgresql
        state: started
      tags:
        - start
        - dbserver
Enter fullscreen mode Exit fullscreen mode

To simply install and configure your web server, run:

ansible-playbook multi_service.yml --tags "install,configure,webserver"
Enter fullscreen mode Exit fullscreen mode

To start your app server, you can run the following:

ansible-playbook multi_service.yml --tags "start,appserver"
Enter fullscreen mode Exit fullscreen mode

To perform a full setup and start the database server, you can run:

ansible-playbook multi_service.yml --tags "install,configure,start,dbserver"
Enter fullscreen mode Exit fullscreen mode

How can Spacelift help you with Ansible projects?

Spacelift's vibrant ecosystem and excellent GitOps flow can greatly assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can then easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.

Another advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, OpenTofu, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their stacks with building workflows across tools. Spacelift greatly simplifies and elevates your workflow for all of these tools, and the ability to create dependencies between stacks and passing outputs enables you to make end-to-end deployments with a small change.

If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.

Key points

Ansible tags provide an efficient and flexible way to manage and control the execution of tasks, roles, and plays within your Ansible playbooks. Whether you need to perform targeted updates, manage multiple services, or conduct rolling updates in high-availability environments, tags enhance your ability to maintain and optimize your infrastructure with precision and ease. 

Adopting best practices for tagging, such as consistent naming conventions and proper documentation, ensures that your playbooks remain organized and effective, making Ansible tags a powerful tool for any IT automation strategy.

Written by Faisal Hashem

Top comments (0)