feat(deploy): add Ansible playbook + standalone deploy-spur skill for bare-metal deployment#250
feat(deploy): add Ansible playbook + standalone deploy-spur skill for bare-metal deployment#250yansun1996 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a self-contained bare-metal deployment workflow for Spur using Ansible, with inventory-driven topology (single-node, multi-node, HA Raft) and an accompanying operator “deploy-spur” skill to capture validated procedures and known gotchas.
Changes:
- Introduces
deploy/ansible/deploy.ymlplus roles to install Spur, configure controllers/agents, and verify the cluster by submitting test jobs. - Adds optional WireGuard mesh setup and a teardown playbook for stopping/wiping deployments.
- Documents supported inventory shapes/variables and adds an operator skill under
.claude/skills/.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| deploy/ansible/deploy.yml | Main multi-play deployment entrypoint wiring install → (optional) wireguard → controller → agents → verify. |
| deploy/ansible/teardown.yml | Stops daemons and optionally wipes state on all hosts. |
| deploy/ansible/ansible.cfg | Local Ansible configuration for inventory defaults and SSH behavior. |
| deploy/ansible/group_vars/all.yml | Centralized default variables (ports, install dir, HA enablement, etc.). |
| deploy/ansible/README.md | Deployment modes, inventory examples, variables, teardown instructions, gotchas, and validation notes. |
| deploy/ansible/inventory/hosts.example.ini | Example inventory templates for common topologies/transports. |
| deploy/ansible/inventory/hosts.ha.example.ini | HA-focused inventory example and caveats for client failover. |
| deploy/ansible/roles/spur_install/tasks/main.yml | Preflight checks, directory creation, installer execution, and PATH adjustment. |
| deploy/ansible/roles/spur_install/defaults/main.yml | Role defaults placeholder. |
| deploy/ansible/roles/spur_wireguard/tasks/main.yml | WireGuard mesh init/join/peer registration/verification tasks. |
| deploy/ansible/roles/spur_wireguard/defaults/main.yml | WireGuard defaults documentation note. |
| deploy/ansible/roles/spur_controller/tasks/main.yml | Controller config generation, Raft state handling, daemon lifecycle, leader wait. |
| deploy/ansible/roles/spur_controller/templates/spur.conf.j2 | Generates spur.conf including HA peers and node inventory. |
| deploy/ansible/roles/spur_controller/defaults/main.yml | Role defaults placeholder. |
| deploy/ansible/roles/spur_agent/tasks/main.yml | Agent addressing/controller targeting and daemon lifecycle. |
| deploy/ansible/roles/spur_agent/defaults/main.yml | Role defaults placeholder. |
| deploy/ansible/roles/spur_verify/tasks/main.yml | Cluster verification via registration wait + single/multi-node test job submission/output collection. |
| deploy/ansible/roles/spur_verify/defaults/main.yml | Role defaults placeholder. |
| .claude/skills/deploy-spur/SKILL.md | Operator skill documenting deployment decision flow, commands, and gotchas. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,478 @@ | |||
| --- | |||
There was a problem hiding this comment.
Thanks for this work Yan.
I think the project is still in very early stages and things are changing very rapidly. This skill can keep drifting every now and then and start confusing AI. It might be better if we do this at some later stages.
Currently, I use AI to deploy spur, most of the times it is able to understand things on the fly. I have some user instructions, but those are uncommitted and local only.
We can follow the same model and commit when things stop changing shape very often.
| @@ -0,0 +1,11 @@ | |||
| [defaults] | |||
There was a problem hiding this comment.
Same for ansible. We know it is important to have ansible scripts. Once things stabalize, we wont require changing ansible scripts much and the the project and ansible playbooks/roles versioning can (or perhaps should) be decoupled to avoid unnecessary maintenance problems in future.
5f27226 to
1d07cd2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #250 +/- ##
==========================================
- Coverage 68.24% 68.22% -0.02%
==========================================
Files 134 134
Lines 36098 36098
==========================================
- Hits 24634 24625 -9
- Misses 11464 11473 +9 🚀 New features to boost your workflow:
|
f0e5a4c to
85f07c1
Compare
… clusters Add a self-contained Ansible playbook (deploy/ansible/) and an equivalent SSH-only operator skill (.claude/skills/deploy-spur-cluster/) that stand up a Spur cluster in every supported topology: single-node, multi-node, HA (multi-controller Raft), and HA with separate compute nodes. Highlights: - Daemons run as systemd services (spurctld/spurd/spurdbd) — survive reboot, Restart=on-failure. - Optional PostgreSQL + spurdbd accounting (spur_accounting_enabled, default on). - Pre-built binary install source (spur_binary_src) with upstream install.sh fallback; Slurm-compatible CLI symlinks on every install path. - WireGuard transport via the real `spur net` CLI (single-controller mesh); direct transport for HA. - Non-destructive re-runs by default (spur_wipe_state=false) with an HA topology-change guard that fails loudly instead of silently breaking Raft. - Idempotent binary upgrades (checksum-based copy + restart). - Verify role runs single- and multi-node smoke jobs and accounting checks. Real inventories are git-ignored; only *.example.ini templates are tracked. requirements.yml pins the ansible.utils collection for the WireGuard path. Validated on a 4-node Ubuntu 22.04 lab across all topologies: basic commands, sample jobs to COMPLETED, accounting in PostgreSQL, and systemd lifecycle. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
85f07c1 to
361de74
Compare
Closes #251
Summary
Two independent paths to deploy Spur on bare-metal, sharing the same gotcha knowledge:
deploy/ansible/— self-contained Ansible playbook. Runansible-playbook deploy.yml -i <inventory>. One inventory shape covers four modes:[spur_controllers]and[spur_agents][spur_controllers], N in[spur_agents][spur_controllers]— auto-enables Raft, assignsnode_idfrom inventory position, writespeersto every controller'sspur.confspur_transport=wireguardand per-hostspur_wg_addressRoles:
spur_install,spur_wireguard,spur_accounting,spur_controller,spur_agent,spur_verify..claude/skills/deploy-spur/SKILL.md— standalone operator skill. Drives the same deploy with only SSH + bash, no Ansible. Works on any workstation with SSH access to the targets. Covers single-node, multi-node, and HA from one flow. Includes install (curlinstall.sh),spur.confrendering, daemon start with correct backgrounding, leader-election wait for HA, agent-registration wait, smoke tests, and teardown.The two paths are decoupled — using the skill does not require the playbook tree to be present, and vice versa.
What's new in this revision
spurctld,spurd, andspurdbdnow run as systemd units (templated.servicefiles,enabled: true,Restart=on-failure) instead ofnohup. Survives reboots;systemctl statusworks as expected.spur_accountingrole). Installs PostgreSQL + wiresspurdbdon the first controller and adds an[accounting]block tospur.conf. Gated byspur_accounting_enabled(default on); setfalseto skip entirely — job submission still works without it.spur_binary_srccopies locally-builtspur/spurctld/spurd/spurdbdfrom the control node (needed while the upstream repo has no published GitHub release —install.shreturns 403). Falls back to the upstream installer when unset.sbatch/squeue/sinfo/scancel/sacct/scontrol/salloc/srun→spur, created regardless of install source.Test plan
Re-validated end-to-end on a 4-node Ubuntu 22.04 cluster (fresh install: PostgreSQL purged on all nodes beforehand). Ansible run from an operator workstation (not a control node on the cluster). Each scenario verified: basic commands (
spur nodes/queue), a sample job driven toCOMPLETED, all daemonsactiveunder systemd, and accounting rows in PostgreSQL viasacct.Ansible playbook — 4 scenarios, all PASS:
-N 2job fanned across both nodes, accounted-N 3job across all three, accountedspurd, job ran on the separate compute node, accounted-e spur_accounting_enabled=false) — deploy skips PostgreSQL/spurdbd; job submission still COMPLETED (sacct unavailable, as expected)spurctld/spurd/spurdbd/postgresqlallactive; symlink task idempotent (ok, notchanged) on re-run/24, so a WG mesh would ride over already-reachable LAN and wouldn't exercise the cross-subnet case WG exists for.directtransport is validated across all 4 scenarios above;wireguardis code-complete and single-controller-sound, and needs validation on a representative multi-subnet topology. Full N-controller peer mesh is not yet automated (agents peer with the first controller).Standalone skill (validated with three fresh subagents, one per mode, each reading SKILL.md directly to bypass any caching):
-N 2COMPLETED-N 2+ follower-forwarding COMPLETEDSkill bugs surfaced during fresh-agent validation and patched in this PR:
partition_nodes_csvfirst-attempt before the working fallback (Step 5)<<EOFvs<<'EOF'in multi-node smoke test — caused\$SPUR_TASK_OFFSETetc. to expand at submit-time instead of at job-run time on the agent (Step 9)Notes worth knowing
N=2HA has zero fault tolerance (quorum = 2). Use odd N ≥ 3 in production. Both paths warn.spurd --controllertakes a single URL in Spur 0.3.0 — agents don't fail over even if Raft does. Production HA needs an L4 VIP / DNS round-robin in front of:6817.peerslist order must stay stable across deploys (openraftnode_idis positional).spurdbd+ PostgreSQL on the first controller for the whole cluster. Default DB creds arespur/spur— overridespur_accounting_db_passwordfor anything beyond a lab.spur_wipe_state=true(default), the Raft job-id counter resets each deploy, so re-deploys reuse job ids 1, 2, … in the accounting DB (upsert on primary key). Setfalseto preserve state.Gotchas baked into both paths
Real bugs hit during validation:
-Dmeans--foreground, not "daemonize". Never pass-Dwhen backgrounding.pkill -f spurdalso killsspurctld(substring match). Alwayspkill -x.< /dev/null+disownor the daemon dies on connection close (skill path; the playbook uses systemd instead).spur show jobclaims. Thespurdsystemd unit setsWorkingDirectory=$SPUR_HOMEso output is predictable.:6817immediately but returnsStatus::unavailable("no leader elected yet")until quorum forms.spur show node <name>is a prefix match, not exact — collides on hostnames sharing a prefix.info-level logs print a multi-lineRaftState{...}per start that can clobbergrep "become leader".Files
deploy/ansible/— playbook, roles (incl.spur_accounting+ systemd unit templates), inventory examples, README.claude/skills/deploy-spur/SKILL.md— standalone operator skill (no Ansible dependency)