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 we will cover:
- What are Ansible tags?
- Benefits of using Ansible tags
- How to use tags in Ansible?
- How to list all tags in Ansible?
- Using multiple tags in Ansible
- How to skip tags in playbooks?
- ‘Always’ and ‘Never’ tags
- Advanced usage of tags
- Best practices for using tags in Ansible
- Ansible tags in real-world examples
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.
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.
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
With the following command, we can trigger only the Install Apache
tagged task:
ansible-playbook playbook.yml --tags "install_apache"
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"
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
You can avoid using the standard Linux package by running the following command:
ansible-playbook playbook.yml --tags "tomcat"
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
Run the following command to list all the task tags available in this playbook:
ansible-playbook apache-playbook.yml --list-tags
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]
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
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"
We can also call multiple tags in the Ansible command:
ansible-playbook example_playbook.yml --skip-tags "configure"
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
Run the following to skip the tag restart_service
:
ansible-playbook update_config.yml --skip-tags "restart_service"
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
Specify your inventory file in the following way:
[dev]
dev.app.com
[qa]
qa.app.com
[uat]
uat.app.com
[production]
prod.app.com
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"
Depending on the situation, the –skip-tags flag can offer considerable flexibility.
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
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
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
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 }}"
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
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/
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"
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
This is how we can hardcode the environments in our inventory file:
[development]
dev.company.com env=development
[production]
prod.company.com env=production
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"
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
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
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"
Here are a few best practices for maintaining tags across your Ansible playbooks, roles, and plays:
Ansible tags best practices | |
Consistency |
|
Documentation |
|
Avoid Overusing Tags |
|
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
To perform a backup, you can run the following:
ansible-playbook db_maintenance.yml --tags "backup"
To perform an update, run the following:
ansible-playbook db_maintenance.yml --tags "update"
To restart the database service, run:
ansible-playbook db_maintenance.yml --tags "restart"
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
You can trigger the dev deployment by running the following:
ansible-playbook app.yml --tags "install,deploy,dev"
Similarly, you can trigger the other environments:
ansible-playbook app.yml --tags "install,deploy,staging"
ansible-playbook app.yml --tags "install,deploy,prod"
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
To remove the server from a load balancer, run the following:
ansible-playbook rolling_update.yml --tags "lb_remove"
To update the application code only, you can run:
ansible-playbook rolling_update.yml --tags "update_code"
To restart the web server, run the following:
ansible-playbook rolling_update.yml --tags "restart"
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
To simply install and configure your web server, run:
ansible-playbook multi_service.yml --tags "install,configure,webserver"
To start your app server, you can run the following:
ansible-playbook multi_service.yml --tags "start,appserver"
To perform a full setup and start the database server, you can run:
ansible-playbook multi_service.yml --tags "install,configure,start,dbserver"
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 great advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their Stacks with building workflows across tools.
Our latest Ansible enhancements solve three of the biggest challenges engineers face when they are using Ansible:
- Having a centralized place in which you can run your playbooks
- Combining IaC with configuration management to create a single workflow
- Getting insights into what ran and where
Provisioning, configuring, governing, and even orchestrating your containers can be performed with a single workflow, separating the elements into smaller chunks to identify issues more easily.
Would you like to see this in action – or just want a tl;dr? Check out this video I put together showing you Spacelift’s new Ansible functionality:
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.
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.
Manage Ansible Better with Spacelift
Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.