Running Ansible Playbook On Windows And Linux Hosts In The Same Play
Tags:
#Ansible
Note
Ansible has no native way to execute on remote Windows and Linux hosts in the same play.
Verified
Tested on Ansible version 2.4.3.0
Preface
Ansible was not designed in a way that allows managing different operating systems in the same play.
Still, in some cases, for convenience’s sake, there might be a need to manage both Windows and Linux hosts in the same play
(personal example - playbook which manages backup scripts across a datacenter without knowing which operating system is installed on the host).
To manage both operating system types, we use Ansible’s group feature.
Configuring Ansible to manage Windows hosts is outside this post’s scope. Please refer to the official Ansible documentation.
Before continuing, make sure you configured the following:
- Ansible host that can query Windows hosts using DNS and authenticate with WinRM (WinRM relies on proper DNS)
- Ansible host that can reach Linux hosts using SSH
- WinRM on Windows hosts
- Dedicated Ansible group for Windows Hosts (Or configure it during play in-memory)
By default, Ansible attempts to communicate with hosts via SSH, so we need to create an Ansible group (either in memory or in an inventory file) to set the connection to WinRM HTTP/S.
Once everything is configured, the easiest (but not the most accurate) way to discover a host’s operating system is by checking which port is accessible on the host.
Example
Example of a playbook that attempts to discover the remote hosts’ operating system by attempting to connect the operating system management protocol port.
Assuming we pass a variable named input_hosts
containing a list of the remote hosts.
# We execute the tasks on our Ansible host to check if they can reach the remote hosts
- hosts: localhost
# Don't gather facts about localhost
gather_facts: false
tasks:
# Check port 22(SSH) for connectivity
- name: Attempt to connect via SSH
# Module which attempts to open socket
wait_for:
host: {{ item }}
# Attempt to connect to port 22
port: 22
# Timeout after 3 seconds
timeout: 3
# Ignore errors that occurred in the run
ignore_error: true
# Assign the result to a variable
register: ssh_connectivity
# Itterate over the variable input_hosts
with_items: {{ input_hosts }}
# Check port 5985(WinRM HTTP)/5986(WinRM HTTPS) for connectivity
- name: Attempt to connect via WinRM
# Module which attempts to open socket
wait_for:
host: {{ item }}
# Attempt to connect to port 5985
port: 5985
timeout: 3
ignore_error: true
register: winrm_connectivity
with_items: "{{ input_hosts }}"
- name: Populate Linux hosts group
# Add host to Ansible's in-memory group
add_host:
name: "{{ item.item }}"
groups: Linux
# Add to group only if succeeded in connecting to port 22
when: item.state = "started"
# Itterate over connectivity results
with_items: "{{ ssh_connectivity.results }}"
- name: Populate Windows hosts group
# You can configure the necessary parameters for the Windows group here instead of in an inventory file
add_host:
name: "{{ item.item }}"
groups: Windows
when: item.state = "started"
with_items: "{{ winrm_connectivity.results }}"
- hosts: Linux
......play tasks.......
- hosts: Windows
......play tasks.......
It’s possible to consolidate those tasks into a role so they can be invoked and reused efficiently.
Summary
There are a few downsides to using this approach:
- Relying on the management protocol port connectivity is not a reliable way to discover remote operating systems since Windows hosts could also use SSH to be managed remotely. In that case, the play will add the Windows host to the Linux group but probably fail to authenticate.
- You can’t execute tasks in parallel on both operating systems.
- More time-consuming than running a dedicated play tailored for a specific operating system.
In conclusion, it’s not always the most efficient way to attempt to manage Windows and Linux hosts with the same playbook, it depends on your use cases.