Kube Provider
--launcher Kube submits Pods to an existing Kubernetes cluster. Nothing
is provisioned — the cluster is assumed to be there. The default
dispatch is direct: creating the Pod is the launch.
| Phase | What happens |
|---|---|
| Provision | None. |
| Launch | One Pod created through @kubernetes/client-node, restartPolicy: Never. |
| Default dispatch | direct |
Where the launch happens
Kube is the one launcher with two working paths, and they differ in who holds the kubeconfig.
| Who creates the Pod | Kubeconfig lives | |
|---|---|---|
| Control plane (default) | @kubernetes/client-node, server-side | in the control plane's Secret store |
| A nymph in the cluster | kubectl apply, from runner/kube.rs | on the box the nymph runs on |
The second path is the same shape SLURM uses: a nymph that can reach the
scheduler does the submitting itself, so the credential never leaves the
cluster. See Running a daemon in-cluster for
when to prefer it — and for the hostPath constraint that limits it to
single-node clusters today.
The spelling is enforced. k8s, K8S, and local are all rejected with a
400 — some of those appear in stale comments in the Prisma schema. The launcher
name is exactly Kube.
Kwargs
Connection (usually on the provider):
| Field | Default | Meaning |
|---|---|---|
kubeconfig | — | A filesystem path, or inline kubeconfig content, or a { "$secret": … } ref. Omit to use the server's KUBECONFIG / ~/.kube/config. |
context | — | Switches the active context after loading. |
namespace | default | The Kubernetes namespace the Pod is created in. |
Pod shape (usually on the mode):
| Field | Required | Meaning |
|---|---|---|
image | yes | Container image. Missing or empty fails the launch immediately. |
resources | no | Passed straight through as the container's resources block — so it takes the real Kubernetes shape, { requests: {...}, limits: {...} }. |
env | no | Object of name → value. Numbers and booleans are stringified; anything else is skipped. |
node_selector | no | → spec.nodeSelector. |
tolerations | no | → spec.tolerations, verbatim list. |
service_account | no | → spec.serviceAccountName. |
image_pull_secrets | no | List of names → spec.imagePullSecrets. |
The Pod runs a single container named user with
command: ["/bin/sh", "-c"].
EC2 and GCE take the launch script as a bash userdata blob. The Kube
launcher instead heredocs it into /tmp/u.py inside the container and
runs python3 /tmp/u.py, so the image needs a python3 on PATH. This
is the one launcher where the script contract differs.
Labels always include lakeshore=true, lakeshore.namespace=<ns>, and
lakeshore.provider=<name> (slugified). Launch-body tags become extra
labels; anything colliding with the three reserved keys is dropped. The
instances listing selects on exactly those three.
Pod names are always auto-generated as lakeshore-<8 hex> — a name
kwarg is not read by this launcher.
Example
.dreamrc tab: The same shape in a local .dreamrc:
lakeshore providers add my-cluster --launcher Kube \
--kwarg context=prod-east \
--kwarg namespace=lakeshore
lakeshore modes add k8s-gpu \
--field provider=my-cluster \
--field image=ghcr.io/lakeshore-py/cuda12.4-pytorch:2.4 \
--field resources='{"limits":{"nvidia.com/gpu":1,"cpu":"4"}}' \
--field node_selector='{"cloud.google.com/gke-accelerator":"nvidia-tesla-a100"}'providers:
my-cluster: !providers.Kube
context: prod-east
namespace: lakeshore
modes:
k8s-gpu:
provider: my-cluster
image: ghcr.io/lakeshore-py/cuda12.4-pytorch:2.4
resources:
limits: { nvidia.com/gpu: 1, cpu: "4" }
node_selector: { "cloud.google.com/gke-accelerator": nvidia-tesla-a100 }Credentials
Same as kubectl. With no kubeconfig kwarg the server loads its
default config (KUBECONFIG or ~/.kube/config). A kubeconfig string
that looks like a path (starts with /, ./, or ~ and has no
newlines) is loaded from disk; anything else is treated as inline
kubeconfig content and staged to a temp file. A { "$secret": … } ref
resolves to plaintext, gets written to a chmod-600 temp file for the
duration of the SDK call, and is cleaned up in a finally. See
Secrets.
Server-side routes
| Verb | Path | Result |
|---|---|---|
POST | /v1/namespaces/:ns/providers/:name/launch | 202 { instanceId, state, providerName, launchedAt } |
GET | /v1/namespaces/:ns/providers/:name/instances | Pods matching the three reserved labels |
DELETE | /v1/namespaces/:ns/providers/:name/instances/:id | 204 — deleting an already-gone Pod is idempotent |
instanceId is the Pod name. state comes from the Pod at create time:
a deletionTimestamp means stopping regardless of phase, otherwise
Pending → starting, Running → running, Succeeded/Failed → stopped, anything else unknown. At create time it is normally
starting; poll instances to watch it become running.
Running a daemon in-cluster
Set dispatch: daemon when the cluster cannot reach the control plane,
when you want every launch to flow through one in-cluster service you can
audit, or when you would rather not hand the control plane a kubeconfig
with broad permissions.
Note that nymph also has its own kube runner, which is a different
thing: a daemon already running can render a Pod manifest and
kubectl apply it per invocation. That runner mounts the daemon's
workdir into the Pod with a hostPath volume, so it only works when the
daemon and the Pod land on the same node — a single-node kind / minikube
/ k3d cluster, or a DaemonSet. Multi-node needs an RWX volume, which is
not built.
Smoke test
Streaming stdout from the Pod back to the CLI is not implemented — read
it with kubectl logs.