Ansible - Multiple/ Alternative hostnames for the same host - ansible

Assume I have hosts with multiple (DNS) names/IPs, e.g. because they have multiple NICs and thus routes to reach them.
I want to play a playbook in case one of these routes fails. Since I do not know which one works, I would like ansible to try all of them and then play the book only once for this host. It would be easy to put all the host's names into the inventory and let it run, but then the playbook would be executed once for each name of the host.
Question: Is there a way to specify alternative host names or to tell ansible to run the playbook only on one host per group?

It can be implemented
to run the playbook only on one host per group
See example below.
- hosts: jails-01
strategy: linear
vars:
lock_file: /var/lock/my_ansible_hostname.lock
tasks:
- name: delete lock_file
file:
path: "{{ lock_file }}"
state: absent
run_once: true
delegate_to: localhost
- name: select host
shell: "echo {{ ansible_hostname }} > {{ lock_file }}"
args:
creates: "{{ lock_file }}"
delegate_to: localhost
- name: winner takes it all
fail:
msg: "Too late. Other thread is running. End of play."
when: lookup('file', lock_file) != ansible_hostname
- name: go ahead
debug:
msg: "{{ ansible_hostname }} goes ahead ... "
# ansible-playbook playbook.yml | grep msg
fatal: [test_01]: FAILED! => {"changed": false, "msg": "Too late. Other thread is running. End of play."}
fatal: [test_03]: FAILED! => {"changed": false, "msg": "Too late. Other thread is running. End of play."}
"msg": "test_02 goes ahead ... "

Related

ansible, execute a task only if another specific host is also in the playbook run

I have a little problem I can't solve. I am writing an update/reboot playbook for my Linux servers and I want to make sure that a task is executed only if another host is in the same playbook run
for example:
stop app on app server when the database server is going to be rebooted
- hosts: project-live-app01
tasks:
- name: stop app before rebooting db servers
systemd:
name: app
state: stopped
when: <<< project-live-db01 is in this ansible run >>>
- hosts: dbservers
serial: 1
tasks:
- name: Unconditionally reboot the machine with all defaults
reboot:
post_reboot_delay: 20
msg: "Reboot initiated by Ansible"
- hosts: important_servers:!dbservers
serial: 1
tasks:
- name: Unconditionally reboot the machine with all defaults
reboot:
post_reboot_delay: 20
msg: "Reboot initiated by Ansible"
I want to use the same playbook to reboot hosts and If i --limit the execution to only some hosts and especially not the dbserver then I don't want to have the app stopped. I try to create a generic playbook for all my projects, which only executes tasks if certain servers are affected by the playbook run.
Is there any way for that?
thank you and have a great day!
cheers, Ringo
It would be possible to create a dictionary with the structure of the project, e.g. in group_vars
shell> cat group_vars/all
project_live:
app01:
dbs: [db01, db09]
app02:
dbs: [db02, db09]
app03:
dbs: [db03, db09]
Put all groups of the DB servers you want to use into the inventory, e.g.
shell> cat hosts
[dbserversA]
db01
db02
[dbserversB]
db02
db03
[dbserversC]
db09
Then the playbook below demonstrates the scenario
shell> cat playbook.yml
---
- name: Stop app before rebooting db servers
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: "Stop app on {{ item.key }}"
loop: "{{ project_live|dict2items }}"
when: item.value.dbs|intersect(groups[dbservers])|length > 0
- name: Reboot db servers
hosts: "{{ dbservers }}"
gather_facts: false
tasks:
- debug:
msg: "Reboot {{ inventory_hostname }}"
For example
shell> ansible-playbook -i hosts playbook.yml -e dbservers=dbserversA
PLAY [Stop app before rebooting db servers] ***********************
msg: Stop app on app01
msg: Stop app on app02
PLAY [Reboot db servers] *******************************************
msg: Reboot db01
msg: Reboot db02
How can I stop services on app* when the play is running on the localhost? Either use delegate_to, or create dynamic group by add_host and use it in the next play, e.g.
shell cat playbook.yml
---
- name: Create group appX
hosts: localhost
gather_facts: false
tasks:
- add_host:
name: "{{ item.key }}"
groups: appX
loop: "{{ project_live|dict2items }}"
loop_control:
label: "{{ item.key }}"
when: item.value.dbs|intersect(groups[dbservers])|length > 0
- name: Stop app before rebooting db servers
hosts: appX
gather_facts: false
tasks:
- debug:
msg: "Stop app on {{ inventory_hostname }}"
- name: Reboot db servers
hosts: "{{ dbservers }}"
gather_facts: false
tasks:
- debug:
msg: "Reboot {{ inventory_hostname }}"
gives
shell> ansible-playbook -i hosts playbook.yml -e dbservers=dbserversA
PLAY [Create group appX] ******************************************
ok: [localhost] => (item=app01)
ok: [localhost] => (item=app02)
skipping: [localhost] => (item=app03)
PLAY [Stop app before rebooting db servers] ************************
msg: Stop app on app01
msg: Stop app on app02
PLAY [Reboot db servers] *******************************************
msg: Reboot db01
msg: Reboot db02

Is there a way to get a list of unreachable hosts in an ansible playbook

I have the ansible role coded below.
---
- name: Get host facts
set_fact:
serverdomain: "{{ansible_domain}}"
server_ip: "{{ansible_ip_addresses[1]}}"
- name: Host Ping Check
failed_when: false
win_ping:
register: var_ping
- name: Get Host name
debug: msg="{{the_host_name}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
It works the way it should do but of course unreachable hosts are not touched at all. I would like to generate a list or is there a variable which contains all the unreachable hosts. Is it possible to have this in a comma delimmeted list and stored in a variable ?
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
EDIT 1
- name Unreachable servers
set_fact:
down: "{{ ansible_play_hosts_all | difference(ansible_play_hosts)}}"
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: "-servername '{{the_host_name}}' -response var_ping.failed -unreachable_hosts {{ down }}"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
when: inventory_hostname == {{ db_server_host }}
Thanks for the answers, I have now been able to use thesame login in my ansible role, and it appears to work.
I do however have some questions. My playbook runs against hosts defined in my inventory, in this case what I want to achieve is a situation where the unreachable hosts is passed onto a powershell script right at the end and at once. So for example, 100 hosts, 10 were unreachable. The playbook should gather the 10 unreachable hosts, and pass the list of hosts in an array format to a powershell script or as a json data type.
All I want to do is be able to process the list of unreachable servers from my powershell script.
At the moment, I get
[
"server1",
"server2"
]
MY script will work with a format like this "server1","server2"
Lastly.The process of logging the unreachable servers at the end, only needs to happen once to a specific server which does the logging to a database. I created a task to do this as seen above.
db_server_host is being passed as a extra variables from ansible tower. The reason i added the when is that I only want it to run on the DB server and not on every host. I get the error The conditional check inventory_hostname == {{ db_server_host }} failed. The error was error while evaluating conditional inventory_hostname == {{ db_server_host }} 'mydatabaseServerName' is undefined
Q: "Get a list of unreachable hosts in an Ansible playbook."
Short answer: Create the difference between the lists below
down: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"
Details: Use Special Variables. Quoting:
ansible_play_hosts_all:
List of all the hosts that were targeted by the play.
ansible_play_hosts:
List of hosts in the current play run, not limited by the serial. Failed/Unreachable hosts are excluded from this list.
For example, given the inventory
shell> cat hosts
alpha
beta
charlie
delta
The playbook
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- 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
gives (bridged) if alpha is unreachable (see below)
ansible_play_hosts_all:
- alpha
- beta
- charlie
ansible_play_hosts:
- beta
- charlie
down:
- alpha
Host alpha was unreachable
PLAY [alpha,beta,charlie] *************************************************
TASK [Gathering Facts] ****************************************************
fatal: [alpha]: UNREACHABLE! => changed=false
msg: 'Failed to connect to the host via ssh: ssh: connect to host test_14 port 22: No route to host'
unreachable: true
ok: [charlie]
ok: [beta]
...
Note
The dictionary hostvars keeps all hosts from the inventory, e.g.
- hosts: alpha,beta,charlie
gather_facts: true
tasks:
- debug:
var: hostvars.keys()
run_once: true
gives (abridged)
hostvars.keys():
- alpha
- beta
- charlie
- delta
tasks:
- ping:
register: ping_out
# one can include any kind of timeout or other stall related config here
- debug:
msg: |
{% for k in hostvars.keys() %}
{{ k }}: {{ hostvars[k].ping_out.unreachable|default(False) }}
{% endfor %}
yields (when an inventory consisting of only an alive alpha host):
msg: |-
alpha: False
beta: True
charlie: True
Lastly, how/where do i need to set gather_facts: no. I tried several places to no avail.
It only appears one time in the playbook keywords and thus it is a play keyword:
- hosts: all
gather_facts: no
tasks:
- name: now run "setup" for those who you wish to gather_facts: yes
setup:
when: inventory_host is awesome

Variable is defined but still getting undefined error

I am trying to write a playbook that completes some of its tasks on the machine that the playbook is running on. I know i can use local_action for this but I am just testing if the playbook works for now. Eventually I will need to use the delegate_to. I have defined the variable and I am using delegate_to: 'variable name' but I am getting this error. : " fatal [target node]: FAILED! => { msg": "'variablename' is undefined. Below is my playbook:
name: Create certs
gather_facts: true
vars:
master: "{{ nameofhost }}"
tasks:
- name: Run command
shell: Command to run
delegate_to: "{{ master }}"
You need to target your play to a target hosts of an inventory
name: Create certs
gather_facts: true
hosts: target_hosts
vars:
master: "{{ nameofhost }}"
tasks:
- name: Run command
shell: Command to run
delegate_to: "{{ master }}"
``` 
Your inventory files may look like that:
[target_hosts]
master ansible_host=your_master_dns_or_ip
And then ansible can target that inventory group and then reduce the task scope to master host. Or you can just use the localhost as target.

Ansible 'with_items' doesn't pass over to Ansible role

I can't figure this out. I have host_vars that I want to pass into my Ansible role. I can iterate through the items in my playbook, but it doesn't work if I give the item into my Ansible role. My host inventory looks like this:
hosts:
host_one:
domain: one
ip: 172.18.1.1
connection:
- connection_name: two
connection_ip: 172.18.1.2
- connection_index: three
local_hub_ip: 172.18.1.3
host_two:
domain: two
ip: 172.18.1.2
For instance,this works correctly:
tasks:
- debug:
msg: "{{item.connection_name}}"
with_items:
- "{{ connection }}"
will correctly print out the connections.connection_name for each connection I have, "two" and "three". However, if I try to pass it into a role:
tasks:
- name: Adding several connections
include_role:
name: connection-create
with_items:
- "{{ connection }}"
in which my role "connection-create" uses a variable called "connection_name" I get a:
FAILED! => {"msg": "'connection_name' is undefined"}
Why doesn't this work?
The loop with_items: "{{ connection }}" creates the loop variable item. The included role can use
item.connection_name
item.connection_ip
The loop variable can be renamed, if needed. See Loop control

Ansible Inventory - Use host entries in a group in a loop

Background: I have a dynamic ansible inventory that is built by a previously run process, I don't know the IPs until after this task completes. I have 2 groups: db servers and web servers defined in the inventory file. The specific task I am trying to complete is create some_user#'dynamic_ip_of_webserver_group'.
I think I am close but somethings not quite right. In my dbserver role main task I have:
- name: Create DB User
mysql_user:
name: dbuser
host: "{{ item }}"
password: "{{ mysql_wordpress_password }}"
priv: "someDB.*:ALL"
with_items:
- "{{ ansible_hostname }}"
- 127.0.0.1
- ::1
- localhost
- "{{ hostvars[groups['webservers']] }}"
This errors out with:
TASK [dbservers : Create DB User] *******************************************************************************************************************************************************************
fatal: [10.10.10.13]: FAILED! => {"msg": "ansible.vars.hostvars.HostVars object has no element [u'10.10.10.30', u'10.10.10.240']"}
It is showing the right IPs and there is only 2 so both of those are correct. I think it is trying to access the inventory item as an object instead of the actual input?
Inventory File:
[webservers]
10.10.10.30
10.10.10.240
Simply:
- "{{ groups['webservers'] }}"
This works, because with_items flattens first nested level of lists.

Resources