Date created: Friday, February 14, 2020 4:26:41 PM. Last modified: Monday, May 6, 2024 5:27:13 PM
Ansible Notes
References:
Ansible network modules == https://docs.ansible.com/ansible/latest/modules/list_of_network_modules.html
ansible_network_os == https://github.com/ansible/ansible/blob/devel/docs/docsite/rst/network/user_guide/platform_index.rst
Nested dicts == https://www.jeffgeerling.com/blog/2017/changing-deeply-nested-dict-variable-ansible-playbook
CLI Args:
ansible -vvvv -i hosts -u james.bensley -k -a "ls -l" linux_servers
# Ansible CLI args:
#
# -vvvv
# Verbosity level
#
# -i
# Inventory file name, or specify a comma seperated list of hostnames/IPs
#
# -u
# Username for SSH auth to remote device
#
# -k
# Prompt for password for SSH auth to remote device
#
# -c
# How to connect to the remote system (default=smart)
#
# -m
# Ansible module to use (default=command)
#
# -a
# Ansible module args
#
# -e
# Set additional key/value pairs like a network device os (-e "ansible_network_os=iosxr")
#
# Finish with hosts file group or match glob/pattern
SSH fingerprints
# Ansible will fail to connect to a network device if the host keys are unknown:
$ansible -i sr1.lab, all -u james.bensley -k -m sros_command -a "commands='show ver'" -c local -e "ansible_network_os=sros"
SSH password:
sr1.lab | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"msg": "Unable to decode JSON from response to exec_command({\"answer\": null, \"command\": \"show ver\", \"prompt\": null}). Received 'None'.",
"rc": 1
}
$ssh sr7.lab
The authenticity of host 'sr1.lab (192.168.0.1)' can't be established.
DSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYY.
Are you sure you want to continue connecting (yes/no)?
# To skip host checking (WHICH IS POTENTIALLY INSECURE) set the environment variable ANSIBLE_HOST_KEY_CHECKING to False:
$ANSIBLE_HOST_KEY_CHECKING=False ansible -i sr1.lab, all -u james.bensley -k -m sros_command -a "commands='show ver'" -c local -e "ansible_network_os=sros"
SSH password:
sr1.lab | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"stdout": [
"show version \nTiMOS-C-15.0.R7 cpm/hops64 Nokia 7750 SR Copyright (c) 2000-2018 Nokia.\nAll rights reserved. All use subject to applicable license agreements.\nBuilt on Wed Jan 10 15:24:09 PST 2018 by builder in /builds/150B/R7/panos/main"
],
"stdout_lines": [
[
"show version ",
"TiMOS-C-15.0.R7 cpm/hops64 Nokia 7750 SR Copyright (c) 2000-2018 Nokia.",
"All rights reserved. All use subject to applicable license agreements.",
"Built on Wed Jan 10 15:24:09 PST 2018 by builder in /builds/150B/R7/panos/main"
]
]
}
Debugging & Troubleshooting
Verbosity
# Use -v to increase Ansible verbosity$ansible-playbook -vvvvv -i hosts -u james.bensley -l my_play.yml
# Optionally enable Ansible logging and set the log path:$ANSIBLE_LOG_PATH=./ansible.log ANSIBLE_DISPLAY_ARGS_TO_STDOUT=True ansible-playbook -vvvvv -i hosts -u james.bensley -l my_play.yml
$ less ./ansible.log
Start at a specific task:
ansible-play book.yaml --start-at-task "Do that thing"
Step through tasks:
ansible-play book.yaml --step
Print facts:
- name: Print facts
ansible.builtin.debug:
var: ansible_facts
Print host vars (this includes facts):
- name: "Print host vars"
ansible.builtin.debug:
var: hostvars[inventory_hostname]
Connection methods and jump hosts / bastion hosts:
Connection module: smart/default, ssh, and paramiko
# This will use the default connection method of "smart" which will default to SSH,
# and requires sshpass to be installed, because SSH won't use a user supplied password (-k option)
# without sshpass:
ansible -i hosts -u james.bensley -k linux_servers -a "ls -l"
# To install sshpass on MacOS use:
# xcode-select --install
# brew install https://raw.githubusercontent.com/esolitos/homebrew-ipa/master/Formula/sshpass.rb
# One can explicitly chose ssh using "-c ssh":
ansible -i hosts -u james.bensley -k linux_servers -c ssh -a "ls -l"
# One can use paramiko as a wrapper to SSH, this is helpful if sshpass is unavailable (like on MacOS)
# however, paramiko can't work with jump hosts / bastion hosts:
ansible -i hosts -u james.bensley -k linux_servers -c paramiko -a "ls -l"
# One can set the default connection type for each hosts so that it doesn't need to specified on the CLI:
[linux_servers]
s1.a1.example.com ansible_connection=paramiko
s1.a2.example.com ansible_connection=paramiko
Connection module: network_cli
# The network_cli connection type is for device with proprietary SSH CLIs like network devices.
# It doesn't require an SSH key for example:
ansible -i hosts lab --limit r1.lab -u james.bensley -k -m iosxr_facts -c network_cli -e "ansible_network_os=iosxr"
ansible -i ar1.lab, all -u james.bensley -k -m iosxr_command -a "commands='show ver'" -c network_cli -e "ansible_network_os=iosxr"
ansible -i rr0.lab, all -u james.bensley -k -m ios_command -a "commands='show ver'" -c network_cli -e "ansible_network_os=ios"
ansible -i me1.lab, all -u james.bensley -k -m ce_command -a "commands='display ver'" -c network_cli -e "ansible_network_os=ce"
# The following works with 7750s but not with a 7210:
ansible -i sr1.lab, all -u james.bensley -k -m sros_command -a "commands='show ver'" -c network_cli -e "ansible_network_os=sros"
# Set the OS to a Cisco Nexus (like with NAPALM) to get 7210 to work:
ansible -i as1.lab, all -u james.bensley -k -m sros_command -a "commands='show ver'" -c network_cli -e "ansible_network_os=nxos"
SSH Jump Hosts:
# A jump host / bastion host will be used automatically if already configured in ~/.ssh/config.
# However, Ansible will not prompt for the password for the jump host, you must already have passwordless SSH keys setup.
# It is also possible to specify an Ansible specific SSH config file by adding the following to ansible.cfg:
[ssh_connection]
ssh_args = -F /path/to/ansible_ssh.cfg
# An SSH config file can contain a specific jump host per remote host.
# However, a specific jump host per remote host can be specified using a variable in the ansible file or on the CLI, if they can't be set in ~/.ssh/config:
# Set the following in host_vars/hostname.yml. To use the same jump host for all hosts set the following in group_vars/all/ansible_ssh.yml:
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q jump_user@jump_host"'
# If the above is set in group_vars for all hotsts, Ansible will try and use the jump host to reach the jump host, so a specific host entry is needed for the jump host which is blank:
host_vars/jump_host/ansible_ssh.yml:
ansible_ssh_common_args: ""
# Via the CLI:
ansible -i hosts -u james.bensley -k linux_servers -e ansible_ssh_common_args='-o ProxyCommand=ssh -W %h:%p -q jump_user@jump_host"' -a "ls -l"
# Another method is to use a port forwarding.
# This forwards port 5555 on the local host to port 22 on the remote device, through the jump_host
ssh -L 5555:device.via.jumphost.only.com:22 jump_username@jump_host
# Then connect to the local host addres on the redirected port, note the ansible variable "ansible_port=5555" which could also be in a host_vars file:
ansible -i 127.0.0.1, all -u james.bensley -k -m iosxr_command -c network_cli -a "commands='show ver'" -e "ansible_network_os=iosxr" -e "ansible_port=5555"
# Sometimes Ansible won't know the host keys of the remote device SSH device, to have Ansible ignore the host key or a changing host key, set the ENV variable "ANSIBLE_HOST_KEY_CHECKING=False" before executing the Ansible command:
ANSIBLE_HOST_KEY_CHECKING=False ansible -i 127.0.0.1, all -u james.bensley -k -m iosxr_command -c network_cli -a "commands='show ver'" -e "ansible_network_os=iosxr" -e "ansible_port=5555"
Filters
Combine:
The example below shows how to use the combine() filter to append a new key/value (testb) to an exist dict (vals) or it will update the existing key/value if it exists:
---
- hosts: GROUP_TEST
gather_facts: no
connection: local
tasks:
- name: Set test val A
set_fact:
vals:
testa: 123
- name: Set test val B
set_fact:
vals: "{{ vals|combine({'testb': 789}, recursive=True) }}"
when: inventory_hostname == "host2"
- name: Print before
debug:
msg: "{{ hostvars[inventory_hostname]['vals'] }}"
- name: Clear results variable
set_fact:
vals: null
- name: Print after
debug:
msg: "{{ hostvars[inventory_hostname]['vals'] }}"
$ansible-playbook -i hosts test.yml -v
Using /etc/ansible/ansible.cfg as config file
PLAY [GROUP_TEST] ************************************************************************************************
TASK [Set test val A] **********************************************************************************************
ok: [host1] => {"ansible_facts": {"vals": {"testa": 123}}, "changed": false}
ok: [host2] => {"ansible_facts": {"vals": {"testa": 123}}, "changed": false}
TASK [Set test val B] **********************************************************************************************
ok: [host2] => {"ansible_facts": {"vals": {"testa": 123, "testb": 789}}, "changed": false}
skipping: [host1] => {"changed": false, "skip_reason": "Conditional result was False"}
TASK [Print before] ************************************************************************************************
ok: [host1] => {
"msg": {
"testa": 123
}
}
ok: [host2] => {
"msg": {
"testa": 123,
"testb": 789
}
}
TASK [Clear results variable] **************************************************************************************
ok: [host1] => {"ansible_facts": {"vals": null}, "changed": false}
ok: [host2] => {"ansible_facts": {"vals": null}, "changed": false}
TASK [Print after] *************************************************************************************************
ok: [host1] => {
"msg": ""
}
ok: [host2] => {
"msg": ""
}
Random:
Generate random number between 1025 (inclusive) and 65536 (exclusive):
---
- hosts: all
connection: local
gather_facts: no
tasks:
- name: Generate random number
set_fact:
rnd: "{{ 65536 | random(start=1025) }}"
- name: Print random number
debug:
msg: "{{ rnd }}"
$ansible-playbook -i 127.0.0.1, rnd.yml
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Generate randmo number] ***************************************************************************************************************************************************************************************************************
ok: [127.0.0.1]
TASK [Print random number] ******************************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "16070"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Inventory
One can see all goups using ansible-inventory:
$ ansible-inventory --graph | grep "@"
09:34:37
@all:
|--@site_1:
|--@site_2:
|--@site_3:
for site in $(ansible-inventory --graph | grep "site_" | awk -F "@" '{print $2}' | cut -d ":" -f 1)
do
echo "Site: ${site}"
ansible-playbook my_play.yml --limit "${site}"
done
Meta Plays
End Play
Handy for debugging, end a play early:
---
- hosts: all
connection: local
gather_facts: no
tasks:
- name: Print text
debug:
msg: "This IS printed"
- name: End play
meta: end_play
- name: Print text
debug:
msg: "This is NOT printed"
$ansible-playbook -v -i 127.0.0.1, end.yml
Using /etc/ansible/ansible.cfg as config file
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Print text] ***************************************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "This IS printed"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Specific Tasks
Brackground Process:
Start a process and fork it to the background, grep for it's PID, then later kill the process by PID:
---
- hosts: all
connection: local
gather_facts: no
tasks:
- name: Start background process
shell: "sleep 25 >/dev/null 2>&1 &"
poll: 0
register: shell_ret
- name: Wait for process to start
pause:
seconds: 2
- name: Display shell_ret variable
debug:
msg: "{{ shell_ret }}"
- name: Get process PID
shell: pgrep sleep
register: proc_pid
- name: Wait for 10 seconds
pause:
seconds: 10
- name: Kill PID
shell: kill "{{ proc_pid['stdout'] }}"
$ansible-playbook -v -i 127.0.0.1, bg.yml
Using /etc/ansible/ansible.cfg as config file
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Start background process] *************************************************************************************************************************************************************************************************************
changed: [127.0.0.1] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "sleep 25 >/dev/null 2>&1 &", "delta": "0:00:00.002128", "end": "2021-09-17 09:33:17.380410", "rc": 0, "start": "2021-09-17 09:33:17.378282", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Wait for process to start] ************************************************************************************************************************************************************************************************************
Pausing for 2 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [127.0.0.1] => {"changed": false, "delta": 2, "echo": true, "rc": 0, "start": "2021-09-17 09:33:17.410033", "stderr": "", "stdout": "Paused for 2.0 seconds", "stop": "2021-09-17 09:33:19.410289", "user_input": ""}
TASK [Display shell_ret variable] ***********************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"cmd": "sleep 25 >/dev/null 2>&1 &",
"delta": "0:00:00.002128",
"end": "2021-09-17 09:33:17.380410",
"failed": false,
"rc": 0,
"start": "2021-09-17 09:33:17.378282",
"stderr": "",
"stderr_lines": [],
"stdout": "",
"stdout_lines": []
}
}
TASK [Get process PID] **********************************************************************************************************************************************************************************************************************
changed: [127.0.0.1] => {"changed": true, "cmd": "pgrep sleep", "delta": "0:00:00.003304", "end": "2021-09-17 09:33:19.559026", "rc": 0, "start": "2021-09-17 09:33:19.555722", "stderr": "", "stderr_lines": [], "stdout": "371", "stdout_lines": ["371"]}
TASK [Wait for 10 seconds] ******************************************************************************************************************************************************************************************************************
Pausing for 10 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [127.0.0.1] => {"changed": false, "delta": 10, "echo": true, "rc": 0, "start": "2021-09-17 09:33:19.584321", "stderr": "", "stdout": "Paused for 10.0 seconds", "stop": "2021-09-17 09:33:29.584570", "user_input": ""}
TASK [Kill PID] *****************************************************************************************************************************************************************************************************************************
changed: [127.0.0.1] => {"changed": true, "cmd": "kill \"371\"", "delta": "0:00:00.002249", "end": "2021-09-17 09:33:29.720979", "rc": 0, "start": "2021-09-17 09:33:29.718730", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ENV Vars:
Read an environment variable, if env_var is zero length, the variable didn't exist or it was empty:
---
- hosts: all
connection: local
gather_facts: no
tasks:
- name: Check if HOME is defined
set_fact:
env_var: "{{ lookup('env','HOME') }}"
- name: End play if zero length
meta: end_play
when: env_var|length == 0
- name: Print the ENV var
debug:
msg: "{{ env_var }}"
# output when checking it $HOME is defined/not-blank
$ansible-playbook -v -i 127.0.0.1, bg.yml
Using /etc/ansible/ansible.cfg as config file
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Check if HOME is defined] **********************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {"ansible_facts": {"env_var": "/home/bensley"}, "changed": false}
TASK [Print the ENV var] ********************************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "/home/bensley"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# Now check if ABC123 variable exists/is not blank
- name: Check if ABC123 is defined
set_fact:
env_var: "{{ lookup('env','ABC123') }}"
$ansible-playbook -v -i 127.0.0.1, bg.yml
Using /etc/ansible/ansible.cfg as config file
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Check if ABC123 is defined] **********************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {"ansible_facts": {"env_var": ""}, "changed": false}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vars prompt:
---
- hosts: all
connection: local
gather_facts: no
vars_prompt:
- name: userpass
prompt: Enter password
unsafe: yes
private: yes
tasks:
- name: Print result
debug:
msg: "{{ userpass}}"
$ ansible-playbook -i 127.0.0.1, test.yml
Enter password:
PLAY [all] **********************************************************************************************************************************************************************************************************************************
TASK [Print result] *************************************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "abc123"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Previous page: Thinkpad Notes
Next page: Automating Cisco IOS/XE Configuration Operations