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

mightywomble (u/mightywomble) - Reddit
u/mightywomble: 💻 Dell XPS13 - OpenSuse Tumbleweed 💻 HPChromebook X2 - ChromeOS 💻 Lenovo T430 - Windows10 📱 Samsung Note 9 📱 Oneplus6 📷 Nikon …

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 a root 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

How To Use Vault to Protect Sensitive Ansible Data on Ubuntu 16.04 | DigitalOcean
Ansible Vault is a feature that allows users to encrypt values and data structures within Ansible projects. This provides the ability to secure sensitive data that may be necessary to successfully run Ansible plays, but should not be publicly visible. Ansible is able to...

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
Ansible playbook tutorial | How to write a playbook with example | GoLinuxCloud
Ansible playbook Tutorial. What is YAML. What is Ansible Playbook. Beginners guide to write playbook with example. Sample ansible playbook to install apache

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