docs: add 100 Days of DevOps challenge notes

This commit is contained in:
2026-08-04 23:18:41 +02:00
parent 76d4b4d716
commit a6ee7a2b07
24 changed files with 6901 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
# Assignment
The Nautilus DevOps team had a discussion about, how they can train different team members to use Ansible for different automation tasks. There are numerous ways to perform a particular task using Ansible, but we want to utilize each aspect that Ansible offers. The team wants to utilise Ansible's conditionals to perform the following task:
An inventory file is already placed under /home/thor/ansible directory on jump host, with all the Stratos DC app servers included.
Create a playbook /home/thor/ansible/playbook.yml and make sure to use Ansible's when conditionals statements to perform the below given tasks.
Copy blog.txt file present under /usr/src/data directory on jump host to App Server 1 under /opt/data directory. Its user and group owner must be user tony and its permissions must be 0644 .
Copy story.txt file present under /usr/src/data directory on jump host to App Server 2 under /opt/data directory. Its user and group owner must be user steve and its permissions must be 0644 .
Copy media.txt file present under /usr/src/data directory on jump host to App Server 3 under /opt/data directory. Its user and group owner must be user banner and its permissions must be 0644.
NOTE: You can use ansible_nodename variable from gathered facts with when condition. Additionally, please make sure you are running the play for all hosts i.e use - hosts: all.
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml, so please make sure the playbook works this way without passing any extra arguments.
# Solution
# Ansible Playbook — per-server copies using `when` conditionals
Create `/home/thor/ansible/playbook.yml` so `ansible-playbook -i inventory playbook.yml` copies a
different file to each app server, selected with **`when` conditionals** on a single
`hosts: all` play — with **no extra arguments**.
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredoc
> below writes the playbook.
## Step 0 — Confirm the inventory hostnames
The conditionals branch on the hostnames as written in your inventory, so read them first:
```bash
cat /home/thor/ansible/inventory
```
Use exactly the names it lists (`stapp01`, `stapp02`, `stapp03` in the standard setup) in the
`when:` lines below. Also confirm `ansible_become_pass` is present, since the play needs root and
`-K` can't be passed.
## Step 1 — Playbook
```bash
cat > /home/thor/ansible/playbook.yml <<'EOF'
---
- name: Copy files to app servers using conditionals
hosts: all
become: yes
tasks:
- name: Copy blog.txt to App Server 1
ansible.builtin.copy:
src: /usr/src/data/blog.txt
dest: /opt/data/blog.txt
owner: tony
group: tony
mode: '0644'
when: inventory_hostname == "stapp01"
- name: Copy story.txt to App Server 2
ansible.builtin.copy:
src: /usr/src/data/story.txt
dest: /opt/data/story.txt
owner: steve
group: steve
mode: '0644'
when: inventory_hostname == "stapp02"
- name: Copy media.txt to App Server 3
ansible.builtin.copy:
src: /usr/src/data/media.txt
dest: /opt/data/media.txt
owner: banner
group: banner
mode: '0644'
when: inventory_hostname == "stapp03"
EOF
```
## How it works
### The conditional pattern
With **`hosts: all`**, every task is evaluated against **every** host. The `when:` clause is what
narrows each one:
- On stapp01, task 1's condition is true → it **runs**; tasks 2 and 3 are false → **skipped**.
- On stapp02, only task 2 runs. On stapp03, only task 3.
So the play visits all three servers, and each executes exactly the one task meant for it. The
`PLAY RECAP` will show roughly `ok=2 changed=1 skipped=2` per host — **skipped tasks are expected
and correct here**, not a failure.
This is the opposite structural choice from writing three separate plays (`hosts: stapp01`, etc.).
Both produce the same end state; the task explicitly asks for the conditional form, which is what
you'd reach for when the differences are small and you want one play covering the fleet.
### Why `inventory_hostname`
`inventory_hostname` is a **magic variable** holding the host's name **exactly as written in the
inventory file**. That makes it the most reliable thing to branch on:
- You can read its value directly (`cat inventory`) — no guessing, no remote lookup.
- It doesn't depend on fact gathering.
- It can't drift from what the inventory says, because it *is* what the inventory says.
**The alternative — `ansible_nodename`:** the task's note mentions it, and it also works, but it's a
**gathered fact** reporting whatever name the operating system is configured with. That's often a
fully-qualified domain name rather than the short one, and the exact value depends on how the lab's
hosts are configured. Comparing it against the wrong string matches nothing, every task silently
skips, and the playbook reports **success with no work done** — a failure mode that looks like a
pass until you inspect the files.
If you prefer that route, get the real values first rather than assuming them:
```bash
ansible -i inventory all -m setup -a "filter=ansible_nodename"
```
then paste the exact output into the conditions:
```yaml
when: ansible_nodename == "<exact value printed for stapp01>"
```
Either variable satisfies the requirement — the task says you *can* use `ansible_nodename`, not that
you must, and validation checks the resulting files rather than which variable you branched on.
`inventory_hostname` is simply the version with nothing left to verify.
### The `copy` task
- **`src: /usr/src/data/blog.txt`** — a path on the **control node** (the jump host). `copy` reads
from the controller by default, which is exactly "copy from jump host to app server." (Copying
between two paths on the *remote* machine would need `remote_src: yes` — not the case here.)
- **`dest: /opt/data/blog.txt`** — the explicit target path, filename included. Clearer than relying
on `dest: /opt/data/` directory-expansion.
- **`owner` / `group`** — the required per-server user. Hardcoded here because each task is already
pinned to one host by its `when:`, so the literal value is unambiguous and matches the requirement
text directly.
- **`mode: '0644'`** — **quoted**. Unquoted octal like `0644` is a classic YAML misparse that
silently yields wrong permissions.
### Why `become: yes`
`/opt/data` is root-owned, so writing there requires escalation — and setting `owner`/`group` runs
`chown`, which is privileged regardless. The play escalates once at play level, with the sudo
password supplied by `ansible_become_pass` from the inventory.
> If `/opt/data` doesn't already exist on the targets, `copy` fails with "Destination directory does
> not exist" — it won't create missing parents. Add a `file`/`state: directory` task before the
> copies if you hit that.
## Verify
```bash
cd /home/thor/ansible
# Connectivity
ansible -i inventory all -m ping
# The actual validation command
ansible-playbook -i inventory playbook.yml
# Each file, ownership and permissions
ansible -i inventory stapp01 -b -m command -a "ls -l /opt/data/blog.txt"
ansible -i inventory stapp02 -b -m command -a "ls -l /opt/data/story.txt"
ansible -i inventory stapp03 -b -m command -a "ls -l /opt/data/media.txt"
```
Expected — playbook `failed=0` with `skipped=2` on each host (normal for this pattern), and `ls -l`
showing `-rw-r--r--` with `tony tony`, `steve steve`, and `banner banner` respectively.
> **If every task shows `skipped` and no files appear**, the `when:` strings don't match the actual
> hostnames — recheck them against `cat inventory`. That's the classic failure mode for conditionals:
> a run that reports success while doing nothing.