docs: add 100 Days of DevOps challenge notes
This commit is contained in:
189
100 - days of devops/devops-92.md
Normal file
189
100 - days of devops/devops-92.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Assignment
|
||||
|
||||
One of the Nautilus DevOps team members is working on to develop a role for httpd installation and configuration. Work is almost completed, however there is a requirement to add a jinja2 template for index.html file. Additionally, the relevant task needs to be added inside the role. The inventory file ~/ansible/inventory is already present on jump host that can be used. Complete the task as per details mentioned below:
|
||||
|
||||
|
||||
a. Update ~/ansible/playbook.yml playbook to run the httpd role on App Server 2.
|
||||
|
||||
|
||||
b. Create a jinja2 template index.html.j2 under /home/thor/ansible/role/httpd/templates/ directory and add a line This file was created using Ansible on <respective server> (for example This file was created using Ansible on stapp01 in case of App Server 1). Also please make sure not to hard code the server name inside the template. Instead, use inventory_hostname variable to fetch the correct value.
|
||||
|
||||
|
||||
c. Add a task inside /home/thor/ansible/role/httpd/tasks/main.yml to copy this template on App Server 2 under /var/www/html/index.html. Also make sure that /var/www/html/index.html file's permissions are 0755.
|
||||
|
||||
|
||||
d. The user/group owner of /var/www/html/index.html file must be respective sudo user of the server (for example tony in case of stapp01).
|
||||
|
||||
|
||||
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 Role — jinja2 template for `index.html`
|
||||
|
||||
Add a jinja2 template to the existing `httpd` role and wire it up so
|
||||
`ansible-playbook -i inventory playbook.yml` deploys it to App Server 2 — with **no extra
|
||||
arguments**.
|
||||
|
||||
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
|
||||
> below write/append the files.
|
||||
|
||||
## Step 0 — Inspect what already exists
|
||||
|
||||
The role is partially built. Look before you touch anything:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/inventory
|
||||
cat /home/thor/ansible/playbook.yml
|
||||
cat /home/thor/ansible/role/httpd/tasks/main.yml
|
||||
ls -l /home/thor/ansible/role/httpd/
|
||||
```
|
||||
|
||||
Note the existing tasks in `main.yml` — **they must be preserved**. Also confirm the inventory has
|
||||
`stapp02` and an `ansible_become_pass`, since the role needs root and `-K` can't be passed.
|
||||
|
||||
## Step 1 — Update the playbook (a)
|
||||
|
||||
```bash
|
||||
cat > /home/thor/ansible/playbook.yml <<'EOF'
|
||||
---
|
||||
- name: Install and configure httpd on App Server 2
|
||||
hosts: stapp02
|
||||
become: yes
|
||||
roles:
|
||||
- role/httpd
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 2 — Create the jinja2 template (b)
|
||||
|
||||
```bash
|
||||
mkdir -p /home/thor/ansible/role/httpd/templates
|
||||
|
||||
cat > /home/thor/ansible/role/httpd/templates/index.html.j2 <<'EOF'
|
||||
This file was created using Ansible on {{ inventory_hostname }}
|
||||
EOF
|
||||
```
|
||||
|
||||
**The quoted `<<'EOF'` is essential here** — it stops the shell from touching `{{ inventory_hostname }}`
|
||||
so the literal jinja2 expression reaches the file intact.
|
||||
|
||||
## Step 3 — Append the task to the role (c, d)
|
||||
|
||||
```bash
|
||||
cat >> /home/thor/ansible/role/httpd/tasks/main.yml <<'EOF'
|
||||
|
||||
- name: Deploy index.html from jinja2 template
|
||||
ansible.builtin.template:
|
||||
src: index.html.j2
|
||||
dest: /var/www/html/index.html
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0755'
|
||||
EOF
|
||||
```
|
||||
|
||||
Note **`>>`** (append), not `>` (overwrite) — this preserves the role's existing install/service
|
||||
tasks. Verify afterwards:
|
||||
|
||||
```bash
|
||||
cat /home/thor/ansible/role/httpd/tasks/main.yml
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### The playbook and the role path
|
||||
|
||||
- **`hosts: stapp02`** — requirement (a) scopes the run to App Server 2 only.
|
||||
- **`roles: - role/httpd`** — the role lives at `/home/thor/ansible/role/httpd`. Ansible's default
|
||||
search path is `./roles/`, but this directory is named `role` (singular), so the relative path
|
||||
`role/httpd` is given explicitly. Ansible resolves it relative to the **playbook's directory**,
|
||||
which is exactly where it sits. Writing just `httpd` would fail with "the role 'httpd' was not
|
||||
found."
|
||||
- **`become: yes`** — the role installs packages, manages a service, and writes under root-owned
|
||||
`/var/www/html/`. All privileged.
|
||||
|
||||
### The jinja2 template — `inventory_hostname`
|
||||
|
||||
Requirement (b) forbids hardcoding the server name. **`{{ inventory_hostname }}`** is an Ansible
|
||||
*magic variable* holding the name of the host **as written in the inventory** — so it evaluates to
|
||||
`stapp02` when the play runs against App Server 2, `stapp01` on App Server 1, and so on.
|
||||
|
||||
That's why the file is a **template** (`.j2`) rather than a static file: `template` renders jinja2
|
||||
expressions at deploy time, substituting the correct value per host. A `copy` task would transfer
|
||||
the literal text `{{ inventory_hostname }}` unrendered — the exact mistake the requirement is
|
||||
guarding against.
|
||||
|
||||
The rendered result on App Server 2:
|
||||
|
||||
```
|
||||
This file was created using Ansible on stapp02
|
||||
```
|
||||
|
||||
> Related variables worth distinguishing: `inventory_hostname` is the inventory's name for the host
|
||||
> (what's needed here), while `ansible_hostname` is the machine's actual short hostname discovered by
|
||||
> fact-gathering. They often match, but the requirement names `inventory_hostname` explicitly.
|
||||
|
||||
### The template task
|
||||
|
||||
- **`ansible.builtin.template`** — renders jinja2 then copies. The counterpart to `copy` for dynamic
|
||||
content.
|
||||
- **`src: index.html.j2`** — a **bare filename**, no path. Inside a role, the `template` module
|
||||
automatically searches the role's `templates/` directory, so `templates/index.html.j2` is found
|
||||
without qualification. That's a role convention worth knowing — it's why the directory name matters.
|
||||
- **`dest: /var/www/html/index.html`** — the deployed path.
|
||||
- **`mode: '0755'`** — quoted so YAML parses it as a string; unquoted octal is a classic silent
|
||||
misparse.
|
||||
|
||||
### Requirement (d) — owner via `{{ ansible_user }}`
|
||||
|
||||
The owner must be "the respective sudo user of the server" — `tony` on stapp01, `steve` on stapp02,
|
||||
`banner` on stapp03. Those are precisely the `ansible_user` values already defined per host in the
|
||||
inventory, so:
|
||||
|
||||
```yaml
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
```
|
||||
|
||||
resolves per host automatically — `steve` here, since the play targets stapp02. No conditionals
|
||||
needed, and it stays correct if the role is later run against other servers. (This is the same
|
||||
pattern as the earlier per-host ownership task.)
|
||||
|
||||
`group` uses the same value because Linux creates a matching primary group per user by default
|
||||
(`steve` → group `steve`).
|
||||
|
||||
### Why appending matters
|
||||
|
||||
`tasks/main.yml` already contains the role's install and service tasks — the work "almost completed"
|
||||
by the team member. Overwriting it with `>` would delete those and the role would no longer install
|
||||
httpd, so the template task would land in a directory that doesn't exist. `>>` adds the new task to
|
||||
the end, which is also the correct **order**: httpd must be installed (creating `/var/www/html/`)
|
||||
before the template is written into it.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cd /home/thor/ansible
|
||||
|
||||
# Existing role tasks still present, new task appended at the end
|
||||
cat role/httpd/tasks/main.yml
|
||||
|
||||
# Connectivity
|
||||
ansible -i inventory stapp02 -m ping
|
||||
|
||||
# The actual validation command
|
||||
ansible-playbook -i inventory playbook.yml
|
||||
|
||||
# Rendered content, ownership, permissions
|
||||
ansible -i inventory stapp02 -b -m command -a "cat /var/www/html/index.html"
|
||||
ansible -i inventory stapp02 -b -m command -a "ls -l /var/www/html/index.html"
|
||||
```
|
||||
|
||||
Expected — playbook `failed=0`; `cat` printing
|
||||
**`This file was created using Ansible on stapp02`** (rendered, *not* the literal `{{ ... }}`); and
|
||||
`ls -l` showing `-rwxr-xr-x` with owner/group `steve steve`.
|
||||
|
||||
> If the file contains a literal `{{ inventory_hostname }}`, the task used `copy` instead of
|
||||
> `template`, or the shell expanded the heredoc — recheck that Step 2 used the **quoted** `<<'EOF'`.
|
||||
> "The role 'httpd' was not found" ⇒ the `role/httpd` relative path is wrong.
|
||||
Reference in New Issue
Block a user