Ansible¶
The lab uses ansible-quasarlab for VM-level configuration. Inventory is inventory.proxmox.yml (dynamic from Proxmox API) plus inventory.static.ini for things outside Proxmox.
Day-to-day commands¶
# Inventory sanity check:
ansible-inventory --graph
ansible-inventory --list --limit <group>
# Ping every host (does it answer over SSH?):
ansible all -m ping
# Run an ad-hoc command:
ansible <group> -m shell -a 'systemctl is-active postgresql@16-main' -b
# Gather facts for one host:
ansible <host> -m setup
ansible <host> -m setup -a 'filter=ansible_distribution*'
Playbook runs, with safety¶
# Always check first when touching a real host:
ansible-playbook -i inventory.proxmox.yml playbook.yml \
--limit <host> --check --diff
# Only run a subset of tags:
ansible-playbook ... --tags "configure,reload" --skip-tags "destructive"
# Step through tasks one at a time (interactive):
ansible-playbook ... --step
--check --diff together is the gold pattern: dry-run mode plus a unified diff of every file the play would change.
Vault¶
ansible-vault create group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml
ansible-vault encrypt_string 'secret-value' --name 'my_var' # inline encryption
ansible-vault rekey group_vars/all/vault.yml # change password
The vault password lives in a pass/gopass entry locally and is referenced via ansible.cfg's vault_password_file = ./bin/vault-pass.sh.
Roles I lean on¶
common/os/debian— base hardening, NTP, unattended upgrades, non-root user.k8s/docker— Docker engine for VM-resident services (uptime-kuma, registry, etc.).monitoring/node_exporter— Prometheus node_exporter on every VM.monitoring/filebeat(nowvector) — log shipping to the in-cluster aggregator.
Things I reach for when a play has gone wrong¶
| Question | Command |
|---|---|
| Why did this task fail? | Re-run with -vvvv for SSH-level detail. |
| What state did it leave the host in? | ansible <host> -m shell -a 'cat /etc/<file>' -b |
| Is something in the play wrong, or the inventory? | --check --diff --limit <host> and read the diff. |
| Has this playbook ever succeeded? | git log --follow playbooks/<file> and check CI. |
Scheduled runs deploy main, not your checkout¶
Two timers on cmd_center1 run at different cadences (ansible-proxmox hourly,
ansible-security every 30 minutes). Both run
from a dedicated automation checkout, force-synced to its remote ref before
every run:
/var/lib/ansible-quasarlab/repo # pinned to origin/main
/var/lib/ansible-quasarlab/observability # pinned to origin/master
# What is automation actually running?
git -C /var/lib/ansible-quasarlab/repo log --oneline -1
# Confirm the units point at the automation checkout, not ~/code
systemctl cat ansible-proxmox.service | grep -E 'ExecStart|WorkingDirectory'
# One-time bootstrap after a rebuild or a unit change
ansible-playbook playbooks/cmd_center.yml --tags ansible_timers --diff
Unmerged work is not deployed. Undoing it is a separate job.
Because runs pin to origin/main, a change only sticks once merged.
But the next run only reconciles what main declares. A templated file
comes back; a purged package, a deleted directory or a service restart does
not, because those tasks do not exist in main to reverse them. After an
accidental deploy, read the changed: lines in the run log and clean up the
irreversible parts by hand.
~/code/ansible-quasarlab is the operator tree. Keep any branch checked out
there; timers do not read it. Manual ansible-playbook runs do, so a manual
run can deploy uncommitted work. Never hand-edit the automation checkout: the
next run discards it.
This exists because the timers used to run straight out of ~/code and, on
2026-08-24, deployed an unmerged branch to the
fleet.
Integration with the rest of the lab¶
- Postgres lives on a VM and is owned by Ansible. Manual edits during incidents (like the pg_hba fix) must be back-ported to the role the same day.
- Uptime Kuma's VM had a stub playbook that never completed end-to-end, which is how the VM ended up idle for 18 days. (decision pending)
- Anything K8s-related lives in ArgoCD, not Ansible. Ansible bootstraps the K8s VMs only.
- Alerting on the timers themselves:
AnsibleRunStale,AnsiblePlaybookFailed, andAnsibleRepoSyncFailed(critical: a run refused to start because it could not pin its checkout, so nothing is being enforced anywhere).