templatesansibleruntime-errorreusabilityconnectivity

List unreachable servers in Ansible using a reusable playbook


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 ?


Solution

  • 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