mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(svc-runner): declare the node-local swarm workload for the CI gates
The swarm convergence gate derived runner_runner-1 from the services metadata and polled a stack service the role never creates: the runner fleet deploys as node-local compose projects on every node because each runner needs its own node's docker socket and identity. Declare workload: node-local on the runner services entry and let the gate context map it to HAS_SWARM_SERVICE=false, so the stack-service gates skip themselves while the swarm deploy, the registry distribution chain and the CLI e2e keep running. Roles without the attribute keep the image/template heuristic; workload: stack forces the gates on. Document the runner architecture for both deploy modes in the role README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
236117f6f1
commit
76508c946a
3 files changed
+83
-6
No files matched your search
@@ -146,6 +146,71 @@ The role self-validates via the scripts in `files/test/`, discovered and run by
|
||||
|
||||
In short: `local.sh` proves the runner can build and deploy entirely on its own hardware; `external.sh` proves it can receive real jobs from GitHub.
|
||||
|
||||
## Architecture
|
||||
|
||||
Every runner instance is an ephemeral container that mounts its host's docker
|
||||
socket (DooD). For each CI job it starts a fresh Infinito DinD container with
|
||||
a unique subnet and volume, runs the job inside it, and tears it down; the
|
||||
runner then re-registers with GitHub as a fresh ephemeral runner.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
gh[GitHub Actions] -->|job| r1[runner-1]
|
||||
gh -->|job| rn[runner-N]
|
||||
subgraph host [host node]
|
||||
r1 -->|/var/run/docker.sock| dockerd[(host dockerd)]
|
||||
rn -->|/var/run/docker.sock| dockerd
|
||||
dockerd --> dind1[DinD container<br/>job 1]
|
||||
dockerd --> dindn[DinD container<br/>job N]
|
||||
end
|
||||
```
|
||||
|
||||
### Compose mode
|
||||
|
||||
One host runs the whole fleet: the role builds the `infinito-runner` image
|
||||
locally, renders one compose project and starts `RUNNER_COUNT` instances.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph host [single host]
|
||||
build[build infinito-runner image]
|
||||
fleet[compose up:<br/>runner-1 .. runner-N]
|
||||
build --> fleet
|
||||
end
|
||||
```
|
||||
|
||||
### Swarm mode
|
||||
|
||||
The fleet is deployed on **every** cluster node, but not as a `docker stack`
|
||||
service (`workload: node-local` in `meta/services.yml`): each runner needs
|
||||
the docker socket and the registered identity of exactly its node. The image
|
||||
is built once on the manager and distributed through the registry dependency
|
||||
(`svc-registry-docker`), the only part that runs as a swarm service.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph cluster [swarm cluster]
|
||||
subgraph mgr [manager]
|
||||
build[build infinito-runner image]
|
||||
reg[(registry<br/>swarm service)]
|
||||
mfleet[node-local compose:<br/>runner-1 .. runner-N]
|
||||
end
|
||||
subgraph wrk [each worker]
|
||||
wfleet[node-local compose:<br/>runner-1 .. runner-N]
|
||||
end
|
||||
end
|
||||
build -->|push| reg
|
||||
reg -->|pull| mfleet
|
||||
reg -->|pull| wfleet
|
||||
```
|
||||
|
||||
Because no stack service exists, the swarm test gates that poll
|
||||
`docker service ls` (`converge`, `seed`, `drain`, `assert`) skip themselves
|
||||
via the `workload: node-local` declaration
|
||||
(`scripts/tests/deploy/swarm/utils/_context.sh` maps `node-local` to
|
||||
`HAS_SWARM_SERVICE=false`); the deploy itself, the registry distribution
|
||||
chain and the CLI e2e suite run in full.
|
||||
|
||||
## Further Resources
|
||||
|
||||
- [GitHub Actions self-hosted runner documentation](https://docs.github.com/en/actions/hosting-your-own-runners)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# nocheck: dockerfile-custom
|
||||
runner:
|
||||
lifecycle: beta
|
||||
workload: node-local
|
||||
proxy:
|
||||
service_key: runner-1
|
||||
github_owner: infinito-nexus
|
||||
|
||||
@@ -46,17 +46,28 @@ fi
|
||||
|
||||
HAS_SWARM_SERVICE="$(PYTHONPATH="${_REPO_ROOT}" "${PYTHON}" -c "
|
||||
import os, yaml
|
||||
svc = os.path.join('${ROLE_DIR}', 'meta', 'services.yml')
|
||||
data = {}
|
||||
if os.path.exists(svc):
|
||||
data = yaml.safe_load(open(svc)) or {}
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
entity = data.get('${ENTITY}')
|
||||
workload = entity.get('workload') if isinstance(entity, dict) else None
|
||||
if isinstance(workload, str):
|
||||
workload = workload.strip().lower()
|
||||
if workload == 'node-local':
|
||||
print('false')
|
||||
raise SystemExit
|
||||
if workload == 'stack':
|
||||
print('true')
|
||||
raise SystemExit
|
||||
try:
|
||||
from utils.roles.meta_lookup import get_role_skip
|
||||
swarm_skipped = 'swarm' in get_role_skip('${APP_ID}')
|
||||
except Exception:
|
||||
swarm_skipped = False
|
||||
has_image = False
|
||||
svc = os.path.join('${ROLE_DIR}', 'meta', 'services.yml')
|
||||
if os.path.exists(svc):
|
||||
data = yaml.safe_load(open(svc)) or {}
|
||||
if isinstance(data, dict):
|
||||
has_image = any(isinstance(v, dict) and 'image' in v for v in data.values())
|
||||
has_image = any(isinstance(v, dict) and 'image' in v for v in data.values())
|
||||
has_tpl = any(os.path.exists(os.path.join('${ROLE_DIR}', 'templates', t)) for t in ('compose.yml.j2', 'service.yml.j2'))
|
||||
print('false' if (swarm_skipped or not (has_image or has_tpl)) else 'true')
|
||||
")"
|
||||
|
||||
Reference in new issue
Block a user