docs: add 100 Days of DevOps challenge notes
This commit is contained in:
182
100 - days of devops/devops-93.md
Normal file
182
100 - days of devops/devops-93.md
Normal 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.
|
||||
Reference in New Issue
Block a user