List unreachable servers in Ansible using a reusable playbook

122 Views Asked by At

I'm able to list unreachable servers using the below Ansible playbook:

---
- name: "Play 1-Find the details here {{ source_host }} & {{ dest_host }}"
  hosts: localhost
  any_errors_fatal: True
  serial: 1
  vars:
    source_host: "{{ hostvars[inventory_hostname]['serverlist_input'] }}"
  gather_facts: no
  tasks:
    - add_host:
        name: "{{ item | trim }}"
        groups: source_node
        printback_rec: "{{ hostvars[inventory_hostname]['printback_input'] }}"
      with_items:
        - "{{ source_host.split(',') }}"

    - set_fact:
        rec_group_names: 'source_node'

- name: Check unreachable hosts
  hosts: "{{ hostvars['localhost']['rec_group_names'] }}"
  gather_facts: true
  any_errors_fatal: false
  tasks:
    - name: Print group names
      debug:
        msg: "GROUP NAME TO BE TESTED: {{ group_names }}"

    - name: Perform ping in Template
      ping:

    - block:
        - debug:
            var: ansible_play_hosts_all
        - debug:
            var: ansible_play_hosts
        - set_fact:
            down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
        - debug:
            var: down
      run_once: true

    - name: "Display unreachable hosts one at a time {{ startlogstring | default('start:') }}"
      debug:
        msg: "Unreachable Host: {{ item }}"
      loop: "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}"
      run_once: true

    - fail:
        msg: "Exiting play if target hosts are unreachable"
      when: ansible_play_hosts_all != ansible_play_hosts
      run_once: true

- name: Play 2- Configure Source nodes
  hosts: source_node
  gather_facts: false
  any_errors_fatal: false
  debugger: never
  ignore_unreachable: yes
  vars:
    ansible_ssh_common_args: '-o ConnectTimeout=2'
  tasks:
    # - name: Detect unreachable hosts
    #   import_playbook: "{{ playbook_dir }}/generictask_templates/logunreachablehost.yml"

    - name: Perform ping
      ping:

Desired Output:

PLAY [Play 1-Find the details here {{ source_host }} & {{ dest_host }}] ********

TASK [Reset Github logs] *******************************************************
changed: [localhost] => (item=precheck.log)

TASK [add_host] ****************************************************************
ok: [localhost] => (item=remotehost4)
ok: [localhost] => (item=remotehost3)
ok: [localhost] => (item=remotehost2)
ok: [localhost] => (item=remotehost1)

TASK [set_fact] ****************************************************************
ok: [localhost]

PLAY [Check unreachable hosts] *************************************************

TASK [Gathering Facts] *********************************************************
fatal: [remotehost1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname remotehost1: Name or service not known", "unreachable": true}
[WARNING]: <redacted>
ok: [remotehost3]
[WARNING]: <redacted>
ok: [remotehost4]
[WARNING]: <redacted>
ok: [remotehost2]

TASK [Print group names] *******************************************************
ok: [remotehost4] => {
    "msg": "GROUP NAME TO BE TESTED: ['source_node']"
}

TASK [Perform ping in Template] ************************************************
ok: [remotehost3]
ok: [remotehost4]
ok: [remotehost2]

TASK [debug] *******************************************************************
ok: [remotehost4] => {
    "ansible_play_hosts_all": [
        "remotehost4",
        "remotehost3",
        "remotehost2",
        "remotehost1"
    ]
}

TASK [debug] *******************************************************************
ok: [remotehost4] => {
    "ansible_play_hosts": [
        "remotehost4",
        "remotehost3",
        "remotehost2"
    ]
}

TASK [set_fact] ****************************************************************
ok: [remotehost4]

TASK [debug] *******************************************************************
ok: [remotehost4] => {
    "down": [
        "remotehost1"
    ]
}

TASK [Display unreachable hosts one at a time start:] ***************
ok: [remotehost4] => (item=remotehost1) => {
    "msg": "Unreachable Host: remotehost1"
}


TASK [fail] ********************************************************************
fatal: [remotehost4]: FAILED! => {"changed": false, "msg": "Exiting play if target hosts are unreachable"}

NO MORE HOSTS LEFT *************************************************************

PLAY RECAP *********************************************************************
remotehost4                : ok=11   changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
remotehost2                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
remotehost1                : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
remotehost3                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Here is my requirement.

I wish to create a reusable template either include_tasks or import_playbook where i can pass any <groups> to hosts: and it would help me log all unreachable servers for that group_name.

The purpose is to reuse this template / playbook in an other play to log unreachable servers.

I tried the below but it does not work and errors:

Step 1: Created reusable playbook called "{{ playbook_dir }}/templates/logunreachable.yml" and copied the same logic from the previous successful run without any changes:

cat {{ playbook_dir }}/templates/logunreachable.yml
---
- name: Check unreachable hosts
  hosts: "{{ hostvars['localhost']['rec_group_names'] }}"
  gather_facts: true
  any_errors_fatal: false
  tasks:
    - name: Print group names
      debug:
        msg: "GROUP NAME TO BE TESTED: {{ group_names }}"
      run_once: true

    - name: Perform ping in Template
      ping:

    - block:
        - debug:
            var: ansible_play_hosts_all
        - debug:
            var: ansible_play_hosts
        - set_fact:
            down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
        - debug:
            var: down
      run_once: true

    - name: "Display unreachable hosts one at a time {{ startlogstring | default('are-inject-start:') }}"
      debug:
        msg: "Unreachable Host: {{ item }}"
      loop: "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}"
      run_once: true

    - fail:
        msg: "Exiting play if target hosts are unreachable"
      when: ansible_play_hosts_all != ansible_play_hosts
      run_once: true

In the main playbook I import the above playbook and expect similar output as before:

Caller playbook:

---
- name: "Play 1-Find the details here {{ source_host }} & {{ dest_host }}"
  hosts: localhost
  any_errors_fatal: True
  serial: 1
  vars:
    source_host: "{{ hostvars[inventory_hostname]['serverlist_input'] }}"
  gather_facts: no
  tasks:
    - add_host:
        name: "{{ item | trim }}"
        groups: source_node
        printback_rec: "{{ hostvars[inventory_hostname]['printback_input'] }}"
      with_items:
        - "{{ source_host.split(',') }}"

    - set_fact:
        rec_group_names: 'source_node'

- name: Play 2- Configure Source nodes
  hosts: source_node
  gather_facts: false
  any_errors_fatal: false
  debugger: never
  ignore_unreachable: yes
  vars:
    ansible_ssh_common_args: '-o ConnectTimeout=2'
  tasks:
    - name: Detect unreachable hosts
      import_playbook: "{{ playbook_dir }}/generictask_templates/logunreachablehost.yml"

    - name: Perform ping
      ping:

However, the output is not as expected like before:

PLAY [Play 1-Find the details here {{ source_host }} & {{ dest_host }}] ********

TASK [Reset Github logs] *******************************************************
changed: [localhost] => (item=precheck.log)

TASK [add_host] ****************************************************************
ok: [localhost] => (item=remotehost4)
ok: [localhost] => (item=remotehost3)
ok: [localhost] => (item=remotehost2)
ok: [localhost] => (item=remotehost1)

TASK [set_fact] ****************************************************************
ok: [localhost]

PLAY [Play 2- Configure Source nodes] ******************************************

TASK [Detect unreachable hosts] ************************************************
fatal: [remotehost1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname remotehost1: Name or service not known", "skip_reason": "Host remotehost1 is unreachable", "unreachable": true}
[WARNING]: <redacted>
fatal: [remotehost3]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3.9"}, "changed": false, "module_stderr": "Shared connection to remotehost3 closed.\\r\\n", "module_stdout": "", "msg": "MODULE FAILURE\\nSee stdout/stderr for the exact error", "rc": 0}
[WARNING]: <redacted>
fatal: [remotehost2]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.9"}, "changed": false, "module_stderr": "Shared connection to remotehost2 closed.\\r\\n", "module_stdout": "", "msg": "MODULE FAILURE\\nSee stdout/stderr for the exact error", "rc": 0}
[WARNING]: <redacted>
fatal: [remotehost4]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.9"}, "changed": false, "module_stderr": "Shared connection to remotehost4 closed.\\r\\n", "module_stdout": "", "msg": "MODULE FAILURE\\nSee stdout/stderr for the exact error", "rc": 0}

TASK [Perform ping] ************************************************************
fatal: [remotehost1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname remotehost1: Name or service not known", "skip_reason": "Host remotehost1 is unreachable", "unreachable": true}

PLAY RECAP *********************************************************************
remotehost4                : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
remotehost2                : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
remotehost1                : ok=0    changed=0    unreachable=2    failed=0    skipped=2    rescued=0    ignored=0   
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
remotehost3                : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Can you please suggest a solution where I could pass the hosts: <groupname> and the workflow could help list all unreachable hosts for the passed ?

1

There are 1 best solutions below

0
Alexander Pletnev On BEST ANSWER

The purpose is to reuse this template / playbook in an other play to log unreachable servers.

Ansible automatically skips the unreachable hosts and proceeds with execution without failing the play. Also, Ansible will automatically skip them in all the subsequent plays. The list of unreachable hosts will be shown in the play recap.

Consider the following minimal reproducible example:

# inventory.yaml
---
all:
  vars:
    ansible_python_interpreter: auto_silent # this suppresses the warnings
source_node:
  hosts:
    remotehost1:
      ansible_connection: ssh
      ansible_host: example.com
    remotehost[2:4]:
      ansible_connection: local
# playbook.yaml
---
- name: Test with unreachable hosts 1
  # you can gather facts as well,
  # but this is faster is you don't need them
  gather_facts: false
  hosts: source_node
  tasks:
    - name: Perform ping 1
      ping:

- name: Test with unreachable hosts 2
  gather_facts: false
  hosts: source_node
  tasks:
    - name: Perform ping 2
      ping:

Output (I have enabled YAML stdout callback enabled for brevity and readability):

Alexanders-Mini:78050414 alexander$ ansible-playbook playbook.yaml -i inventory.yaml 

PLAY [Test with unreachable hosts 1] **********************************************************************************************************************************************************************

TASK [Perform ping 1] *************************************************************************************************************************************************************************************
ok: [remotehost2]
ok: [remotehost3]
ok: [remotehost4]
fatal: [remotehost1]: UNREACHABLE! => 
    changed: false
    msg: 'Failed to connect to the host via ssh: ssh: connect to host example.com port
        22: Operation timed out'
    unreachable: true

PLAY [Test with unreachable hosts 2] **********************************************************************************************************************************************************************

TASK [Perform ping 2] *************************************************************************************************************************************************************************************
ok: [remotehost3]
ok: [remotehost2]
ok: [remotehost4]

PLAY RECAP ************************************************************************************************************************************************************************************************
remotehost1                : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0   
remotehost2                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
remotehost3                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
remotehost4                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

Now, if you still want to list the unreachable hosts in a separate playbook while passing the list of hosts ad-hoc. Actually, there are three errors preventing that in your current implementation:

  1. there is no hostvars simply because Ansible doesn't know what hosts should it run the play on;
  2. if you change it to a plain variable instead, it will only work if you pass the variable directly via --extra-vars;
  3. if you don't have any hosts defined on the inventory (or any inventory at all), you can only run this play on the implicit localhost.

All of them block the reusability of the playbook.

So, to solve them all, you'll need to add the hosts to the inventory in the same playbook but in a separate play on localhost, and run ping on all hosts if that plain variable is not defined (or on that target group, if it's not empty). You don't need to add them twice, by the way, so I'm checking if they are already present in the inventory. I also added an ability to control the fact gathering and speed up the things:

# detect_and_list_unreachable_hosts.yaml
---
- name: Add the hosts to the inventory if they are missing
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Add the list of hosts to the custom group
      add_host:
        name: "{{ item | trim }}"
        groups: "{{ target_group | default ('source_node') }}"
      loop: "{{ target_hosts.split(',') }}"
      when:
        - target_hosts is defined and target_hosts
        - groups['source_node'] is defined and item not in item not in groups['source_node'
          or item not in groups

- name: List the unreachable hosts
  hosts: "{{ test_hosts | default('all') }}"
  gather_facts: "{{ gather_facts_on_test_hosts | default('false') }}"
  tasks:
    - name: Ping the hosts
      ping:

    - name: Log the ping results
      debug:
        var: ansible_play_hosts_all | difference(ansible_play_hosts)
      run_once: true

This playbook will work in any circumstances: with or without the extra vars, with or without the predefined inventory, with or without localhost in the inventory, with or without the target hosts in the inventory.

You can also add a failed_when condition, or call assert or fail module to stop the execution if there are any unreachable hosts but some others still work. To make the output even more clear, you can also set a fact delegating it to localhost and displaying it in the subsequent play. Otherwise, it will be shown for a random host, which could be confusing:

# detect_and_list_unreachable_hosts.yaml
---
- name: Add the hosts to the inventory if they are missing
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Add the list of hosts to the custom group
      add_host:
        name: "{{ item | trim }}"
        groups: "{{ target_group | default ('source_node') }}"
      loop: "{{ target_hosts.split(',') }}"
      when:
        - target_hosts is defined and target_hosts
        - groups['source_node'] is defined and item not in item not in groups['source_node'
          or item not in groups

- name: List the unreachable hosts
  hosts: "{{ test_hosts | default('all') }}"
  gather_facts: "{{ gather_facts_on_test_hosts | default('false') }}"
  tasks:
    - name: Ping the hosts
      ping:

    - name: Delegate the ping results to localhost for logging
      set_fact:
        unreachable_hosts: "{{ ansible_play_hosts_all | difference(ansible_play_hosts) }}"
      delegate_to: localhost
      delegate_facts: true
      run_once: true

- name: Display the unreachable hosts
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Log the list of unreachable hosts
      debug:
        var: unreachable_hosts
      failed_when: unreachable_hosts is defined and unreachable_hosts

To use it with any other playbook, just add it as the first play:

# playbook.yaml
---
- name: Detect the unreachable hosts
  import_playbook: detect_and_list_unreachable_hosts.yaml

# subsequent plays go here

If you don't want to use extra vars or you don't have an inventory, you can always add vars to that import:

# playbook.yaml
---
- name: Detect the unreachable hosts
  import_playbook: detect_and_list_unreachable_hosts.yaml
  vars:
    target_hosts: "{{ some_other_hosts }}"
    # or even hardcode:
    # target_hosts: remotehost5,remotehost6