DEV Community

Cover image for Ansible Level 1 Certification Task
Thu Kha Kyawe
Thu Kha Kyawe

Posted on

Ansible Level 1 Certification Task

Task 1 Information

To manage all servers within the stack using Ansible, the Nautilus DevOps team is planning to use a common sudo user among all servers. Ansible will be able to use this to perform different tasks on each server. This is not finalized yet, but the team has decided to first perform testing. The DevOps team has already installed Ansible on jump host using yum, and they now have the following requirement:

On the jump host, please make the necessary changes so that Ansible can utilize kareem as the default SSH user for all hosts. Ensure these adjustments are made within Ansible's default configuration without creating a new config file.


Task 2 Information

Ansible utilizes SSH connections to communicate with remote hosts. The Nautilus DevOps team intends to employ a unified Ansible manager for overseeing several remote hosts. To streamline operations, they seek to implement a common Ansible configuration to govern these hosts.

Create an Ansible configuration file under /home/thor/ansible-config directory and disable the SSH host key checking for all Ansible managed hosts.


Task 3 Information

a.. On jump host create a playbook /home/thor/ansible/playbook-t2q3.yml to copy /usr/src/itadmin-t2q3/linux-t2q3.txt file on same host at location /opt/itadmin-t2q3.

Note: Validation will try to run the playbook using command ansible-playbook -i localhost playbook-t2q3.yml so please make sure the playbook works this way without passing any extra arguments.


Task 4 Information

a. On jump host create a playbook /home/thor/ansible/playbook-t2q5.yml to copy /usr/src/itadmin-t2q5/story-t2q5.txt file from App Server 2 at location /opt/itadmin-t2q5 on App Server 2.

b. An inventory is already placed under /home/thor/ansible/inventory-t2q5.

Note: Validation will try to run the playbook using command ansible-playbook -i inventory-t2q5 playbook-t2q5.yml so please make sure the playbook works this way without passing any extra arguments.


Task 5 Information

The Nautilus DevOps team is working to create some data on different app servers in using Ansible. They want to create some files/directories and have some specific requirements related to this task. Find below more details about the same:

a. Utilise the inventory file /home/thor/playbook/inventory-t4q3, present on the jump host.

b. Create a playbook named /home/thor/playbook/playbook-t4q3.yml to create a directory named /opt/backup-t4q3 on all App Servers.

Note: Validation will attempt to execute the playbook using the command ansible-playbook -i inventory-t4q3 playbook-t4q3.yml. Please ensure the playbook functions correctly with this command alone, without requiring any additional arguments.


Task 6 Information

The Nautilus DevOps team is working to create some data on different app servers in using Ansible. They want to create some files/directories and have some specific requirements related to this task. Find below more details about the same:

Create a playbook called playbook-t4q6.yml under /home/thor/playbook/ directory and configure it to create a file called /opt/file-t4q6.txt on all App Servers. The contents of the file must be This file is created by Ansible!. Inventory is already placed under /home/thor/playbook/inventory-t4q6.

Note: Validation will try to run the playbook using command ansible-playbook -i inventory-t4q6 playbook-t4q6.yml, so please make sure the playbook works this way without passing any extra arguments.


Task 7 Information

As per the details given in the table below, you can see that, the web servers are linux based hosts and the db server is a Windows machine. Update the inventory /home/thor/playbooks/inventory-t3q6 to add a similar entry for server4.company.com host.

Find the required details from the table below.


| Alias | HOST | Connection | User | Password |

| web1 | server1.company.com | ssh | root | Password123! |

| web2 | server2.company.com | ssh | root | Password123! |

| web3 | server3.company.com | ssh | root | Password123! |

| db1 | server4.company.com | winrm | administrator | Dbp@ss123! |

Note: For Linux based hosts, use ansible_ssh_pass parameter and for Windows based hosts, use ansible_password parameter.


Task 8 Information

The Nautilus DevOps team intends to test multiple Ansible playbooks across various app servers in the Stratos DC. Before proceeding, certain prerequisites must be addressed. Specifically, the team requires the establishment of a password-less SSH connection between the Ansible controller and the managed nodes. An assigned ticket outlines the task; please carry out the following details:

a. The Jump host serves as our Ansible controller, and the Ansible playbooks will be executed through the thor user from the jump host.

b. An inventory file, /home/thor/playbook/inventory-t3q2, is available on the jump host. Utilize this inventory file to perform an Ansible ping from the jump host to App Server 3 and ensure the successful execution of the ping command.


Task 9 Information

The Nautilus DevOps team recently recommended employing Ansible for configuration management and automation purposes. As they've commenced creating playbooks, a need has arisen for a basic playbook, outlined as follows.

Create a playbook named /home/thor/ansible/playbook-t1q2.yml on the jump host and configure the playbook to run on localhost and execute an echo command echoing Welcome!.


Task 10 Information

A team member completed writing a playbook, but when we attempted to execute it, an error occurred. We need someone to review the playbook, identify the issue, and fix it.

The playbook name is /home/thor/ansible/playbook-t1q5.yml, make sure it executes without any error.


Task 1 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Confirm Ansible is installed

On jump host:

ansible --version

You should see output showing:

ansible version

config file location

Example line to observe:

config file = /etc/ansible/ansible.cfg

⚠️ This is important β€” we must modify the default config file, not create a new one.

🟩 Step 2 β€” Edit the default Ansible configuration file

Open:

sudo vi /etc/ansible/ansible.cfg
Enter fullscreen mode Exit fullscreen mode

🟩 Step 3 β€” Locate the [defaults] section

Find this section:

[defaults]

Under it, add or modify the following line:

remote_user = kareem

Save and exit.

🟩 Step 4 β€” Verify the change

Run:

ansible-config dump | grep DEFAULT_REMOTE_USER

Expected output:

DEFAULT_REMOTE_USER(/etc/ansible/ansible.cfg) = kareem

βœ” This confirms Ansible will now use kareem as default SSH user
βœ” No need to specify ansible_user in inventory anymore


🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a very simple way.

This lab changes how Ansible connects to servers.

Instead of writing the SSH user in every inventory file,
we tell Ansible:

β€œAlways use kareem unless I say otherwise.”

🌊 What Are We Building?

Think of it like this:

1️⃣ Ansible needs a username to connect
2️⃣ We set one global default username
3️⃣ Now Ansible automatically uses that user everywhere

Flow:

Ansible Command
  ↓
ansible.cfg
  ↓
remote_user = kareem
  ↓
SSH connection uses kareem

🟒 Step 1 β€” Understand How Ansible Connects

When you run:

ansible all -m ping

Ansible internally does something like:

ssh @server

If no user is defined:

It uses the current Linux user (thor)

Or a user defined in inventory

We changed that behavior.

🟒 Step 2 β€” Set a Global Default User

Inside:

/etc/ansible/ansible.cfg

We added:

[defaults]
remote_user = kareem

Now Ansible automatically assumes:

ssh kareem@server

Even if inventory does not mention a username.

🟒 Step 3 β€” Why This Is Useful

Before:

Every inventory file needed:

ansible_user=kareem

After:

Inventory can simply be:

stapp01
stapp02
stapp03

Cleaner. Simpler. Centralized.

🟒 Step 4 β€” How Ansible Chooses a User (Priority Order)

Ansible decides the SSH user like this:

1️⃣ ansible_user in inventory
2️⃣ remote_user in ansible.cfg
3️⃣ Current Linux user

We configured level #2.

So now the default becomes kareem.

🟒 Step 5 β€” Why We Edited /etc/ansible/ansible.cfg

The lab says:

β€œEnsure these adjustments are made within Ansible's default configuration without creating a new config file.”

Ansible config priority:

ansible.cfg in current directory

~/.ansible.cfg

/etc/ansible/ansible.cfg ← default system config

We modified the system default.

No new file created.

🟒 Step 6 β€” What This Looks Like During Execution

When you run:

ansible stapp01 -m ping

Internally Ansible now does:

ssh kareem@stapp01

Because:

remote_user = kareem

is globally defined.

🟒 Final Result

After the change:

βœ” Ansible automatically uses kareem
βœ” No need to specify username in inventory
βœ” Configuration is centralized
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Change Ansible default SSH behavior
βœ” Modify system-wide ansible.cfg
βœ” Understand user precedence order
βœ” Control connection behavior globally


Task 2 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Create the directory

mkdir -p /home/thor/ansible-config
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Create the Ansible configuration file

Create a file named ansible.cfg inside that directory:

vi /home/thor/ansible-config/ansible.cfg
Enter fullscreen mode Exit fullscreen mode

🟩 Step 3 β€” Add the required configuration

Insert the following content:

[defaults]
host_key_checking = False

Save and exit.

🟩 Step 4 β€” Verify Ansible is using this config file

Move into the directory:

cd /home/thor/ansible-config

# Now check:

ansible --version
Enter fullscreen mode Exit fullscreen mode

Output:

thor@jumphost ~/ansible-config$ ansible --version
ansible [core 2.14.17]
  config file = /home/thor/ansible-config/ansible.cfg
  configured module search path = ['/home/thor/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /home/thor/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.18 (main, Jan 24 2024, 00:00:00) [GCC 11.4.1 20231218 (Red Hat 11.4.1-3)] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True
Enter fullscreen mode Exit fullscreen mode

βœ” This confirms Ansible is using your custom configuration
βœ” SSH host key checking is now disabled


🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a very simple way.

This lab changes how Ansible handles SSH security checks.

🌊 What Are We Building?

Think of it like this:

Normally when Ansible connects to a new server:

Ansible
  ↓
SSH
  ↓
β€œAre you sure you trust this host?”

That message blocks automation.

We disabled that check.

🟒 Step 1 β€” What Is Host Key Checking?

When you SSH to a new server manually, you see:

Are you sure you want to continue connecting (yes/no)?

This is called host key verification.

It prevents:

Man-in-the-middle attacks

Connecting to fake servers

Ansible performs the same check by default.

🟒 Step 2 β€” Why Disable It?

In automation labs:

Servers are created dynamically

Host keys change frequently

Manual confirmation is impossible

So we disable it globally.

🟒 Step 3 β€” What This Line Does

Inside:

[defaults]
host_key_checking = False

This tells Ansible:

β€œDo NOT ask to verify SSH host keys.”

Now connection flow becomes:

Ansible
  ↓
SSH
  ↓
Connect immediately

No prompt. No interruption.

🟒 Step 4 β€” Why We Created ansible.cfg in That Directory

Ansible configuration priority order:

1️⃣ ansible.cfg in current directory
2️⃣ ~/.ansible.cfg
3️⃣ /etc/ansible/ansible.cfg

When we run Ansible from:

/home/thor/ansible-config

It automatically uses:

/home/thor/ansible-config/ansible.cfg

So we created a project-level configuration.

🟒 Step 5 β€” What Changes Internally?

Before:

ansible all -m ping

Might fail with:

Host key verification failed.

After:

ansible all -m ping

Connects immediately.

🟒 Final Result

βœ” Created custom ansible.cfg
βœ” Disabled SSH host key checking
βœ” Confirmed Ansible uses that config
βœ” Automation runs without SSH prompts
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Create project-level Ansible configuration
βœ” Disable SSH host key checking
βœ” Understand Ansible config precedence
βœ” Control SSH behavior globally


Task 3 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Create the playbook directory (if not exists)

mkdir -p /home/thor/ansible
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Create the playbook file

vi /home/thor/ansible/playbook-t2q3.yml
Enter fullscreen mode Exit fullscreen mode

🟩 Step 3 β€” Add the correct playbook content

Insert exactly this:

---
- name: Copy file on localhost
  hosts: localhost
  connection: local
  gather_facts: no

  tasks:
    - name: Copy linux-t2q3.txt to /opt/itadmin-t2q3
      copy:
        src: /usr/src/itadmin-t2q3/linux-t2q3.txt
        dest: /opt/itadmin-t2q3/linux-t2q3.txt
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Ensure destination directory exists (IMPORTANT)

sudo mkdir -p /opt/itadmin-t2q3

# Ensure proper permissions:

sudo chown thor:thor /opt/itadmin-t2q3
Enter fullscreen mode Exit fullscreen mode

🟩 Step 5 β€” Test the playbook exactly like validation will

cd /home/thor/ansible
ansible-playbook -i localhost playbook-t2q3.yml
Enter fullscreen mode Exit fullscreen mode

Expected result:

thor@jumphost ~/ansible$ ansible-playbook -i inventory-t2q5 playbook-t2q5.yml

PLAY [Copy story file on App Server 2] **********************************************************************

TASK [Ensure destination directory exists] ******************************************************************
ok: [stapp02]

TASK [Copy story file inside App Server 2] ******************************************************************
changed: [stapp02]

PLAY RECAP **************************************************************************************************
stapp02                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a very simple way.

This lab copies a file from the jump host to itself using Ansible.

🌊 What Are We Building?

Think of it like this:

Source File
/usr/src/itadmin-t2q3/linux-t2q3.txt
  ↓
Ansible Playbook
  ↓
Destination Folder
/opt/itadmin-t2q3/

Even though it’s the same machine, we still use Ansible.

🟒 Step 1 β€” Why Use hosts: localhost?

Validation runs:

ansible-playbook -i localhost playbook-t2q3.yml

So our play must target:

hosts: localhost

Otherwise it won’t match.

🟒 Step 2 β€” Why connection: local?

By default, Ansible tries SSH.

But this is the same machine.

So we tell Ansible:

connection: local

Meaning:

β€œDo NOT use SSH. Execute locally.”

🟒 Step 3 β€” What the copy Module Does
copy:
src: /usr/src/itadmin-t2q3/linux-t2q3.txt
dest: /opt/itadmin-t2q3/linux-t2q3.txt

This tells Ansible:

1️⃣ Read file from src
2️⃣ Copy it to dest
3️⃣ Create file if it does not exist

🟒 Step 4 β€” Why We Don’t Need Inventory File

Because validation runs:

-i localhost

This creates a temporary inventory with:

localhost

And our play targets exactly that.

Perfect match.

🟒 Final Result

After execution:

βœ” File exists in /opt/itadmin-t2q3/
βœ” Playbook runs without extra arguments
βœ” Works with -i localhost
βœ” No SSH required
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Run Ansible locally
βœ” Use connection: local
βœ” Write validation-safe playbooks
βœ” Copy files using Ansible


Task 4 Information

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Move to ansible directory

cd /home/thor/ansible
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Confirm App Server 2 hostname in inventory

cat inventory-t2q5
Enter fullscreen mode Exit fullscreen mode

You will likely see something like:

stapp02 ansible_host=172.16.238.11 ansible_ssh_pass=Am3ric@ ansible_user=steve
Enter fullscreen mode Exit fullscreen mode

We must target:

stapp02

(That is App Server 2.)

🟩 Step 3 β€” Create the playbook

vi playbook-t2q5.yml
Enter fullscreen mode Exit fullscreen mode

Add the following content:

---
- name: Copy story file on App Server 2
  hosts: stapp02
  become: yes
  gather_facts: no

  tasks:
    - name: Ensure destination directory exists
      file:
        path: /opt/itadmin-t2q5
        state: directory
        mode: '0755'

    - name: Copy story file inside App Server 2
      copy:
        src: /usr/src/itadmin-t2q5/story-t2q5.txt
        dest: /opt/itadmin-t2q5/story-t2q5.txt
        remote_src: yes
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Test exactly like validation

ansible-playbook -i inventory-t2q5 playbook-t2q5.yml
Enter fullscreen mode Exit fullscreen mode

Expected result:

thor@jumphost ~/ansible$ ansible-playbook -i inventory-t2q5 playbook-t2q5.yml

PLAY [Copy story file on App Server 2] **********************************************************************

TASK [Ensure destination directory exists] ******************************************************************
ok: [stapp02]

TASK [Copy story file inside App Server 2] ******************************************************************
changed: [stapp02]

PLAY RECAP **************************************************************************************************
stapp02                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a simple way.

🌊 What Are We Building?

We are copying a file inside App Server 2.

Source (on App Server 2):

/usr/src/itadmin-t2q5/story-t2q5.txt

Destination (on App Server 2):

/opt/itadmin-t2q5/story-t2q5.txt

Flow:

Jump Host
  ↓ (Ansible connects)
App Server 2
  ↓
Copy file internally

🟒 Step 1 β€” Why hosts: stapp02?

The requirement says:

Copy from App Server 2

So we must target only that host.

If we use all, the lab may fail.

Precision matters.

🟒 Step 2 β€” Why remote_src: yes Is Critical

By default, Ansible thinks:

src β†’ file is on jump host

But our file is already on App Server 2.

So we add:

remote_src: yes

This tells Ansible:

β€œThe source file is already on the remote machine.”

Without this line, the playbook will fail.

This is the most important detail in this lab.

🟒 Step 3 β€” Why We Use become: yes

The destination path is:

/opt/itadmin-t2q5

Normal users cannot write to /opt.

So we use:

become: yes

This runs tasks using sudo.

🟒 Step 4 β€” Why We Create the Directory in the Playbook

Instead of manually creating the directory, we use:

file:
state: directory

Why?

Because:

Playbook becomes self-contained

Validation does not depend on manual steps

It works even if directory does not exist

🟒 Final Result

After execution:

βœ” File copied successfully
βœ” Located at /opt/itadmin-t2q5/story-t2q5.txt
βœ” Only App Server 2 affected
βœ” Works with given inventory
βœ” No extra CLI arguments
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Copy files inside a remote server
βœ” Use remote_src: yes correctly
βœ” Use become for privileged paths
βœ” Write validation-safe playbooks


Task 5 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Move to the required directory

cd /home/thor/playbook
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Verify inventory file exists

ls -l inventory-t4q3
cat inventory-t4q3
Enter fullscreen mode Exit fullscreen mode

This inventory already contains the App Servers.
We will not modify it.

🟩 Step 3 β€” Create the playbook

vi playbook-t4q3.yml

# Add the following content:

---
- name: Create backup directory on all App Servers
  hosts: all
  become: yes
  gather_facts: no

  tasks:
    - name: Create /opt/backup-t4q3 directory
      file:
        path: /opt/backup-t4q3
        state: directory
        mode: '0755'
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Test exactly like validation

ansible-playbook -i inventory-t4q3 playbook-t4q3.yml
Enter fullscreen mode Exit fullscreen mode

Expected result:

thor@jumphost ~/playbook$ ansible-playbook -i inventory-t4q3 playbook-t4q3.yml

PLAY [Create backup directory on all App Servers] ***********************************************************

TASK [Create /opt/backup-t4q3 directory] ********************************************************************
changed: [stapp02]
changed: [stapp03]
changed: [stapp01]

PLAY RECAP **************************************************************************************************
stapp01                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
stapp02                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
stapp03                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a very simple way.

🌊 What Are We Building?

We are creating this directory:

/opt/backup-t4q3

On:

App Server 1

App Server 2

App Server 3

Flow:

Jump Host
  ↓
Ansible connects to all App Servers
  ↓
Creates directory /opt/backup-t4q3

🟒 Step 1 β€” Why hosts: all?

The inventory contains only the App Servers.

So:

hosts: all

Means:

Run this task on every server listed.

🟒 Step 2 β€” Why Use the file Module?

We use:

file:
path: /opt/backup-t4q3
state: directory

Because:

state: directory ensures the folder exists

If it already exists β†’ nothing changes

If it doesn’t β†’ Ansible creates it

This is called idempotency.

🟒 Step 3 β€” Why become: yes Is Required

/opt is a system directory.

Normal users cannot create folders there.

So we use:

become: yes

Which means:

Run the task with sudo privileges.

Without this, the playbook would fail.

🟒 Step 4 β€” Why No Extra Arguments?

Validation runs:

ansible-playbook -i inventory-t4q3 playbook-t4q3.yml

So:

We must not depend on --ask-become-pass

We must not depend on extra flags

Everything must be inside the playbook

🟒 Final Result

After execution:

βœ” Directory created on all App Servers
βœ” Located at /opt/backup-t4q3
βœ” Proper permissions applied
βœ” Works with provided inventory
βœ” No extra arguments required
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Run playbooks on multiple hosts
βœ” Create directories using file module
βœ” Use become for system paths
βœ” Write validation-safe playbooks


Task 6 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines (Validation Safe)

🟩 Step 1 β€” Move to required directory

cd /home/thor/playbook
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Verify inventory exists

ls -l inventory-t4q6
cat inventory-t4q6
Enter fullscreen mode Exit fullscreen mode

Do not modify it.

🟩 Step 3 β€” Create the playbook

vi playbook-t4q6.yml

Insert the following:

---
- name: Create file on all App Servers
  hosts: all
  become: yes
  gather_facts: no

  tasks:
    - name: Create /opt/file-t4q6.txt with required content
      copy:
        dest: /opt/file-t4q6.txt
        content: "This file is created by Ansible!"
        mode: '0644'
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Test exactly like validation

ansible-playbook -i inventory-t4q6 playbook-t4q6.yml
Enter fullscreen mode Exit fullscreen mode

Expected result:

thor@jumphost ~/playbook$ ansible-playbook -i inventory-t4q6 playbook-t4q6.yml

PLAY [Create file on all App Servers] ***********************************************************************

TASK [Create /opt/file-t4q6.txt with required content] ******************************************************
changed: [stapp03]
changed: [stapp01]
changed: [stapp02]

PLAY RECAP **************************************************************************************************
stapp01                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
stapp02                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
stapp03                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a very simple way.

🌊 What Are We Building?

We are creating this file:

/opt/file-t4q6.txt

On:

App Server 1

App Server 2

App Server 3

With this exact content inside:

This file is created by Ansible!

Flow:

Jump Host
  ↓
Ansible connects to all App Servers
  ↓
Creates file in /opt
  ↓
Writes required content

🟒 Step 1 β€” Why hosts: all?

The inventory already contains only App Servers.

So:

hosts: all

Means:

Run on every server listed in inventory.

🟒 Step 2 β€” Why Use the copy Module Instead of file?

We need to:

Create a file

Add specific content

The file module only creates empty files.

The copy module allows:

content: "This file is created by Ansible!"

So it:
1️⃣ Creates file if missing
2️⃣ Overwrites if content is wrong
3️⃣ Keeps it consistent

This is called idempotent behavior.

🟒 Step 3 β€” Why become: yes?

Because:

/opt

Is a system directory.

Normal users cannot write there.

So:

become: yes

Means:

Use sudo to execute tasks.

🟒 Step 4 β€” Why No Extra Arguments?

Validation runs:

ansible-playbook -i inventory-t4q6 playbook-t4q6.yml

So:

No --ask-become-pass

No extra flags

Everything must be defined inside playbook

🟒 Final Result

After execution:

βœ” File exists on all App Servers
βœ” Located at /opt/file-t4q6.txt
βœ” Contains exact required sentence
βœ” Proper permissions set
βœ” Works with provided inventory
βœ” No extra arguments required
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Create files with specific content
βœ” Use copy module with content
βœ” Run playbooks on multiple hosts
βœ” Use become for system paths
βœ” Write validation-safe playbooks


Task 7 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Open the inventory file

vi /home/thor/playbook/inventory-t3q6
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Review existing entries

You will likely see entries similar to:

web1 ansible_host=server1.company.com ansible_user=root ansible_ssh_pass=Password123!
web2 ansible_host=server2.company.com ansible_user=root ansible_ssh_pass=Password123!
web3 ansible_host=server3.company.com ansible_user=root ansible_ssh_pass=Password123!

Do NOT modify them.

🟩 Step 3 β€” Add entry for Windows DB server

Add the following line at the end:

db1 ansible_host=server4.company.com ansible_connection=winrm ansible_user=administrator ansible_password=Dbp@ss123!
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Verify the file

cat /home/thor/playbook/inventory-t3q6
Enter fullscreen mode Exit fullscreen mode

Output

# Sample Inventory File

# Web Servers
web1 ansible_host=server1.company.com ansible_connection=ssh ansible_user=root ansible_ssh_pass=Password123!
web2 ansible_host=server2.company.com ansible_connection=ssh ansible_user=root ansible_ssh_pass=Password123!
web3 ansible_host=server3.company.com ansible_connection=ssh ansible_user=root ansible_ssh_pass=Password123!

db1 ansible_host=server4.company.com ansible_connection=winrm ansible_user=administrator ansible_password=Dbp@ss123!
Enter fullscreen mode Exit fullscreen mode

βœ” Linux hosts use ansible_ssh_pass
βœ” Windows host uses ansible_password
βœ” Windows host explicitly sets ansible_connection=winrm

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a simple way.

🌊 What Are We Building?

We already had 3 Linux web servers:

web1 β†’ SSH β†’ root
web2 β†’ SSH β†’ root
web3 β†’ SSH β†’ root

Now we add:

db1 β†’ WinRM β†’ administrator

So the environment becomes:

Linux Servers (SSH)
+
Windows Server (WinRM)

🟒 Step 1 β€” Why Linux Uses ansible_ssh_pass?

Linux servers connect using:

ssh

So we define:

ansible_user=root
ansible_ssh_pass=Password123!

Ansible understands:

Use SSH and this password.

🟒 Step 2 β€” Why Windows Uses ansible_password?

Windows does NOT use SSH by default.

It uses:

WinRM

So we must specify:

ansible_connection=winrm
ansible_user=administrator
ansible_password=Dbp@ss123!

Notice:

Linux β†’ ansible_ssh_pass

Windows β†’ ansible_password

This difference is very important in exams.

🟒 Step 3 β€” Why We Added ansible_connection=winrm

Without it, Ansible would try:

ssh administrator@server4.company.com

Which would fail.

By adding:

ansible_connection=winrm

We tell Ansible:

β€œThis is a Windows host. Use WinRM protocol.”

🟒 Final Inventory Structure

Linux Hosts:

SSH β†’ root β†’ ansible_ssh_pass

Windows Host:

WinRM β†’ administrator β†’ ansible_password
🟒 Final Result

βœ” server4.company.com added
βœ” Correct alias: db1
βœ” Correct connection type: winrm
βœ” Correct authentication variable
βœ” Linux and Windows properly differentiated
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Configure mixed Linux & Windows inventory
βœ” Use ansible_ssh_pass vs ansible_password
βœ” Configure WinRM connection
βœ” Properly define host-specific connection types


Task 8 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Switch to thor (if not already)

whoami
Enter fullscreen mode Exit fullscreen mode

Output must be:

thor

🟩 Step 2 β€” Verify inventory file

cat /home/thor/playbook/inventory-t3q2
Enter fullscreen mode Exit fullscreen mode

Output

stapp01 ansible_host=172.16.238.10 ansible_ssh_pass=Ir0nM@n
stapp02 ansible_host=172.16.238.11 ansible_ssh_pass=Am3ric@
stapp03 ansible_host=172.16.238.12 ansible_ssh_pass=BigGr33n
Enter fullscreen mode Exit fullscreen mode

Update with this

stapp01 ansible_host=172.16.238.10 ansible_ssh_pass=Ir0nM@n ansible_user=tony
stapp02 ansible_host=172.16.238.11 ansible_ssh_pass=Am3ric@ ansible_user=steve
stapp03 ansible_host=172.16.238.12 ansible_ssh_pass=BigGr33n ansible_user=banner
Enter fullscreen mode Exit fullscreen mode

🟩 Step 3 β€” Generate SSH key (if not already created)

Check if key exists:

ls ~/.ssh
Enter fullscreen mode Exit fullscreen mode

If id_rsa does NOT exist, generate one:

ssh-keygen -t rsa
Enter fullscreen mode Exit fullscreen mode

Press Enter for:

File location

Passphrase

Confirm passphrase

This creates:

~/.ssh/id_rsa
~/.ssh/id_rsa.pub

🟩 Step 4 β€” Copy SSH key to App Server 3

Use the user defined in inventory (usually banner).

ssh-copy-id banner@stapp03
Enter fullscreen mode Exit fullscreen mode

Enter the password when prompted.

This enables password-less SSH.

🟩 Step 5 β€” Verify manual SSH works without password

ssh banner@stapp03
Enter fullscreen mode Exit fullscreen mode

It should log in without asking for password.

Exit:

exit

🟩 Step 6 β€” Run Ansible ping using provided inventory

cd /home/thor/playbook
ansible -i inventory-t3q2 stapp03 -m ping
Enter fullscreen mode Exit fullscreen mode

Expected output:

thor@jumphost ~/playbook$ ansible -i inventory-t3q2 stapp03 -m ping
stapp03 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
Enter fullscreen mode Exit fullscreen mode

βœ” SSH key authentication working
βœ” Ansible connectivity verified
βœ” Lab requirement satisfied

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a simple way.

🌊 What Are We Building?

We are creating this connection:

Jump Host (thor)
↓
SSH Key Authentication
↓
App Server 3

So Ansible can connect without typing passwords.

🟒 Step 1 β€” Why Password-less SSH?

Ansible is automation.

If every connection asks for a password:

Password:

Automation breaks.

So we use SSH keys instead.

🟒 Step 2 β€” What Does ssh-keygen Do?

It creates:

Private key β†’ stays on jump host

Public key β†’ gets copied to remote server

Think of it like:

Private Key β†’ Your secret key
Public Key β†’ Lock installed on server

If they match β†’ access granted.

🟒 Step 3 β€” What Does ssh-copy-id Do?

It:

1️⃣ Takes your public key
2️⃣ Adds it to:

~/.ssh/authorized_keys

on App Server 3
3️⃣ Enables password-less login

🟒 Step 4 β€” Why Test Manual SSH First?

If this works:

ssh banner@stapp03

Without password β†’ Ansible will work.

If manual SSH fails β†’ Ansible will fail too.

Always test SSH first.

🟒 Step 5 β€” What Does Ansible Ping Actually Test?

It checks:

SSH connectivity

Python interpreter availability

Inventory correctness

It does NOT use network ping.

🟒 Final Result

After completion:

βœ” SSH key configured
βœ” No password prompts
βœ” Ansible ping successful
βœ” Inventory used correctly
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Set up password-less SSH
βœ” Use ssh-keygen and ssh-copy-id
βœ” Test SSH connectivity properly
βœ” Validate Ansible communication


Task 9 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Move to the ansible directory

cd /home/thor/ansible
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Create the playbook file

vi playbook-t1q2.yml
Enter fullscreen mode Exit fullscreen mode

🟩 Step 3 β€” Add the correct playbook content

Insert exactly this:

---
- name: Echo Welcome on localhost
  hosts: localhost
  connection: local
  gather_facts: no

  tasks:
    - name: Run echo command
      command: echo Welcome!
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 4 β€” Test the playbook

Run:

ansible-playbook playbook-t1q2.yml
Enter fullscreen mode Exit fullscreen mode

Expected output:

thor@jumphost ~/ansible$ ansible-playbook playbook-t1q2.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does
not match 'all'

PLAY [Echo Welcome on localhost] ****************************************************************************

TASK [Run echo command] *************************************************************************************
changed: [localhost]

PLAY RECAP **************************************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what we built in a simple way.

🌊 What Are We Building?

We are telling Ansible:

β€œRun a command on this same machine.”

So the flow is:

Ansible Playbook
  ↓
Runs on localhost
  ↓
Executes: echo Welcome!
  ↓
Prints: Welcome!

🟒 Step 1 β€” Why hosts: localhost?

We are not targeting remote servers.

We want Ansible to run on:

localhost

That means:

This same jump host.

🟒 Step 2 β€” Why connection: local?

By default, Ansible tries SSH.

But localhost does not need SSH.

So we tell Ansible:

connection: local

Meaning:

Execute directly on this machine.

🟒 Step 3 β€” Why Use command Module?

We use:

command: echo Welcome!

The command module:

Executes a command

Does not use a shell

Is safe and simple

Since we just need to echo text, this is perfect.

🟒 What Happens Internally?

When you run:

ansible-playbook playbook-t1q2.yml

Ansible:

1️⃣ Reads the playbook
2️⃣ Targets localhost
3️⃣ Runs command module
4️⃣ Prints output

🟒 Final Result

βœ” Playbook created at correct path
βœ” Runs on localhost
βœ” Executes echo command
βœ” Displays β€œWelcome!”
βœ” No extra arguments required
βœ” Lab requirement satisfied

🎯 What I Learned

I learned how to:

βœ” Create a basic Ansible playbook
βœ” Run tasks on localhost
βœ” Use connection: local
βœ” Execute shell commands using Ansible


Task 10 Solutions

βœ… Part 1: Lab Step-by-Step Guidelines
🟩 Step 1 β€” Open the problematic playbook

cat /home/thor/ansible/playbook-t1q5.yml
Enter fullscreen mode Exit fullscreen mode

🟩 Step 2 β€” Check for common YAML errors

Playbook currently has:

  • host: localhost

❌ host is incorrect
βœ” It must be hosts

Ansible requires the keyword:

hosts:

Edit the file:

vi /home/thor/ansible/playbook-t1q5.yml
Enter fullscreen mode Exit fullscreen mode

Replace its content with:

---
- hosts: localhost
  connection: local
  gather_facts: no

  tasks:
    - name: Debug a message
      debug:
        msg: "Hello There!"
Enter fullscreen mode Exit fullscreen mode

Save and exit.

🟩 Step 3 β€” Validate syntax before running

Use:

ansible-playbook /home/thor/ansible/playbook-t1q5.yml --syntax-check
Enter fullscreen mode Exit fullscreen mode

This will show the exact line number of the problem.

🟩 Step 4 β€” Run playbook to confirm fix

ansible-playbook /home/thor/ansible/playbook-t1q5.yml
Enter fullscreen mode Exit fullscreen mode

Expected:

thor@jumphost ~$ ansible-playbook /home/thor/ansible/playbook-t1q5.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does
not match 'all'

PLAY [localhost] ********************************************************************************************

TASK [Debug a message] **************************************************************************************
ok: [localhost] => {
    "msg": "Hello There!"
}

PLAY RECAP **************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Enter fullscreen mode Exit fullscreen mode

🧠 Part 2: Simple Step-by-Step Explanation (Beginner Friendly)

Let’s explain what went wrong in a very simple way.

🌊 What Was the Problem?

You wrote:

host: localhost

But Ansible playbooks require:

hosts: localhost

That β€œs” matters.

YAML is strict.
Ansible expects exact keywords.

🟒 Why hosts Is Mandatory

Every playbook must define:

hosts:

It tells Ansible:

β€œWhere should this play run?”

Without it, Ansible does not understand the play structure.

🟒 Why We Added gather_facts: no

By default, Ansible gathers system facts.

For a simple debug message, that is unnecessary.

So we disable it for faster execution.

(Not mandatory, but cleaner.)

🟒 Correct Playbook Structure

Every valid playbook should look like:


  • hosts: tasks:

That is the minimum required structure.

🎯 What I Learned

I learned:

βœ” hosts is required (not host)
βœ” YAML is indentation and keyword sensitive
βœ” Small spelling errors break playbooks
βœ” How to debug simple syntax issues


Resources & Next Steps
πŸ“¦ Full Code Repository: KodeKloud Learning Labs
πŸ“– More Deep Dives: Whispering Cloud Insights - Read other technical articles
πŸ’¬ Join Discussion: DEV Community - Share your thoughts and questions
πŸ’Ό Let's Connect: LinkedIn - I'd love to connect with you

Credits
β€’ All labs are from: KodeKloud
β€’ I sincerely appreciate your provision of these valuable resources.

Top comments (0)