docs: add 100 Days of DevOps challenge notes
This commit is contained in:
179
100 - days of devops/devops-85.md
Normal file
179
100 - days of devops/devops-85.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Assignment
|
||||
|
||||
The Nautilus DevOps team is testing various Ansible modules on servers in Stratos DC. They're currently focusing on file creation on remote hosts using Ansible. Here are the details:
|
||||
|
||||
|
||||
a. Create an inventory file ~/playbook/inventory on jump host and include all app servers.
|
||||
|
||||
|
||||
b. Create a playbook ~/playbook/playbook.yml to create a blank file /opt/app.txt on all app servers.
|
||||
|
||||
|
||||
c. Set the permissions of the /opt/app.txt file to 0755.
|
||||
|
||||
|
||||
d. Ensure the user/group owner of the /opt/app.txt file is tony on app server 1, steve on app server 2 and banner on app server 3.
|
||||
|
||||
|
||||
Note: Validation will execute the playbook using the command ansible-playbook -i inventory playbook.yml, so ensure the playbook functions correctly without any additional arguments.
|
||||
|
||||
# Solution
|
||||
|
||||
# Ansible Inventory + Playbook — create `/opt/app.txt` with per-host ownership
|
||||
|
||||
Set up the jump host so `ansible-playbook -i inventory playbook.yml` creates a blank
|
||||
`/opt/app.txt` on all three Stratos DC app servers, mode `0755`, owned by a **different user on
|
||||
each host** — with **no extra arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write the two files.
|
||||
|
||||
## Step 1 — Inventory (all three app servers)
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/inventory <<'EOF'
|
||||
[app_servers]
|
||||
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_become_pass=Ir0nM@n
|
||||
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_become_pass=Am3ric@
|
||||
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_become_pass=BigGr33n
|
||||
|
||||
[app_servers:vars]
|
||||
ansible_connection=ssh
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Playbook
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Create /opt/app.txt on all app servers
|
||||
hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Create blank file with correct mode and ownership
|
||||
ansible.builtin.file:
|
||||
path: /opt/app.txt
|
||||
state: touch
|
||||
mode: '0755'
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
EOF
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The neat trick: `{{ ansible_user }}` for per-host ownership
|
||||
|
||||
Requirement (d) wants a **different owner per server** — `tony` on stapp01, `steve` on stapp02,
|
||||
`banner` on stapp03. Notice those are exactly the SSH users already defined per host in the
|
||||
inventory. So instead of writing three separate tasks with `when:` conditionals, the playbook
|
||||
references the variable:
|
||||
|
||||
```yaml
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
```
|
||||
|
||||
Ansible evaluates `ansible_user` **per host** during the play, so it resolves to `tony` on stapp01,
|
||||
`steve` on stapp02, and `banner` on stapp03 automatically. One task, three correct outcomes — and it
|
||||
stays correct if a host is added or a username changes, since the inventory is the single source of
|
||||
truth.
|
||||
|
||||
> `group` uses the same value because these systems create a matching primary group for each user
|
||||
> (user `tony` → group `tony`), which is the Linux default. If your lab's groups differ, set `group`
|
||||
> explicitly per host instead.
|
||||
|
||||
The alternative — hardcoding with conditionals — would look like this and is strictly worse:
|
||||
|
||||
```yaml
|
||||
# NOT recommended, shown for contrast
|
||||
- name: Set owner on stapp01
|
||||
ansible.builtin.file:
|
||||
path: /opt/app.txt
|
||||
owner: tony
|
||||
when: inventory_hostname == "stapp01"
|
||||
# ...repeated for each host
|
||||
```
|
||||
|
||||
### The inventory
|
||||
|
||||
Each app server is keyed by its **wiki server name** (`stapp01`, `stapp02`, `stapp03`), which
|
||||
resolves from the jump host — so the inventory name doubles as the connection target and **no
|
||||
`ansible_host` is needed**. Confirm with `getent hosts stapp01` if you want certainty.
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `ansible_user` | SSH username — **and** the file owner, via the templating above. |
|
||||
| `ansible_ssh_pass` | SSH password (password auth, not keys). |
|
||||
| `ansible_become_pass` | Sudo password, needed because the play uses `become: yes`. |
|
||||
| `ansible_connection` | Transport plugin, set once for the group via `[app_servers:vars]`. |
|
||||
|
||||
> **Verify every credential against your lab's wiki.** The pairs shown follow the common Stratos DC
|
||||
> pattern but should be confirmed rather than assumed.
|
||||
|
||||
Since validation runs the bare command (no `-u`, `-k`, `-K`), **all** connection and escalation
|
||||
details must live in the inventory.
|
||||
|
||||
### Why `become: yes` is required
|
||||
|
||||
`/opt` is root-owned, so the SSH users can't create a file there directly — the play escalates with
|
||||
`become: yes`. Escalation is also what makes **`owner:`/`group:` work at all**: changing a file's
|
||||
ownership requires root (`chown` is privileged). Without `become`, you'd get permission errors on
|
||||
both the create and the chown.
|
||||
|
||||
Because sudo may prompt for a password and you can't pass `-K`, `ansible_become_pass` is set per
|
||||
host in the inventory. (If the lab has passwordless sudo, the variable is simply unused — harmless
|
||||
either way.)
|
||||
|
||||
### The `file` module options
|
||||
|
||||
- **`state: touch`** — creates the file if absent, leaving it blank. Exactly "create a blank file."
|
||||
- **`mode: '0755'`** — quoted so YAML reads it as a string; unquoted octal like `0755` is a classic
|
||||
misparse that silently produces the wrong permissions.
|
||||
- **`owner` / `group`** — applied by the module after creation, which is why root privileges are
|
||||
needed.
|
||||
|
||||
Note `state: touch` bumps timestamps on every run, so re-runs report **changed** rather than **ok**.
|
||||
That's fine here; add `modification_time: preserve` and `access_time: preserve` if you want strict
|
||||
idempotency.
|
||||
|
||||
### Host key checking
|
||||
|
||||
First-time SSH connections can fail on host-key verification. The safest fix — needing no extra
|
||||
command-line arguments — is an `ansible.cfg` beside the playbook:
|
||||
|
||||
```bash
|
||||
cat > ~/playbook/ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
EOF
|
||||
```
|
||||
|
||||
`sshpass` must also be installed on the jump host for password auth.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd ~/playbook
|
||||
|
||||
# All three hosts listed
|
||||
ansible-inventory -i inventory --list
|
||||
|
||||
# Connectivity + credentials across every server
|
||||
ansible -i inventory all -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Confirm mode and per-host ownership
|
||||
ansible -i inventory all -b -m command -a "ls -l /opt/app.txt"
|
||||
```
|
||||
|
||||
Expected — `ping` returning `SUCCESS` for all three; the playbook finishing with `failed=0`; and the
|
||||
final listing showing `-rwxr-xr-x` with owner/group `tony tony` on stapp01, `steve steve` on
|
||||
stapp02, and `banner banner` on stapp03.
|
||||
|
||||
> "Missing sudo password" ⇒ `ansible_become_pass` absent or wrong. A chown failure ⇒ `become: yes`
|
||||
> didn't take. Wrong owner ⇒ check that `ansible_user` in the inventory matches the required owner
|
||||
> for that host.
|
||||
Reference in New Issue
Block a user