I'm with out a doubt not a coder, i'm a more traditional sysadmin who over the years has turned to services like Puppet and Chef to perform automated tasks on the systems I run.
The current darling of the Infrastructure Automation as Code world is Ansible and the following is my journey getting to grips with it.
The purpose of this blog is to get to a point where from a single Ansible host something useful can be done on several remote systems. This is a work in progress and it's a learning document. I've found most of the howto guides I've followed don't answer the questions I have at all or in a fashion which makes sense to me.
Where possible I have tried to put the issues I have had and how I resolved them.
My End Goal is to get Ansible playbooks working with Rundeck to manage remote systems.
This is NOT for production systems...
This is NOT written by an Ansible Guru...
I will answer questions on Reddit

Versions
Version |
Date |
Information |
Todo |
0.9.0 | 1 June 2020 | Basic Notes and framework got down | Complete Playbooks at end of the post |
0.9.1 | 4 June 2020 | Added an extended simple playbook example | Create a broken up Playbook with reusuable code |
Systems
I have a mix of 3 server OS on my HomeLab
- Ubuntu 18.04
- Opensuse Leap 15.1
- CentOS 7
They are on a flat subnet and run either on top of VMware ESXi or Proxmox, all of the servers have python2 installed, and to make life easier i've installed ansible on all the servers as it installs the prerequs needed to run stuff it seems.
The Ansible Host
I've set up an Ubuntu 18.04 server as what i'm calling my Ansible Host, its the server which all the ansible commands will be run from, with passwordless ssh access to the other servers on the Home Lan
Everything i'm running here, runs from my Ansible host
Passwordless SSH
These instructions are based on not having any ssh keys currently in the environment, these will be overwritten if they are already there.
Login to the Ansible host and run
ssh-keygen -t rsa
Connect to the remote host(s) you want to login to from the Ansible Host using passwordless ssh. and create a .ssh folder under the users home folder
ssh david@10.10.68.11 mkdir -p .ssh
The authenticity of host '10.10.68.11 (10.10.68.11)' can't be established.
RSA key fingerprint is 45:0e:28:11:d6:81:62:16:04:3f:db:38:02:la:22:4e.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.10.68.11' (ECDSA) to the list of known hosts.
david@10.10.68.11's password: [Enter Your Password Here]
Copy the public key over to the remote site as an authorized_keys file
cat .ssh/id_rsa.pub | ssh david@10.10.68.11 'cat >> .ssh/authorized_keys'
david@10.10.68.11's password: [Enter Your Password Here]
Change the rights on the authorized_keys, this is important depending on the version of ssh server used.
ssh david@10.10.68.11 "chmod 700 .ssh; chmod 640 .ssh/authorized_keys"
david@10.10.68.11's password: [Enter Your Password Here]
From the Ansible host ssh to the host should no longer require a password.
ssh david@10.10.68.11
Repeat the last 4 steps on each remote system the Ansible host will need to connect to.
Installing Ansible
In this example I'm using an Ubuntu 18.04 box to run my Ansible code from across the servers on my home lan
sudo apt install ansible
Setting up /etc/ansible/hosts Inventory file
The central location for the Ansible host to know which machines ansible should talk to is in /etc/ansible/hosts which can be edited the default install is pretty well documented. Its also possible to change the location of this inventory file, however that's not something I need to do.
In my case I've used IP Addresses, resolvable DNS names can also be used.
As you can also see I've created 3 groups [opensuse], [ubuntu] and [centos7] as I know i'm going to be running code based on the OS not the service I'm running on them.
[opensuse]
#git.lan - gitlab
10.10.68.173
#mbpsuse.lan - media server
10.10.68.27
#opensuse10.lan - docker and test
10.10.68.11
#database.lan - mariadb
10.10.68.10
#nexcloud.lan - nextcloud server
10.10.68.158
#homerepo.lan - Proxy server
10.10.68.167
#dc.lan - Samba Domain controller
10.10.68.20
[ubuntu]
#home.lan - Andible and general jumpbox
10.10.68.92
#external.lan - Website and external reverse proxy
10.10.68.49
#ubuntu2004.lan - Ubuntu 20.04 Desktop on Proxmox
10.10.68.171
[centos7]
#rundeck.lan - Rundeck Automation server
10.10.68.5
Save and exit the file
Testing
Does Ansible see your hosts in the hosts file?
To check that Ansible is reading your hosts file correctly run
ansible all --list-hosts
should display
hosts (11):
10.10.68.5
10.10.68.173
10.10.68.27
10.10.68.11
10.10.68.10
10.10.68.158
10.10.68.167
10.10.68.20
10.10.68.92
10.10.68.49
10.10.68.171
Test with Ansible Ping
I'm going to use the following command to test i'm able to connect from my ansible host to the remote nodes I've listed in the Inventory hosts file.
ansible all -m ping -u USER
Note the -u USER needs to be changed to the user you can ssh using the passwordless key we setup earlier with, so in my case I'm going to use a user david
ansible all -m ping -u david
When i first ran this i got both
Successes
These look like this
10.10.68.5 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Failure
Mine looked like this
10.10.68.10 | FAILED! => {
"changed": false,
"module_stderr": "Shared connection to 192.168.86.10 closed.\r\n",
"module_stdout": "/bin/sh: /usr/bin/python: No such file or directory\r\n",
"msg": "MODULE FAILURE",
"rc": 127
}
Getting Errors
So obviously the errors are not correct, and a little digging came up with the reason, it seems when the Ansible application is installed it makes sure that the python modules are setup however python3 and python2 (aka python) seem to have a few path issues.
The Python problem
On the OpenSUSE Leap and Ubuntu 18.04 i'm running python3 is installed and while the command
which python3
shows
/usr/bin/python3
the command
which python
doesn't come back with anything, which proves a problem to the Ansible application when run remotely
To solve this I ran the symlink command
sudo ln -s /usr/bin/python3 /usr/bin/python
This then showed the python executable
which python
/usr/bin/python
Which allowed me a 100 success run of the command
ansible all -m ping -u USER
We now have communication, time to run some remote commands
Adhoc Commands
What are these?
These are essentially a method of using Ansible to run commands on remote servers either as all or as groups.
ansible all -b --become-user=root -m shell -a 'hostname' -u david
The switches
-b
: Instruct ansible to become another user to run the command--become-user=root
: Run the command as aroot
user-m
: Declares which module is used in the command-a
: Declares which arguments are passed to the module
Possibly result in
10.10.68.20 | FAILED! => {
"changed": false,
"module_stderr": "Shared connection to 10.10.68.20 closed.\r\n",
"module_stdout": "sudo: a password is required\r\n",
"msg": "MODULE FAILURE",
"rc": 1
}
as we can see
sudo: a password is required
use --extra-vars with ansible_sudo_pass
ansible all -b --become-user=root -m shell -a 'hostname' -u david --extra-vars "ansible_sudo_pass=SuperSecretPassword"
will show
10.10.68.49 | SUCCESS | rc=0 >>
external
Problem is this shows the sudo password in clear text
How do i supply Ansible the sudo password without entering the password in the command line (or playbook)?
Option 1: Using group_vars
This option stores the passwords in a file on the Ansible Host in cleartext. While I agree this is only partially better than adding the password directly in a playbook, we can build on this method later
Create the group_vars folder
sudo mkdir /etc/ansible/group_vars/
Then for each group heading in your /etc/ansible/hosts file (the ones in mine above are [opensuse], [ubuntu], [centos7]
Create a file under /etc/ansible/group_vars with the same name as the group containing the following
Note: ENTERYOURSUDOPASSWORDHERE needs to be the sudo password not the actual words
ansible_user: david
ansible_become: True
ansible_become_pass: **ENTERYOURSUDOPASSWORDHERE**
You should end up the with the following file/folder structure (your group names will be different)
/etc/ansible/group_vars/opensuse
/etc/ansible/group_vars/ubuntu
/etc/ansible/group_vars/centos7
Now when I run the command
ansible all -b --become-user=root -m shell -a 'hostname' -u david
I get the result
10.10.68.49 | SUCCESS | rc=0 >>
external
I didn't need to use the --extra-vars "ansible_sudo_pass=SuperSecretPassword" switch
Option 2: Using ansible-vault
I'm adding option 2 here even though at time of writing i've not managed to get it to work, I've been following the instructions at

The flow is to on the Ansible Host
- Create an encrypted file using the ansible-vault command
- put the password in the generated file
- Save and exit
- create a hidden text file ~/.vault_pass with the password to Ansible Vault
- from the command line set an environment variable
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
- You shouldn't need to enter the password for the vault file now
As I said, at the time of writing, i'm using Option 1 as its a home lab, and will look to figure out option 2 later.
Playbooks
While its great being able to run adhoc commands from the ansible command line, the fact is for this to be powerful we should be using playbooks, sets of tasks run in order from the Ansible Host.
Rather than go over how to write playbooks, what they are and how they work which the entire internet has well covered (especially if you want to install a web server), i'm going to put here some examples and notes around them.
Install Files based on OS
Issue
When I build a new machine, there are a set of standard applications I want to install and files I like to add to the system. Rather than being a manual task I'd like to make this using Ansible Playbook.
I'd like to run this as a single playbook which has outcomes based on the remote OS as I use either Ubuntu, CentOS or OpenSUSE
Playbook
My initial Playbook is really nothing more than a YML task list
postinstall.yml
- hosts: all
become: yes
vars:
user:
- name: "david"
packages:
- nano
- wget
- nmap
- htop
tasks:
- name: Ensure a list of packages installed
update_cache: yes
package:
name: "{{ packages }}"
state: latest
- name: All done!
debug:
msg: Packages have been successfully installe
Run with the line
ansible-playbook postinstall.yml
Notes
For package to use the zypper installer on OpenSUSE python-xml needs installing
sudo zypper in python-xml
I've used the Ansible package module as its cross linus OS compatible so supports in my example Apt, RPM and Zypper
Install Files based on OS Extended
Building on the above code I've build a longer playbook.yaml file to install apps from the package manager and from repos
Code
#This section adds the repo for the OpenSUSE version of Cockpit
#I extracted the URL for the repository out of the .repo file
#its set to run only on the OpenSUSE hosts
- hosts: opensuse
become: yes
vars:
user:
- name: "david"
tasks:
- name: Adding Cockpit repo on Opensuse
zypper_repository:
name: Cockpit
repo: 'https://download.opensuse.org/repositories/systemsmanagement:/cockpit/openSUSE_Leap_15.1/'
auto_import_keys: yes
overwrite_multiple: yes
state: present
runrefresh: yes
#This section uses the ansible package installer 'package' to install software across CentOS (yum), Ubuntu (apt) and OpenSuse (zypper)
#this only works for packages where the package name is the same across the distros
- hosts: all
become: yes
vars:
user:
- name: "david"
packages:
- nano
- wget
- nmap
- htop
- cockpit
- git
- nodejs
tasks:
- name: Ensure a list of packages installed
package:
name: "{{ packages }}"
state: latest
- name: All done!
debug:
msg: Packages have been successfully installed
- hosts: all
become: yes
vars:
user:
- name: "david"
tasks:
- name: Install Netdata
git:
repo: 'https://github.com/firehol/netdata.git'
dest: /opt/netdata/
clone: yes
update: yes
- name: to run the shell script
shell: bash netdata-installer.sh --dont-wait
creates: /opt/netdata/installed
args:
chdir: /opt/netdata
handlers:
- name: to restart netdata
service:
name: netdata
state: restarted
- hosts: all
become: yes
vars:
user:
- name: "david"
tasks:
- name: Link Netdata locally to netdata cloud
shell: netdata-claim.sh -token=random_datat -rooms=random room number -url=https://app.netdata.cloud
creates: /opt/netdata/claimed
- hosts: all
become: yes
vars:
user:
- name: "david"
tasks:
- name: ensure file exists
copy:
content: ""
dest: /opt/netdata/claimed
force: no
group: sys
owner: root
mode: 0555
Notes
Some points to note here
- I start the playbook using zypper_repository because unlike Ubuntu or CentOS, Opensuse doesn't have Cockpit in its coore repository. Because I classify my devices by OS this is simple by using the hosts: opensuse command
- To install the software from the Repos, I've used the Ansible Package module which utilises the OS's built in package module which has its pro's and cons. The pro is the use of one Ansible and not so much repeating code. The con is that packages are not always named the same across distros.
- I've used the git module to pull down the netdata code from the internet locally on the box. This will upgrade Netdata as well,
- Finally i've dropped out to a shell to run a Netdata command to link the host to my Netdata cloud account, at the end of this i have used the module ensure file exists to confirm if the shell command has ben run before.
**TODO**
Update a file in a folder
Issue
Resolution
Notes
Manage /etc/ansible/hosts using git pull
Issue
Resolution
Notes
Backup my Ghost CMS Site
Issue
Resolution
Notes
Testing with Molecule
**TODO**
References
A step by step guide to Ansible (Tutorial)
SSH Passwordless Login Using SSH Keygen in 5 Easy Steps

https://docs.ansible.com/ansible/latest/modules/zypper_repository_module.html