Ansible

Installation

user@ansible:~# python3 -mvenv ~/virtenv

user@ansible:~# . ~/virtenv/bin/activate

(virtenv) user@ansible:~# python -mpip install ansible

Collecting ansible

  Downloading ansible-8.7.0-py3-none-any.whl (48.4 MB)

     |████████████████████████████████| 48.4 MB 4.2 MB/s            

Collecting ansible-core~=2.15.7

...

(virtenv) user@ansible:~# 

Prerequisites

Inventory

First we have to create an inventory and include the nodes we want to manage. The simplest method is to create an ini-file.  Each line defines a node and is in the format shown below:

[<nodegroup>]

  <node_name>   ansible_host=<ip_address>

Logical Groups:

Nodes in group hosts:

File Structure

To organize our ansible files we will use the following file structure:

We'll explain roles later in this document.

Ad-Hoc Commands

We can use ad-hoc commands to send commands to a bunch of hosts, e.g. to check if they are reachable. We still have to use an inventory to refer to the nodes. In the following example we check if all hosts in the inventory are up and reachable:

Tasks

A task is the smallest unit in ansible. It's running a single command and its definition is usually stored in a file. An ad-hoc command is also considered as a task that is created on the fly via the command line interface. Tasks could be as simple as:

Ansible Task Definition

- name: task name

  copy:

    src  : /tmp/file1

    dest : /tmp/file2

The bash equivalent command to the ansible task would look like this:

bash$ cp /tmp/file1 /tmp/file2

Plays

A play is the next bigger unit that consists of multiple tasks ( at least one ) that are needed to accomplish a bigger goal, e.g.

Ansible Play Definition

- name: play 1

  hosts: localhost

  tasks:

    - name: task 1

      file:

        name : /tmp/dir2

        state: directory


    - name: task 2

      copy:

        src  : /tmp/file

        dest : /tmp/dir2/file2      

The bash equivalent command execution would be:

bash$ mkdir /tmp/dir2

bash$ cp /tmp/file1 /tmp/dir2/file2

Playbooks

The next bigger unit would be a playbook that consists of multiple plays, e.g.

# Playbook 1


- name: play 1

  hosts: host1

  tasks:

    - name: task 1

      file:

        name : /tmp/dir2

        state: directory

     - name: task 2

      copy:

        src  : /tmp/file

        dest : /tmp/dir2/file2


- name: play 2

  hosts: host2

  tasks:

    - name: task 1

      yum:

        name: httpd

    

    - name: task 2

      yum:

        name: php

Roles

A role is like a playbook that is written in a reusable way. In a sense it's similar to a function in programming. It can be used to deploy a specific software that could be run on multiple operating systems, so we hide the complexity in the role and just apply it in a play, e.g.

A role is divided into several files within the following directory structure:

Main Directories:

Other Directories:

Below is an example how roles are used in a playbook:

# Playbook


- name: install apache

  hosts: 

    - host1

    - host2

  roles:

    - roles/httpd

    - roles/php

host1: centos9

host2: debian11

Modules

A module is a command in ansible that is used in tasks. It's usually a python script that is run on the target and if written in an idempotent way it makes sure the result of that script execution is always the same. Usually the command expects some arguments to use, e.g. filepath, username, ...

We have used modules in our examples before when we used tasks. We called the module we used as command. The following example is using the file module with the arguments name and state to indicate that we want the directory /tmp/dir2 present on the target host.

- name: task 1

  file:

    name : /tmp/dir2

    state: directory

Writing custom  Modules

We are going to create a simple hello world module that runs on the target and returns the json string: { "hello" : "world" }

# ./library/hello.py

from ansible.module_utils.basic import *


def main():

  module = AnsibleModule(argument_spec={})       # declare the code to be a module

  res    = {"hello":"world"}                     # prepare the response as json

  module.exit_json(changed=False, meta=res)      # return the response


if __name__ == '__main__':                       # make the script run standalone

  main()

We would use that module in a playbook like so:

---

# ./playbooks/playbook.yml


- name: playbooks.hello

  hosts: localhost

  tasks:

    - name: playbook.hello.tasks.module.run

      hello:

This is how a run would look like. We use the environment variable ANSIBLE_LIBRARY to include our module.