Starting with Ansible

Share

This article covers basics of ansible based automation

  1. File structure
  2. Inventory preparation
  3. Securing credentials
  4. Example playbooks to test

Structure

  1. Simple ansible based automation have a inventory file and example playbooks. In this document we will prepare both in YAML format.
  2. Extended variables can also be stored outside main inventory file and these variable files are placed inside “group_vars” and “host_vars” directory.
  3. “group_vars” contains variables common to all hosts.
  4. “host_vars” contains variables specific to a host. For eg, if some hosts are using different ssh creds, then these can be defined in host specific file inside “host_vars” dir.
  5. Confidential information such as user/password, keys etc are prepared and stored in encrypted form using ansible vault.

For the test/demo setup following structure is prepared

tree ansible-works/
ansible-works/
├── backup <- unused
├── group_vars
│   └── all
│       └── creds.vault.yaml <- contains encrypted ssh username/password 
├── homecreds.yml    <- unused
├── inventory.yaml   <- Inventory file
├── listfiles.yaml   <- playbook to list files in /tmp 
└── checkhosts.yaml    <- playbook to check connection

Inventory preparation

Let us see details of following inventory file:

cat inventory.yaml
home_hosts:
  hosts:
    berry_nw_101:
      ansible_host: 192.168.101.200
    berry_nw_1:
      ansible_host: 192.168.1.200
    berry_localhost:
      ansible_host: 127.0.0.1
  vars:
    ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
    ansible_user: "{{ ssh_username }}"
    ansible_password: "{{ ssh_password }}"

“home_hosts” is just a host group name to bundle set of hosts. You can have multiple groups. For eng say “datacentre1-hosts”, “datacentre2-hosts”

“hosts” is a keyword and is used to define host you want to connect and run commands on. Each host entity shall have a logical name, say its hostname, for eg “berry_nw_101.

“ansible_host” is again a keyword and holds IPaddress or DNS name of device/server/host machine.

“vars” is also a keyword and holds varibles you want to define directory or as template. For eg in above eg, “ansible_ssh_common_args” is defined to skip host finger print verification, “ansible_user”, is a keyword, and shall pick value from “ssh_username” macro defined in “creds.vault.yaml” inside “group_vars” directory. Same for “ansible_password”.

Securing credentials

To avoid exposing sensitive information like ssh user/pass, we are using ansible vault to encrypt user/password file using a passowrd.

Create valut file:

ansible-vault create group_vars/all/creds1.vault.yaml

New Vault password:
Confirm New Vault password:

Add username password macro variables defined in inventory file (ssh_username, ssh_password in this case):

ssh_username: testuser
ssh_password: somepass

Save it. You can edit it in future using “ansible-vault edit <filename>” command.

Content is vault file are stored encrypted:

cat group_vars/all/creds.vault.yaml

$ANSIBLE_VAULT;1.1;AES256
34666663353465313337386361613566613436313738343935646462303038353436396132323631
3462616436623531346465373531373833646363356234370a663164336630346234343364343538
32616637376436353830373739653966336632393637303830313963373162636661326139666662
3564336265376562310a356631346562633239323664613461333536613031333966396637663337
31326666656239316265303963316633306365313364633036393837323335363766323134396537
3036393232383035343735336130343439306162303937616539

At this point you can verify inventory file using following command:

ansible-inventory -i inventory.yaml --list --ask-vault-pass

Enter vault password to decrypt vault

Vault password:
{
    "_meta": {
        "hostvars": {
            "berry_localhost": {
                "ansible_host": "127.0.0.1",
                "ansible_password": "{{ ssh_password }}",
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no",
                "ansible_user": "{{ ssh_username }}",
                "ssh_password": "somepass",
                "ssh_username": "testuser"
            },
            "berry_nw_1": {
                "ansible_host": "192.168.1.200",
                "ansible_password": "{{ ssh_password }}",
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no",
                "ansible_user": "{{ ssh_username }}",
                "ssh_password": "somepass",
                "ssh_username": "testuser"
            },
            "berry_nw_101": {
                "ansible_host": "192.168.101.200",
                "ansible_password": "{{ ssh_password }}",
                "ansible_ssh_common_args": "-o StrictHostKeyChecking=no",
                "ansible_user": "{{ ssh_username }}",
                "ssh_password": "somepass",
                "ssh_username": "testuser"
            }
        },
        "profile": "inventory_legacy"
    },
    "all": {
        "children": [
            "ungrouped",
            "home_hosts"
        ]
    },
    "home_hosts": {
        "hosts": [
            "berry_nw_101",
            "berry_nw_1",
            "berry_localhost"
        ]
    }
}

Example playbooks to test

Check ssh connection to hosts

This playbook uses ansible.builtin.ping ansible module. Remember this is not ICMP ping.

 cat checkhosts.yaml
- name: Check connections
 hosts: home_hosts
 tasks:
  - name: Ping lab hosts
    ansible.builtin.ping:
  - name: Print message
    ansible.builtin.debug:
      msg: Test connection

This playbook shall check ssh connection to hosts using credentials defined in creds.vault.yaml file

 ansible-playbook -i inventory.yaml checkhosts.yaml --ask-vault-pass

Enter vault password

Vault password:

PLAY [Check connections] ********************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************
[WARNING]: Host 'berry_localhost' is using the discovered Python interpreter at '/usr/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/devel/reference_appendices/interpreter_discovery.html for more information.
ok: [berry_localhost]
[WARNING]: Host 'berry_nw_101' is using the discovered Python interpreter at '/usr/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/devel/reference_appendices/interpreter_discovery.html for more information.
ok: [berry_nw_101]
[ERROR]: Task failed: Failed to connect to the host via ssh: ssh: connect to host 192.168.1.200 port 22: Connection timed out
fatal: [berry_nw_1]: UNREACHABLE! => {"changed": false, "msg": "Task failed: Failed to connect to the host via ssh: ssh: connect to host 192.168.1.200 port 22: Connection timed out", "unreachable": true}

TASK [Ping lab hosts] ***********************************************************************************************************************************************************************
ok: [berry_nw_101]
ok: [berry_localhost]

TASK [Print message] ************************************************************************************************************************************************************************
ok: [berry_nw_101] => {
   "msg": "Test connection"
}
ok: [berry_localhost] => {
   "msg": "Test connection"
}

PLAY RECAP **********************************************************************************************************************************************************************************
berry_localhost            : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
berry_nw_1                 : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
berry_nw_101               : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Here of three, two hosts are working. One host is dead at the time of test.

List files in /tmp of host machines

 cat listfiles.yaml

Here “directory” is a variable with “/tmp” value and playbook uses OS “ls” command to list files.

- name: Home Play
  hosts: home_hosts

  vars:
    directory: /tmp

  tasks:

    - command: "ls {{directory}}"
      register: dir_out

    - debug: var={{item}}
      with_items: dir_out.stdout_lines

Run it to test

ansible-playbook -i inventory.yaml listfiles.yaml --ask-vault-pass

Enter Vault password

Vault password:

PLAY [Home Play] ****************************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************
[WARNING]: Host 'berry_nw_101' is using the discovered Python interpreter at '/usr/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/devel/reference_appendices/interpreter_discovery.html for more information.
ok: [berry_nw_101]
[WARNING]: Host 'berry_localhost' is using the discovered Python interpreter at '/usr/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/devel/reference_appendices/interpreter_discovery.html for more information.
ok: [berry_localhost]
[ERROR]: Task failed: Failed to connect to the host via ssh: ssh: connect to host 192.168.1.200 port 22: Connection timed out
fatal: [berry_nw_1]: UNREACHABLE! => {"changed": false, "msg": "Task failed: Failed to connect to the host via ssh: ssh: connect to host 192.168.1.200 port 22: Connection timed out", "unreachable": true}

TASK [command] ******************************************************************************************************************************************************************************
changed: [berry_nw_101]
changed: [berry_localhost]

TASK [debug] ********************************************************************************************************************************************************************************
ok: [berry_nw_101] => (item=dir_out.stdout_lines) => {
    "dir_out.stdout_lines": [
        "checklist.txt",
        "piperun",
        "fluent",
        "td-lock-202ssssw2b",
        "SERVERENGINE_SOCKETMANAGER_2025-08-27T03:04:54Z_2451",
        "test.txt"
    ],
    "item": "dir_out.stdout_lines"
}
ok: [berry_localhost] => (item=dir_out.stdout_lines) => {
    "dir_out.stdout_lines": [
        "global.txt",
        "apache.logs",
        "fluentd-lock-20250827-2451-rhfw2b",
        "somedata"
    ],
    "item": "dir_out.stdout_lines"
}

PLAY RECAP **********************************************************************************************************************************************************************************
berry_localhost            : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
berry_nw_1                 : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
berry_nw_101               : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Here of 3, on two hosts “ls /tmp” got executed and out is recorded.

You can write more complex automations using ansible framework with nested playbooks using roles/tasks/templates.

manish