Table of Contents

Ansible

How to install Ansible

On a Linux machine. This may also we a WSL machine do the following:

sudo apt get update
sudo apt install ansible
sudo apt install sshpass

Installation is complete!

Configure Ansible

To configure Ansible create an inventory file. Make a directory on the root the Linux Directory:

mkdir inventory

Inside the inventory folder, create a file called hosts

nano hosts

Inside the hosts file add the following:

[servers]
212.71.234.106
192.168.1.30
192.168.1.40
192.168.1.50
192.168.1.6

The top line is the label that will be used when issuing Ansible commands. The IP's below are the machine that Ansible will communicate with.

Now run your first Ansible command!

ansible -i ./hosts servers -m ping --user root --ask-pass

The above takes the input of the hosts file created and specifies the label “servers” as the target for the action. -m indicates an ansible module to be used. In this case it is ping. –user specifies the user that will be used to run the commands remotely on the Servers. –ask-pass will prompt for a password

You should see the following:

192.168.1.40 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.1.30 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
212.71.234.106 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
192.168.1.50 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.1.6 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Other Ansible commands

The command below runs and adhoc command in the way of directly communicating at a interactive session. -a indicates an adhoc command. In the case below the release version of the Linux OS will be displayed

ansible -i ./hosts servers -a "cat /etc/os-release" --user root --ask-pass

The following is displayed:

212.71.234.106 | CHANGED | rc=0>>
PRETTY_NAME="Ubuntu 21.10"
NAME="Ubuntu"
VERSION_ID="21.10"
VERSION="21.10 (Impish Indri)"
VERSION_CODENAME=impish
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=impish
192.168.1.40 | CHANGED | rc=0>>
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
192.168.1.30 | CHANGED | rc=0>>
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
192.168.1.50 | CHANGED | rc=0>>
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
192.168.1.6 | CHANGED | rc=0>>
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

To display the timezone on your Servers run the following:

ansible -i ./hosts servers -a "timedatectl" --user root --ask-pass

The following is displayed:

192.168.1.6 | CHANGED | rc=0>>
               Local time: Thu 2022-06-16 06:47:41 UTC
           Universal time: Thu 2022-06-16 06:47:41 UTC
                 RTC time: Thu 2022-06-16 06:47:41
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
212.71.234.106 | CHANGED | rc=0>>
               Local time: Thu 2022-06-16 07:47:41 BST
           Universal time: Thu 2022-06-16 06:47:41 UTC
                 RTC time: Thu 2022-06-16 06:47:41
                Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
192.168.1.40 | CHANGED | rc=0>>
               Local time: Thu 2022-06-16 07:47:41 BST
           Universal time: Thu 2022-06-16 06:47:41 UTC
                 RTC time: n/a
                Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
192.168.1.30 | CHANGED | rc=0>>
               Local time: Thu 2022-06-16 07:47:41 BST
           Universal time: Thu 2022-06-16 06:47:41 UTC
                 RTC time: n/a
                Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
192.168.1.50 | CHANGED | rc=0>>
               Local time: Thu 2022-06-16 07:47:41 BST
           Universal time: Thu 2022-06-16 06:47:41 UTC
                 RTC time: n/a
                Time zone: Europe/London (BST, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Ansible Playbooks

It is useful running adhoc commands but there are times when several commands need to be run at the same time or more complex commands or you want commands run in a efficient format. This is where ansible playbooks are used. A playbook is a list of commands that are run run against multiple machines. Playbooks are in YAML format.

Example 1 - Nano Install

See an example playbook below:

- name: nano
  hosts: servers
  tasks:
    - name: ensure nano is installed
      apt:
        name: nano
        state: present

The playbook above has a name of nano - This can be whatever is relevant to the task you want to run. hosts specifies the machines that the playbook will be run against. In this case it will be servers, this is in reference to the label created in the inventory hosts file. tasks will be various tasks that this playbook will run. name is the name of that task. apt is the module that will be used. name is the package that you want to perform an action for. state is the current state of the package, in this case it is present, meaning that nano should be present. If not it will be installed. There are a number of states, see below:

The playbook can be run using the following:

ansible-playbook ./nanoeditor.yaml --user root --ask-pass --ask-become-pass -i ./hosts

The above is the same as other commands but uses ansible-playbook instead of ansible. The parameter –ask-become-pass means that when the apt module is used remotely to apply nano sudo privileges will be used.

The result of the command is:

SSH password:
BECOME password[defaults to SSH password]:

PLAY [nano] ********************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************
ok: [192.168.1.40]
ok: [192.168.1.6]
ok: [192.168.1.30]
ok: [192.168.1.50]
ok: [212.71.234.106]

TASK [ensure nano is installed] ************************************************************************************************************************************************************
ok: [192.168.1.6]
ok: [212.71.234.106]
ok: [192.168.1.50]
ok: [192.168.1.30]
ok: [192.168.1.40]

PLAY RECAP *********************************************************************************************************************************************************************************
192.168.1.30               : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.40               : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.50               : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.6                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
212.71.234.106             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The above indicates that nano is installed and this can be seen by the ok given on the Task and PLAY RECAP

Example 2 - Set Timezone

Another playbook example would be to set the timezone for all Linux instances. The playbook for this is below:

- name: timezone
  hosts: "*"
  become: yes
  tasks:
  - name: Set Timezone
    shell: timedatectl set-timezone Europe/London

The above YAML file is the same as the previous playbook with notable exceptions. The parameter become has been added. This means that elevated privileges are used to run this playbook. shell is used for the module. This is the same as directly running a command at an interactive session.

Run the playbook with the command:

ansible-playbook ./timezone.yaml --user root --ask-pass --ask-become-pass -i ./hosts

The result is:

SSH password:
BECOME password[defaults to SSH password]:

PLAY [timezone] ****************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************
ok: [212.71.234.106]
ok: [192.168.1.40]
ok: [192.168.1.6]
ok: [192.168.1.30]
ok: [192.168.1.50]

TASK [Set Timezone] ************************************************************************************************************************************************************************
changed: [192.168.1.40]
changed: [192.168.1.6]
changed: [212.71.234.106]
changed: [192.168.1.50]
changed: [192.168.1.30]

PLAY RECAP *********************************************************************************************************************************************************************************
192.168.1.30               : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.40               : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.50               : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.1.6                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
212.71.234.106             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Note how the above result shows changed=1. This means that the configuration for timezone has changed and the timezone change has been applied.

Example 3 - Copy files

See the YAML below:

- name: file
  hosts: "*"
  become: yes
  tasks:
    - name: start copy
      ansible.builtin.copy:
        src: /home/amit/test.txt
        dest: /root
        remote_src: yes
        owner: root
        mode: '0777'

The above copies files from a particular source to a destination. It is very important to specify remote_src otherwise it will be assumed the files are coming from the same machine that is Hosting ansible.

Example 4 - Change file contents

See YAML below:

- name: Change file contents
  hosts: "*"
  become: yes
  tasks:
  - name: change file contents
    ansible.builtin.lineinfile:
      path: /home/amit/test.txt
      regexp: 'hello mr amit'
      line: ansible is working well!

The above uses the module ansible.builtin.lineinfile to replace contents in a file. regexp is used to search for text to be replaced. line is what will be replacing regexp

Use Key based authentication

The ideal way to connect to remote hosts is to use key based (rsa) authentication. To do this do the following on the ansible Host:

ssh-keygen -t rsa

This will generate a public and private key pair. The public keys need to be distributed to all Hosts that ansible will be serving.

To view the public key navigate to:

cd /root/.ssh

Then view the public key with:

cat id_rsa.pub

The following (similar) will be displayed:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7DMfSVbNJU+NRzc9oeYmVXctBcUyFGLToNal/X2VqTeMI9I/bw0hoKc4zBQzva/nbmTaNMf+JGk3anOKj4Sg9GXyi+/Z51rJ4yYr2fIowFNXm1ltbcOLtBTr6YfJcKkjbOXmVPF4KPWC516P9/kfOx8jSuQpmTx4vYhI0o8Yo9o1YHaNRuijRU5t8NPONDgVJp9rhBDMDDVzg6Y7xTdgawBav3L6hco1Wyk3j41HPSZUoICpXS2K8uVj427WLaFe7/J6U7fIkGlTbD5UL/TLdhgYV29Sskbf5UQxogw3SGxHUqTmbVggQpwNlCUMsyBM61eO+GKrpQvEXGTikQi2tXqRbm2HQROWC32v6VtvwrB5g7tBeqEl/PzMAaHiZm3Foj8juyp6JgFA1lHVDGx2aPGNQItCcrqwVqG/hV4+s4ps7P59a9aKgP2yQcacCcRm0Cx1cbK6AQuSQ7+3oAwUW/IxOsq44YWAuy+fvLfCJGk+LoRzyZjf0UXWlvQAe5L0= root@LT001752

Copy the above to the following location on all remote Host machines:

/root/.ssh/authorized_keys

Once the public key has been added to all Hosts, ansible commands can be run as follows:

ansible-playbook ./copysshpub.yaml -i ./oneserver

ansible-playbook ./changefile.yaml -i ./oneserver

ansible -i ./hosts servers -m ping

Notice how it is no longer necessary to use: –user root –ask-pass –ask-become-pass