|
| 1 | +# Local Dockerized Slurm for Clustron |
| 2 | + |
| 3 | +A self-contained Slurm cluster in Docker so the backend's `internal/slurm` |
| 4 | +functions (account/user/association management and job submission) can be |
| 5 | +exercised against a **real `slurmrestd`** during local development. |
| 6 | + |
| 7 | +> **"Slurm can't run in a container" — only half true.** Production *compute |
| 8 | +> nodes* are awkward to containerize (they want cgroups, GPUs, MPI, privileged |
| 9 | +> host access). But a single-host **dev/test** cluster — `slurmctld` + |
| 10 | +> `slurmdbd` + `slurmrestd` + a dynamic compute node — runs in Docker just fine. |
| 11 | +> This stack is built on the well-maintained |
| 12 | +> [giovtorres/slurm-docker-cluster](https://github.com/giovtorres/slurm-docker-cluster) |
| 13 | +> image and needs **no source compile** (it pulls a prebuilt image). |
| 14 | +
|
| 15 | +## What you get |
| 16 | + |
| 17 | +| Container | Role | Port | |
| 18 | +| --- | --- | --- | |
| 19 | +| `mysql` (MariaDB) | slurmdbd accounting storage | internal | |
| 20 | +| `slurmdbd` | accounting daemon; generates the shared `jwt_hs256.key` | internal `6819` | |
| 21 | +| `slurmctld` | controller; mints JWTs via `scontrol token` | internal `6817` | |
| 22 | +| `slurmrestd` | **REST API with JWT auth** (`X-SLURM-USER-TOKEN`) | **host `6820`** | |
| 23 | +| `cpu-worker` | one dynamic compute node so a partition exists | internal `6818` | |
| 24 | + |
| 25 | +The backend talks to `slurmrestd` exactly as it does in dev/stage: |
| 26 | +`POST /slurmdb/<ver>/accounts_association`, `GET /slurm/<ver>/jobs`, etc., authenticating |
| 27 | +with a JWT in the `X-SLURM-USER-TOKEN` header. |
| 28 | + |
| 29 | +## Prerequisites |
| 30 | + |
| 31 | +- Docker + Docker Compose (v2). On Windows, run these from your **WSL** shell. |
| 32 | +- Outbound network on first run (to pull `giovtorres/slurm-docker-cluster` and |
| 33 | + `mariadb`). |
| 34 | + |
| 35 | +## Quick start |
| 36 | + |
| 37 | +From `clustron-backend/`: |
| 38 | + |
| 39 | +```bash |
| 40 | +make slurm-up # == ./.deploy/local/slurm/deploy.sh |
| 41 | +make slurm-token # prints a root JWT (== ./.deploy/local/slurm/mint-token.sh root) |
| 42 | +``` |
| 43 | + |
| 44 | +`make slurm-up` waits for every daemon to become healthy and then prints which |
| 45 | +REST API versions the running image serves, e.g.: |
| 46 | + |
| 47 | +``` |
| 48 | +/slurm/v0.0.43 |
| 49 | +/slurm/v0.0.44 |
| 50 | +/slurmdb/v0.0.43 |
| 51 | +/slurmdb/v0.0.44 |
| 52 | +``` |
| 53 | + |
| 54 | +### Point the backend at it |
| 55 | + |
| 56 | +Copy the `slurm:` block from [`config.slurm-local.yaml`](./config.slurm-local.yaml) |
| 57 | +into your `clustron-backend/config.yaml`, then paste the `make slurm-token` |
| 58 | +output into `slurm_root_token`: |
| 59 | + |
| 60 | +```yaml |
| 61 | +slurm: # MUST be nested under `slurm:` |
| 62 | + slurm_restful_base_url: "http://localhost:6820" |
| 63 | + slurm_restful_version: "v0.0.44" # pick one from the deploy.sh output |
| 64 | + slurm_root_token: "eyJhbGciOi..." # from `make slurm-token` |
| 65 | +``` |
| 66 | +
|
| 67 | +By default `make slurm-token` mints a 1-hour (3600s) root token. For an |
| 68 | +effectively unexpired token, pass a long lifespan via `SLURM_LIFESPAN`: |
| 69 | + |
| 70 | +```bash |
| 71 | +make slurm-token SLURM_LIFESPAN=infinite # exp ~year 2094 — paste once, forget |
| 72 | +``` |
| 73 | + |
| 74 | +`SLURM_USER` and `SLURM_LIFESPAN` are forwarded to `mint-token.sh` (defaults |
| 75 | +`root` / `3600`), so `./.deploy/local/slurm/mint-token.sh root infinite` is the |
| 76 | +equivalent direct call. |
| 77 | + |
| 78 | +Now `make run` the backend. Creating a group will create a matching Slurm |
| 79 | +account (see below). |
| 80 | + |
| 81 | +## The group → account flow |
| 82 | + |
| 83 | +`group.Service.Create` runs a saga: **DB group → LDAP base group → LDAP admin |
| 84 | +group → Slurm account**. The Slurm step calls `slurm.CreateAccountAssociation` |
| 85 | +(the `sacctmgr add account` equivalent — `POST /slurmdb/<ver>/accounts_association`) |
| 86 | +with the group's LDAP CN as the account name, so the account is created **with** |
| 87 | +its cluster association and is usable for jobs. (The bare `/accounts` endpoint |
| 88 | +creates an account with no association.) If it fails, the saga compensates the |
| 89 | +earlier steps (LDAP groups deleted) and the DB transaction rolls back. |
| 90 | + |
| 91 | +Verify the REST path the backend uses, without the backend, with: |
| 92 | + |
| 93 | +```bash |
| 94 | +./smoke-test.sh # create + delete "clustron-smoke", asserting both |
| 95 | +# override the API version if needed: |
| 96 | +SLURM_RESTFUL_VERSION=v0.0.44 ./smoke-test.sh myaccount |
| 97 | +``` |
| 98 | + |
| 99 | +Inspect accounts and their associations directly inside the cluster: |
| 100 | + |
| 101 | +```bash |
| 102 | +docker compose exec slurmctld sacctmgr -i show account |
| 103 | +docker compose exec slurmctld sacctmgr -i show assoc account=clustron-smoke |
| 104 | +``` |
| 105 | + |
| 106 | +> **Heads-up — group creation now depends on Slurm.** Because the account step |
| 107 | +> is part of the saga, group creation will **fail** if `slurmrestd` is |
| 108 | +> unreachable or `slurm_root_token` is missing/expired. Keep this stack up (and |
| 109 | +> the token fresh) while working on groups, or expect group creation to error. |
| 110 | + |
| 111 | +## Per-user job tokens (out of scope here) |
| 112 | + |
| 113 | +The backend's *job* endpoints need a per-user JWT from an external |
| 114 | +[slurm-token-helper](https://github.com/NYCU-SDC/slurm-token-helper) |
| 115 | +(`GET /api/token/{username}`). That integration is deferred to a separate task — |
| 116 | +this stack is scoped to the group → Slurm-account flow, which uses |
| 117 | +`slurm_root_token` and needs no helper. For ad-hoc job testing you can mint a |
| 118 | +per-user token straight from `scontrol`: |
| 119 | + |
| 120 | +```bash |
| 121 | +./mint-token.sh alice 3600 |
| 122 | +curl -H "X-SLURM-USER-TOKEN: $(./mint-token.sh alice)" http://localhost:6820/slurm/v0.0.44/jobs |
| 123 | +``` |
| 124 | + |
| 125 | +## Slurm version / REST API version |
| 126 | + |
| 127 | +`SLURM_VERSION` (default `25.11.4`) selects the image tag and therefore which |
| 128 | +OpenAPI plugins `slurmrestd` exposes. The backend's `slurm_restful_version` |
| 129 | +**must** match one of them. The project uses **`v0.0.44`**, which Slurm 25.11 |
| 130 | +serves. SchedMD removes old plugin versions over time, so if you need a specific |
| 131 | +version, confirm it in the `make slurm-up` output and pin `SLURM_VERSION` |
| 132 | +accordingly: |
| 133 | + |
| 134 | +```bash |
| 135 | +SLURM_VERSION=25.05.6 make slurm-up # if you specifically need an older API |
| 136 | +``` |
| 137 | + |
| 138 | +## Teardown |
| 139 | + |
| 140 | +```bash |
| 141 | +make slurm-down # == ./cleanup.sh ; stops everything and wipes volumes |
| 142 | +``` |
| 143 | + |
| 144 | +This removes the MariaDB and Slurm state volumes, resetting all accounts. |
| 145 | + |
| 146 | +## Files |
| 147 | + |
| 148 | +| File | Purpose | |
| 149 | +| --- | --- | |
| 150 | +| `compose.yaml` | the cluster definition (prebuilt image, no build) | |
| 151 | +| `deploy.sh` / `cleanup.sh` | bring up (with version probe) / tear down | |
| 152 | +| `mint-token.sh` | mint a root or per-user JWT via `scontrol token` | |
| 153 | +| `smoke-test.sh` | create, read, and delete an account via the backend's REST paths (asserts deletion) | |
| 154 | +| `config.slurm-local.yaml` | the `slurm_*` block to paste into `config.yaml` | |
| 155 | + |
| 156 | +## Troubleshooting |
| 157 | + |
| 158 | +**`sinfo` shows the node `down` / jobs stay `PENDING` ("Required node not |
| 159 | +available") after a host or Docker restart.** The compute node is dynamic |
| 160 | +(`slurmd -Z`); when its container restarts, slurmctld marks it |
| 161 | +`down` ("Node unexpectedly rebooted") and the image's `ReturnToService=1` only |
| 162 | +auto-returns *non-responsive* nodes. Just re-run: |
| 163 | + |
| 164 | +```bash |
| 165 | +make slurm-up # resumes any down nodes and sets ReturnToService=2 |
| 166 | +``` |
| 167 | + |
| 168 | +or fix it by hand: |
| 169 | + |
| 170 | +```bash |
| 171 | +docker exec clustron-slurm-slurmctld scontrol update nodename=c1 state=resume |
| 172 | +``` |
| 173 | + |
| 174 | +**`srun` fails / hangs when run as root.** Slurm refuses to run jobs as root or |
| 175 | +SlurmUser. Submit as a normal user inside the cluster: |
| 176 | + |
| 177 | +```bash |
| 178 | +docker exec clustron-slurm-slurmctld su slurm -s /bin/bash -c "srun -N1 hostname" |
| 179 | +``` |
| 180 | + |
| 181 | +## Notes / caveats |
| 182 | + |
| 183 | +- Compute is minimal: one dynamic node, jobs may sit `PENDING`. Accounting and |
| 184 | + job *submission/listing* work; this stack is not for running real workloads. |
| 185 | +- `slurmctld`, `slurmrestd` and the worker run `privileged: true` (Slurm needs |
| 186 | + it). Fine for local dev; do not copy this layout to production. |
| 187 | +- The cluster is independent of the core `.deploy/local` stack (separate Compose |
| 188 | + project `clustron-slurm-local`), so `make prepare`/`make run` are unaffected. |
0 commit comments