core: GPU containers, bind sub-paths, and stop containers on unit stop
- container launcher: `gpus:` passes through as docker `--gpus`. - requires: `path:` is appended to the bound URL, for consumers that want a sub-path as their base (e.g. an OpenAI-compatible server's /v1). - container services now get `ExecStop=-docker stop wildpc-<name>`. SIGTERM only reached the `docker run` client, so a stop/restart left the container running unsupervised and holding its name, and the next start failed with a name conflict until systemd gave up.
This commit is contained in:
@@ -697,7 +697,7 @@ def _requires_env(config: WildpcConfig, dep: DeploymentSpec) -> dict[str, str]:
|
||||
if r.kind == "deployment" and r.bind:
|
||||
url = _target_url(config, r.ref)
|
||||
if url:
|
||||
out[r.bind] = url
|
||||
out[r.bind] = url.rstrip("/") + r.path if r.path else url
|
||||
return out
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ def _registry_requires(dep: DeploymentSpec) -> list[dict]:
|
||||
"""A deployment's `requires` as plain dicts for the registry — carried into the
|
||||
mesh payload so peers can draw cross-node consumption."""
|
||||
return [
|
||||
{"kind": r.kind, "ref": r.ref, "bind": r.bind}
|
||||
{"kind": r.kind, "ref": r.ref, "bind": r.bind, "path": r.path}
|
||||
for r in (getattr(dep, "requires", None) or [])
|
||||
]
|
||||
|
||||
@@ -1092,6 +1092,8 @@ def _build_run_cmd(
|
||||
# certs/data/secrets readable with no chown). ${uid}/${gid} expand
|
||||
# to the wildpc process's own ids.
|
||||
cmd.extend(["--user", _subst(run.user, placeholders)]) # type: ignore[union-attr]
|
||||
if run.gpus: # type: ignore[union-attr]
|
||||
cmd.extend(["--gpus", run.gpus]) # type: ignore[union-attr]
|
||||
for tp in run.tmpfs: # type: ignore[union-attr]
|
||||
cmd.extend(["--tmpfs", _subst(tp, placeholders)])
|
||||
for container_port, host_port in run.ports.items(): # type: ignore[union-attr]
|
||||
@@ -1158,10 +1160,16 @@ def _build_stop_cmd(name: str, run: object, source_dir: Path | None) -> list[str
|
||||
"""The ExecStop teardown command for a runner, or [] if a plain SIGTERM suffices.
|
||||
|
||||
Compose stacks need an explicit ``down`` so networks/anonymous volumes are
|
||||
reclaimed rather than left dangling when the unit stops.
|
||||
reclaimed rather than left dangling when the unit stops. A container needs an
|
||||
explicit ``stop``: SIGTERM only reaches the ``docker run`` client, leaving the
|
||||
container running unsupervised and holding its name, so the next start fails.
|
||||
``-`` tells systemd a container that's already gone isn't a failure.
|
||||
"""
|
||||
if run.launcher == "compose": # type: ignore[union-attr]
|
||||
return [*_compose_base(name, run, source_dir), "down"]
|
||||
if run.launcher == "container": # type: ignore[union-attr]
|
||||
runtime = shutil.which("docker") or shutil.which("podman") or "docker"
|
||||
return [f"-{runtime}", "stop", f"wildpc-{name}"]
|
||||
return []
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,9 @@ class RunContainer(LaunchBase):
|
||||
# tmpfs mounts (e.g. ["/var/run/postgresql"]) for image runtime dirs that must
|
||||
# be writable when the container runs as a non-default uid.
|
||||
tmpfs: list[str] = Field(default_factory=list)
|
||||
# GPUs to hand the container (docker `--gpus`), e.g. "all" or "device=0".
|
||||
# None → no GPU access.
|
||||
gpus: str | None = None
|
||||
|
||||
|
||||
class RunNode(LaunchBase):
|
||||
@@ -308,6 +311,9 @@ class Requirement(BaseModel):
|
||||
kind: Literal["system", "deployment", "tool"] = "deployment"
|
||||
ref: str
|
||||
bind: str | None = None
|
||||
# Appended to the target's URL when binding — for a consumer that wants a
|
||||
# sub-path of it (e.g. an OpenAI-compatible server's "/v1") as its base URL.
|
||||
path: str | None = None
|
||||
|
||||
|
||||
# ---------------------
|
||||
|
||||
Reference in New Issue
Block a user