~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless-runner-k8s

0
0
109 lines · 3.9 KB Raw History

Plan: tenant k3s Job executor for knotless-run

Goal

Prove the execution mechanics for a knotless-run executor that runs one workflow run as one Kubernetes Job on a socialweb-computer tenant k3s cluster.

The runner daemon stays outside the cluster. The Job pod is the unit that raises the tenant's scale demand, so a Pending Job causes a NodeClaim, the market RFP rents a VM, the pod is scheduled, and the cluster returns to zero after the run.

Scope of this proof of concept

  • One Job per run, one long-lived pod, one kubectl exec per step.
  • The checkout is streamed into /work with tar over kubectl exec -i.
  • Shared steps are streamed into /steps.
  • Outputs are streamed back from /outputs and become images in the check.
  • A run writes a JSON result whose step fields mirror dev.knotless.check.
  • --dry-run prints the Job and every call.
  • Tests use a fake kubectl; no cluster is needed for cargo test.
  • A live script obtains a tenant kubeconfig through the hosted gateway with the alice credential file and oauth-signin.ts.

Out of scope for this proof:

  • Patching knotless-run itself.
  • Real workflow YAML parsing.
  • Caches, PVCs, and artifact stores.
  • Secret injection.
  • Cancellation over XRPC.
  • Queue landing, which stays on a stable runner.

Mapping to knotless's executor seam

crates/knotless-run/src/executor.rs has:

  • Executor::{Shell, Docker} and Session::{Shell, Docker}.
  • Executor::start(setup, log, timeout) starts the run's container.
  • Session::command(script, own) returns the step command.
  • Session::stop() removes the container.
  • run(cmd, log, timeout, cancelled) enforces timeout and cancellation.

This proof is the same shape:

knotlessThis proof
Setup.dir--workdir, streamed to /work
Setup.steps--steps-dir, streamed to /steps
Setup.outputs--outputs, streamed from /outputs
Setup.image--image
Setup.varsnot yet; pod env or ephemeral Secret later
Executor::startkubectl apply, kubectl wait
Session::commandkubectl exec <pod> -- sh -ec <script>
executor::runspawn, poll, on timeout kill and delete the Job
Session::stopkubectl delete job
CheckStepStepResult
CheckRecord.log--log
check outputs--outputs

The real change is a closed-enum variant and a new Session::Kubernetes, or a small trait with start, command, stop, cleanup.

What the proof must show

  1. A Job's pod becomes Ready and accepts exec through the tenant client-certificate front.
  2. A checkout and shared steps stream into the pod.
  3. Steps share one environment, so a tool installed by one step is there for the next.
  4. Logs stream into one run log.
  5. Outputs stream back.
  6. The Job is deleted, so the node scales to zero.
  7. A result JSON has the step statuses and exit codes a check needs.

Live proof

scripts/tenant-kubeconfig.sh
export KUBECONFIG=$HOME/.tmp/knotless-runner-k8s/tenant/knotless-poc.kubeconfig.yaml
cargo run --quiet -- run \
  --name knotless-poc \
  --runner did:plc:runner \
  --image busybox:1.36 \
  --workdir examples \
  --steps-file examples/steps.tsv \
  --outputs "$HOME/.tmp/knotless-runner-k8s/outputs" \
  --result "$HOME/.tmp/knotless-runner-k8s/result.json" \
  --log "$HOME/.tmp/knotless-runner-k8s/run.log"

Then check the tenant returned to zero nodes:

kubectl get nodes

Next changes after this proof

  1. Add Executor::Kubernetes to knotless-run, behind the same CLI validation that rejects the shell executor for pulls and queue.
  2. Inject per-step variables through an ephemeral Secret and pod env.
  3. Add a cold-cache strategy; no PV or object store exists in the design.
  4. Decide one job per node for untrusted runs and set required hostname anti-affinity.
  5. Add startup cleanup by dev.knotless.runner and dev.knotless.run labels, mirroring the docker executor's remove_left.