Engineering Retrospective: Evolution from Imperative Scripts to Idempotent Ansible Automation
Platform Standard: Historical Configuration Management Retrospective
Milestone Era: Milestone v2.0 Architecture
Status: Archival Reference (Foundation for modern GitOps)
Executive Overview
| Attribute | Specification |
|---|---|
| Domain | Configuration Management & Fleet Orchestration |
| Target Infrastructure | Heterogeneous Fleet: Proxmox Host, Operations Bastion, K3s Kubernetes Nodes |
| Tooling Adopted | Ansible Core 2.15+, Ansible Vault (AES-256), SSH Pipelining, Proxmox Community Collections |
| Lead Engineer | Vijay Singh (Platform & DevOps Engineer) |
| Key Outcome | Eliminated 100% of manual SSH drift; codified idempotent fleet patching and zero-trust jump host access |
1. The Problem: The Scalability Wall of Imperative Shell Scripts
Following initial infrastructure provisioning via Terraform, maintaining virtual machines using manual SSH logins and localized shell scripts (setup.sh, install-docker.sh) rapidly broke down:
- Configuration Drift: When a system package failed on
node-1but succeeded onnode-2, nodes silently diverged in patch level, kernel modules, and security parameters. - Lack of Idempotency: Re-running a bash script frequently appended duplicate lines to
/etc/hostsor failed on existing users, causing destructive pipeline aborts. - Plaintext Credential Exposure: Scripts required database passwords and API tokens passed as unencrypted environment flags or stored in plaintext files.
- Network Access Friction: Target virtual machines resided in private subnets behind NAT without public IP addresses, requiring complex manual SSH tunneling.
2. Architectural Architecture: Agentless Ansible Fleet
To eliminate manual drift without burdening constrained 16GB host memory with background daemon agents (like Puppet or Chef), Ansible was implemented across the infrastructure:
flowchart TD
Workstation["Administrative Workstation\n(Ansible Control Node)"]
Overlay["Tailscale Encrypted Mesh\n(100.64.0.0/10)"]
OpsCenter["ops-center Bastion Host\n(SSH Proxy Jump Gateway)"]
PrivateSubnet["Private RFC-1918 LAN (192.168.0.0/24)"]
Node1["k3s-prod (Kubernetes)"]
Node2["proxmox-pve (Bare-Metal Hypervisor)"]
Node3["ephemeral-lab (Sandboxes)"]
Workstation -->|SSH Pipelining| Overlay
Overlay --> OpsCenter
OpsCenter --> PrivateSubnet
PrivateSubnet --> Node1
PrivateSubnet --> Node2
PrivateSubnet --> Node3
3. Engineering Challenges & Technical Remediations
Challenge 1: The "Chicken and Egg" NAT Connectivity Barrier
Problem: Workstations operating over external networks (or remote cellular connections) could not reach internal virtual machines on 192.168.0.0/24. Opening SSH ports on the home router would violate Zero-Trust standards.
Solution: Implemented the SSH Jump Host Pattern leveraging OpenSSH ProxyCommand within Ansible group variables. All connections dynamically tunnel through the authenticated ops-center bastion over Tailscale:
# inventory/group_vars/internal_nodes.yml
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -W %h:%p -q -o StrictHostKeyChecking=accept-new [email protected]"
-o ControlMaster=auto
-o ControlPersist=15m
-o PreferredAuthentications=publickey
Challenge 2: In-Repository Secret Protection
Problem: Application configurations required sensitive API tokens, Proxmox root passwords, and Cloudflare credentials. Plaintext storage in GitHub was strictly prohibited.
Solution: Adopted Ansible Vault with AES-256 Encryption. All secrets were encapsulated in encrypted files (vault.yml) with zero plaintext values checked into Git:
# Encrypt individual sensitive variables
ansible-vault encrypt_string 'MySuperSecureDBPass123' --name 'db_password'
# Automated playbook execution using local ephemeral key file
ansible-playbook playbooks/site.yml --vault-password-file ~/.vault_pass
Challenge 3: Physical Memory Contention via Zone Switching
Problem: The single physical Mini PC host possessed only 16GB of physical RAM. Running the production K3s cluster simultaneously with multi-node "Kubernetes The Hard Way" lab VMs consistently exceeded memory limits, threatening host stability.
Solution: Engineered an orchestration playbook (manage_zones.yml) utilizing the community.general.proxmox module to interact directly with the hypervisor API. The playbook programmatically suspends or boots entire architectural zones on demand:
# playbooks/manage_zones.yml
- name: "Hypervisor | Toggle Operational Zones"
hosts: localhost
tasks:
- name: "Suspend Ephemeral Lab VM to Preserve Production Memory"
community.general.proxmox_kvm:
api_host: "100.108.178.93"
api_user: "root@pam"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
vmid: 201
state: stopped
when: active_mode == "production"
4. Operational Comparison Matrix
| Evaluation Dimension | Manual Shell Scripts (Pre-Ansible) | Idempotent Ansible Fleet (Milestone v2.0) | Modern GitOps Flux CD (Milestone v3.0) |
|---|---|---|---|
| Execution Model | Imperative, manual, step-dependent. | Idempotent, declarative playbooks. | Continuous reconciliation via Kubernetes Operator. |
| Configuration Drift | High (Unavoidable divergence). | Zero during playbook execution. | Zero (Continuous drift correction every 5m). |
| Secret Management | Insecure plaintext or manual export. | Encrypted via Ansible Vault (AES-256). | In-Git Mozilla SOPS with Age asymmetric encryption. |
| Failure Recovery | Manual troubleshooting per host. | Re-run playbook to restore desired state. | Automatic self-healing directly from Git commits. |
5. Architectural Retrospective
Ansible transformed the infrastructure from fragile artisanal servers into repeatable, standardized compute blocks.
This foundation provided the operational discipline necessary to transition to Milestone v3.0, where host OS configuration remains codified in Ansible, while application and platform lifecycle management was elevated to declarative Kubernetes GitOps via Flux CD v2.