When managing infrastructure with Ansible, we often need to apply playbooks to most hosts while excluding specific server groups. While command-line patterns like all,!ntpservers
work perfectly, implementing the same exclusion logic within playbooks requires different approaches.
Here are three effective ways to exclude hosts in playbooks:
1. Using the hosts: directive with patterns
- name: Apply to all hosts except database servers
hosts: all:!dbservers
tasks:
- name: Install monitoring client
ansible.builtin.package:
name: monitoring-agent
state: present
2. Leveraging Group Variables
- name: Conditional execution based on group membership
hosts: all
tasks:
- name: Install client on non-server hosts
ansible.builtin.package:
name: client-software
state: present
when: "'servers' not in group_names"
3. Combining Multiple Patterns
- name: Complex exclusion pattern
hosts: "{{ groups['web_servers'] + groups['app_servers'] }}"
tasks:
- name: Configure load balancer settings
ansible.builtin.template:
src: templates/lb_config.j2
dest: /etc/lb_config.conf
For more complex scenarios, consider these approaches:
Dynamic Inventory Filtering
- name: Filter hosts using JMESPath
hosts: "{{ hostvars | dict2items | json_query(query) | map(attribute='value') | list }}"
vars:
query: "[?value.group_names.contains(@, 'production') && !value.group_names.contains(@, 'database')]"
tasks:
- name: Deploy application
ansible.builtin.copy:
src: /opt/apps/main.jar
dest: /opt/apps/
Using Custom Filters
- name: Apply custom filter plugin
hosts: "{{ all_servers | exclude('backup_servers') }}"
tasks:
- name: Run security update
ansible.builtin.apt:
name: "*"
state: latest
update_cache: yes
When implementing host exclusions, watch for these issues:
- Pattern evaluation occurs during inventory parsing, not during play execution
- Exclusions don't affect included or imported playbooks
- Dynamic groups might require special handling
For maximum reliability, combine pattern matching with explicit conditionals in tasks when appropriate.
While host patterns are evaluated early, complex exclusions can impact performance:
- Simple patterns (like
all:!group
) are fastest - Nested conditionals add minimal overhead
- Custom filters and JMESPath queries should be used judiciously
When managing infrastructure with Ansible, you'll often need to target specific subsets of hosts while excluding others. While command-line patterns like all,!ntpservers
work well for ad-hoc commands, implementing this in playbooks requires a different approach.
The most straightforward way is to use exclusion patterns directly in your playbook's hosts
directive:
- name: Install client on all hosts except servers
hosts: all:!servers
tasks:
- name: Install client package
ansible.builtin.package:
name: my-client-pkg
state: present
For more complex scenarios, leverage Ansible groups in your inventory:
[web]
web1.example.com
web2.example.com
[db]
db1.example.com
[all_except_db:children]
web
Then reference this group in your playbook:
- name: Configure all hosts except database servers
hosts: all_except_db
tasks:
- name: Apply common configuration
template:
src: templates/common.conf.j2
dest: /etc/common.conf
For runtime decisions, use Ansible's magic variables:
- name: Conditional execution based on host facts
hosts: all
tasks:
- name: Skip servers
ansible.builtin.command: /usr/bin/install-client
when: "'server' not in group_names"
- Document your exclusion logic in playbook comments
- Use descriptive group names (e.g.,
non_prod
instead ofall:!prod
) - Test exclusion patterns with
--list-hosts
flag first - Consider using tags for more granular control
Here's a complete example for patching non-critical systems:
- name: Apply security patches to non-critical hosts
hosts: all:!critical:!database
serial: 20%
tasks:
- name: Update all packages
ansible.builtin.yum:
name: '*'
state: latest
update_cache: yes
when: ansible_os_family == 'RedHat'