Lab 10: Ansible Capstone — Server Provisioning

Time: 45 minutes | Level: Architect | Docker: docker run -it --rm ubuntu:22.04 bash

Overview

This capstone lab integrates everything from Labs 06–09 into a production-grade server provisioning playbook. You will build a complete automation system that creates users with SSH keys, installs and configures nginx, sets up firewall rules with ufw, configures fail2ban, sets up log rotation, applies sysctl hardening, creates a systemd service, and runs validation checks — all in a single orchestrated playbook.

Prerequisites

  • Completed Labs 06–09 (Ansible Foundations through Vault)

  • Understanding of Linux server administration concepts

  • Familiarity with systemd, sysctl, and security hardening


Step 1: Project Structure and Inventory Setup

docker run --rm ubuntu:22.04 bash -c "
apt-get update -qq 2>/dev/null && apt-get install -y -qq python3-pip python3 2>/dev/null
pip3 install ansible --quiet 2>/dev/null

mkdir -p /tmp/capstone/{roles,group_vars/all,host_vars,templates,tasks}

echo '=== Creating project structure ==='
find /tmp/capstone -type d | sort

cat > /tmp/capstone/inventory.ini << 'EOF'
[servers]
localhost ansible_connection=local

[servers:vars]
ansible_python_interpreter=/usr/bin/python3
EOF

cat > /tmp/capstone/ansible.cfg << 'EOF'
[defaults]
inventory          = inventory.ini
host_key_checking  = False
retry_files_enabled = False
stdout_callback    = yaml
interpreter_python = auto_silent
gather_facts       = smart
fact_caching       = memory
roles_path         = ./roles

[privilege_escalation]
become             = False
become_method      = sudo
EOF

cat > /tmp/capstone/group_vars/all/main.yml << 'EOF'
---
# Server identity
server_environment: production
server_timezone: UTC

# Users to create
server_users:
  - name: deploy
    comment: Deployment User
    shell: /bin/bash
    groups: [sudo]
    ssh_key: ssh-rsa AAAA...examplekey deploy@ci
    sudo_nopasswd: true
  - name: appuser
    comment: Application Service User
    shell: /bin/bash
    groups: []
    ssh_key: ssh-rsa AAAA...appkey appuser@server
    sudo_nopasswd: false

# Nginx configuration
nginx_port: 80
nginx_server_name: myapp.example.com
nginx_document_root: /var/www/html
nginx_worker_processes: auto
nginx_worker_connections: 1024

# Firewall rules
firewall_allowed_tcp_ports:
  - 22
  - 80
  - 443

# Fail2ban configuration
fail2ban_maxretry: 5
fail2ban_bantime: 3600
fail2ban_findtime: 600

# Sysctl hardening
sysctl_settings:
  net.ipv4.ip_forward: 0
  net.ipv4.conf.all.accept_redirects: 0
  net.ipv4.conf.all.send_redirects: 0
  net.ipv4.conf.all.accept_source_route: 0
  net.ipv4.tcp_syncookies: 1
  net.ipv4.conf.all.log_martians: 1
  kernel.randomize_va_space: 2
  fs.suid_dumpable: 0

# Application service
app_service_name: mywebapp
app_service_user: appuser
app_service_exec: /usr/local/bin/mywebapp
app_service_port: 3000

# Log rotation
logrotate_app_logs:
  - path: /var/log/myapp/*.log
    rotate: 30
    frequency: daily
    compress: true
    delaycompress: true
    missingok: true
    notifempty: true
EOF

echo '=== ansible.cfg ==='
cat /tmp/capstone/ansible.cfg
echo ''
echo '=== group_vars/all/main.yml ==='
cat /tmp/capstone/group_vars/all/main.yml
"

📸 Verified Output:

💡 Tip: Always create a project-local ansible.cfg to set project defaults. This ensures consistent behavior regardless of who runs the playbook or on what machine. Keep it in version control — it documents your project's Ansible configuration decisions.


Step 2: Users and SSH Keys

📸 Verified Output:

💡 Tip: Always use validate: visudo -cf %s when writing sudoers files — it prevents deploying a broken sudoers file that could lock you out. The %s is replaced with a temp file path that visudo validates before the file is actually written.


Step 3: Nginx Installation and Configuration

📸 Verified Output:

💡 Tip: Always run nginx -t as part of your configuration task and use it in a handler chain. Pattern: deploy config → notify "test nginx" → if test passes, notify "reload nginx". This prevents applying broken configurations that would take down your web server.


Step 4: Sysctl Hardening and Firewall Rules

📸 Verified Output:

💡 Tip: Use dict2items to iterate over YAML dictionaries as key-value pairs. The sysctl module is idempotent — it only applies changes and triggers a reload when values differ. The ignore_errors: true is needed in containers where some sysctl settings are restricted.


Step 5: Fail2ban and Log Rotation

📸 Verified Output:

💡 Tip: Fail2ban jails work by watching log files for failed authentication patterns. The [nginx-http-auth] and [nginx-limit-req] jails protect against web brute-force attacks. Always set bantime to at least 1 hour (3600s) for production, and use action_mwl to get email notifications with ban details.


Step 6: Systemd Service Creation

📸 Verified Output:

💡 Tip: The meta: flush_handlers after deploying the service unit is critical — systemctl daemon-reload must run before any systemctl enable/start commands, or systemd won't see the new/updated service file. Security directives like NoNewPrivileges, PrivateTmp, and ProtectSystem are free hardening with no performance cost.


Step 7: Validation Play

📸 Verified Output:

💡 Tip: The assert module is your infrastructure testing tool. It runs assertions mid-playbook and fails immediately if a condition isn't met. Use it in a separate validation play (or separate playbook) that runs after provisioning. This is the foundation of Infrastructure as Code testing.


Step 8: Capstone — Complete Orchestrated Provisioning Playbook

Scenario: Assemble all components into a single site.yml that can provision a fresh Ubuntu 22.04 server end-to-end with proper error handling, idempotency, and post-provisioning validation.

📸 Verified Output:

💡 Tip: A multi-play site.yml is the gold standard for server provisioning. Each play has a clear responsibility. Run it twice — the second run should show 0 changes (idempotency). Add --tags users or --tags nginx to run specific plays during iterative development. Combine with Ansible Vault (Lab 09) for complete production-grade automation.


Summary

Component
Playbook Pattern
Key Modules

Pre-flight checks

Play 1 with assert module

assert, stat, shell

User management

user + authorized_key + copy (sudoers)

user, authorized_key

Nginx install

apt + template + file + service

apt, template, file, service

Nginx config

.j2 template + nginx -t validation

template, command

Vhost enable

Symlink with file: state: link

file

Sysctl hardening

sysctl module + dict2items loop

sysctl

Firewall

ufw module with loop over ports

ufw

Fail2ban

apt + template (jail.local.j2)

apt, template, service

Log rotation

copy or template to /etc/logrotate.d/

copy, template

Systemd service

template (.service.j2) + meta: flush_handlers

template, systemd

Handlers

notify: + handler play

meta: flush_handlers

Validation

assert + stat + command

assert, stat

Vault secrets

!vault inline + --vault-password-file

ansible-vault

Idempotency

All modules are idempotent by design

changed_when, register

CI/CD integration

ansible-playbook --check --diff + vault env vars

--check, --diff, -e

Labs 06–10 Skill Matrix

Lab
Topic
Key Skill

Lab 06

Ansible Foundations

Inventory, ad-hoc, first playbook

Lab 07

Roles & Galaxy

Role structure, dependencies, Galaxy

Lab 08

Variables & Templates

Precedence, Jinja2, handlers

Lab 09

Vault & Secrets

Encryption, vault IDs, CI/CD

Lab 10

Capstone

Multi-play provisioning + validation

Last updated