Ansible uses Jenkins templates to build the playbook.
Templates can use variables
from the inventory file, or part of the facts
collected.
Variables can also be extracted from the inventory
.
Additional variables can be set via the --extra-vars
flag.
Set default values with:
node_file: "node-v{{ node_ver }}-linux-x64.tar.xz"
The following playbook downloads a specific version of nodejs to the webservers
group:
root@f12d33c83ada:~# cat playbook.yaml
---
- name: Download nodejs
gather_facts: no
hosts: webservers
vars:
node_host: 'https://nodejs.org/'
node_ver: "{{ node_version | default('14.18.2') }}"
node_file: "node-v{{ node_ver }}-linux-x64.tar.xz"
node_url: "{{ node_host }}/dist/v{{ node_ver }}/{{ node_file }}"
tasks:
- name: download nodejs
get_url:
url: "{{ node_url }}"
dest: "/tmp/{{ node_file }}"
force: yes
Running it to change the version:
root@f12d33c83ada:~# ansible-playbook -i inventory.yaml playbook.yaml --extra-vars node_version=16.13.1
With package manager in debian-like OS.
apt install python3
apt install python3-pip
apt install python3-argcomplete
Once python is installed, get ansible with:
python -m pip install ansible
Enable auto-complete:
mkdir ~/.ansible-auto
activate-global-python-argcomplete3 --dest ~/.ansible-auto
source ~/.ansible-auto/python-argcomplete.sh
# Remember to include this in your runtimeconfig
Generate config file with al options disabled:
ansible-config init -t all --disabled > ~/.ansible.cfg
Test installation by running ping
module locally:
root@d29cb20e2399:~# ansible localhost -m ping
[WARNING]: No inventory was parsed, only implicit localhost is available
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}
When running commands remotely, ansible will attempt to use ssh authentication.
The first time you ssh to a new host, it will require to validate the fingerprint:
root@f12d33c83ada:~# ansible all -i 172.17.0.3, -m ping
The authenticity of host '172.17.0.3 (172.17.0.3)' can't be established.
ECDSA key fingerprint is SHA256:ON9GHyGDFBtEvMDi1D6ZTZ+xPBPNsZzBcGmORUIn06g.
Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C [ERROR]: User interrupted execution
This can be disabled by setting the host_key_checking
to false:
root@f12d33c83ada:~# fgrep host_key ~/.ansible.cfg
host_key_checking=False
The next step is to decide if you are going to manually input the password every time you run your playbook.
To do this, you'll need to use the --ask-pass
flag, and have ssh-pass
installed on your system:
root@f12d33c83ada:~# ansible all -i 172.17.0.3, -m ping --ask-pass
SSH password:
172.17.0.3 | FAILED! => {
"msg": "to use the 'ssh' connection type with passwords or pkcs11_provider, you must install the sshpass program"
}
After installing with apt install sshpass
:
root@f12d33c83ada:~# ansible all -i 172.17.0.3, -m ping --ask-pass
SSH password:
[WARNING]: No python interpreters found for host 172.17.0.3 (tried ['python3.10', 'python3.9', 'python3.8', 'python3.7', 'python3.6',
'python3.5', '/usr/bin/python3', '/usr/libexec/platform-python', 'python2.7', 'python2.6', '/usr/bin/python', 'python'])
Finally, to run playbooks without providing any password at all, use ssh-keygen
and ssh-copy-id <user>@<host>
to use key based authentication.
Running ansible modules remotely requires python being installed in the remote system.
To show this we need to have a simple inventory with all the hosts:
root@f12d33c83ada:~# cat inventory.yaml
all:
hosts:
172.17.0.3:
172.17.0.4:
Trying to use ping get us:
root@f12d33c83ada:~# ansible all -i inventory.yaml -m ping
[WARNING]: No python interpreters found for host 172.17.0.4 (tried ['python3.10', 'python3.9', 'python3.8', 'python3.7', 'python3.6',
'python3.5', '/usr/bin/python3', '/usr/libexec/platform-python', 'python2.7', 'python2.6', '/usr/bin/python', 'python'])
172.17.0.4 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"module_stderr": "Shared connection to 172.17.0.4 closed.\r\n",
"module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n",
"msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
"rc": 127
}
[...]
You can use the raw
module to install python in the remote host:
root@f12d33c83ada:~# ansible all -i inventory.yaml -m raw -a "apt update && apt install -y python3-minimal"
172.17.0.3 | CHANGED | rc=0 >>
[...]
Processing triggers for libc-bin (2.31-13+deb11u2) ...
Shared connection to 172.17.0.3 closed.
172.17.0.4 | CHANGED | rc=0 >>
Hit:1 http://security.debian.org/debian-security bullseye-security InRelease
Hit:2 http://deb.debian.org/debian bullseye InRelease
[...]
Once python is installed, ansible will find the interpreter:
root@f12d33c83ada:~# ansible all -i inventory.yaml -m ping
172.17.0.3 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
172.17.0.4 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Ansible requires an inventory with the name of targets (hosts) where it will run the modules.
The inventory allows multiple levels of grouping, and can also include variables for each host.
You can also add specific options like ansible_connection
root@f12d33c83ada:~# cat inventory.yaml
---
all:
hosts:
mylocal:
ansible_connection: local
children:
webservers:
hosts:
w1:
ansible_host: 172.17.0.3
http_port: 80
database:
vars:
ntp_server: time.google.com
hosts:
d1:
ansible_host: 172.17.0.4
d2:
ansible_host: 172.17.0.5
You can use a linter to confirm that the syntax of the file is correct before trying to use it:
root@f12d33c83ada:~# pip install yamllint
root@f12d33c83ada:~# yamllint inventory.yaml
root@f12d33c83ada:~#
Then we can run the module in a single host, a group, or use --limit
to run it only in one host or a sub-group in a group.
# Single host
root@f12d33c83ada:~# ansible mylocal -i inventory.yaml -m ping
mylocal | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
# Group
root@f12d33c83ada:~# ansible webservers -i inventory.yaml -m ping
w1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
# Group with limit
root@f12d33c83ada:~# ansible database -i inventory.yaml -m ping --limit d1
d1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Every time you run an ansible command remotely, it starts by collecting a list fo facts about the system.
This facts are variables that can be used in the playbook.
collecting facts can be disabled to speed-up the playbook run
We can add custom facts to the target system by writing to /etc/ansible/facts.d
a INI
file with .fact
extension.
Use the setup
(ansible.builtin.setup
) or gather_facts
module to list all the facts:
root@f12d33c83ada:~# ansible webservers -i inventory.yaml -m setup | head
w1 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.17.0.3"
],
"ansible_all_ipv6_addresses": [],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
Create a simple playbook that writes a file to the target host:
---
- name: first
hosts: webservers
tasks:
- name: write file
command: "touch /tmp/test"
Run the playbook on the inventory:
root@f12d33c83ada:~# ansible-playbook -i inventory.yaml playbook.yaml
PLAY [first] ********************************************************************************************
TASK [Gathering Facts] ********************************************************************************
ok: [w1]
TASK [write file] **************************************************************************************
changed: [w1]
PLAY RECAP ******************************************************************************************
w1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Here you see how the playbook included only one task, but it ended up running two tasks, because it always start by collecting facts.
We can disable this by setting gather_facts: no
in the playbook:
---
- name: first
gather_facts: no
hosts: webservers
tasks:
- name: write file
command: "touch /tmp/test"
Then:
root@f12d33c83ada:~# ansible-playbook -i inventory.yaml playbook.yaml
PLAY [first] *******************************************************************************************************
TASK [write file] **************************************************************************************************
changed: [w1]
PLAY RECAP *********************************************************************************************************
w1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0