Conversation
The CPU/GPU distinction is not Docker-specific; the Kubernetes provider added later in this branch needs it too, and importing it from the Docker adapter would pull the Docker SDK in with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
The relock also moves pyyaml from 6.0.2 to 6.0.3. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
The supervisor creates and deletes worker Pods through the Kubernetes API, so a cluster schedules workers onto GPU nodes instead of the supervisor placing containers on its own host. Credentials travel in a per-worker Secret consumed via envFrom rather than inline in the pod spec, and the secret set is derived from the SecretStr fields on WorkerConfig so a newly added credential cannot silently land in the manifest. GPUs are requested as extended resources and left to the device plugin to assign. Object names are sanitized to RFC 1123, which Docker container names are not; the default node alias alone would otherwise be rejected by the API. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
The manifests are ordinary YAML assets carrying compose-compatible ${VAR}
substitution plus a few directives for what substitution alone cannot express:
conditional inclusion, integers for fields the API rejects as strings, file
contents for configuration compose bind-mounts, and the environment file's own
values for what compose passes through env_file. One .env therefore drives both
backends.
Values handed to the cluster come from the environment file rather than the
process environment, so nothing else in the operator's shell is copied into the
namespace.
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
`flowmesh stack up/down/restart/logs/ps/clean --backend k8s` renders the manifests and drives them with kubectl, while the compose path is unchanged when the backend is compose. The backend also reads from STACK_BACKEND, so an environment file selects it once. Restarting with an image tag applies rather than rolls, because a rollout restart leaves the pod template's image as it was and the tag would silently have no effect. The worker drain moves into utils so both backends run it before the supervisor goes away. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Teardown applied the whole rendered stream to `kubectl delete`, which includes the Namespace and the results PersistentVolumeClaim. Deleting a namespace cascades to every claim inside it, so `stack down` destroyed task results and Redis state, while the compose backend's `down` leaves volumes alone. `down` now keeps the namespace and the claims and `clean` removes them, matching the split compose has between `down` and `down -v`. Separately, the RBAC the server needs is bound in the worker namespace, so setting K8S_WORKER_NAMESPACE to anything other than the stack namespace made the apply target a namespace that was never created. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Pod deletion is accepted asynchronously and the object outlives the call while it terminates, so recreating a worker under the same name raced the pod being replaced and would fail with a conflict. Replacement now deletes with no grace period and waits for the name to come free. The `flowmesh.io/node-id` label also carried a worker id, which is assigned only once the worker registers — after the pod exists — so it was both misnamed and never actually set. It is dropped; the node alias remains the stable key the orphan reaper selects on. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
`clean` deletes persistent volume claims by label, but the Redis claims are created by the StatefulSet controller from volumeClaimTemplates, which carried no labels — so cleanup silently left both Redis volumes behind. Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
The factory resolved cluster access on first use, so it always constructed and the node advertised `kubernetes` even with no kubeconfig and no cluster — the create then failed late instead of being refused by the provider registry. Resolving access in the initializer lets an unreachable cluster drop the provider the same way an absent Docker daemon drops that one. The dockerless-host test now stubs every spawning provider rather than two of them, so its assertion about the surviving provider set no longer depends on whether the machine running it happens to have a kubeconfig. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
The `external` provider covers workers deployed with kubectl that enroll themselves, which this guide had listed as unsupported. Contrast the two Kubernetes paths by who owns the pod lifecycle, and record that a `kubernetes` worker is a bare Pod and so is not rescheduled when its node is lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
kaiitunnz
force-pushed
the
kaiitunnz/feat/k8s-stack-backend
branch
from
September 11, 2026 14:58
2d8da21 to
5ba8d64
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Adds Kubernetes as a deployment target, in two parts. A
kubernetesworker provider lets the supervisor create and delete worker Pods through the cluster API instead of placing containers on its own host. A--backend k8smode forflowmesh stack up/down/restart/logs/ps/cleanrenders the server, both Redis instances, Services, and RBAC from the same env schema that drives compose, then applies them with kubectl.One namespace is one FlowMesh node: a single server Deployment runs
NODE_ROLE=rootwith its supervisor, and the cluster scheduler does the machine placement that bare-metal worker nodes do today. A cluster can still join an external root node by settingNODE_ROLE=workerand pointing at that node's Redis, in which case the Redis workloads are not deployed.This sits alongside the
externalprovider from #128 rather than replacing it:externaladmits workers whose lifecycle you own, whilekubernetesgives FlowMesh the ability to create and destroy them on demand.docs/KUBERNETES.mdcontrasts the two.Changes
src/server/supervisor/adapters/kubernetes.py— the new provider: pod and secret construction, start/stop lifecycle, orphan reaping, and a node-derived hardware probe.src/server/supervisor/manager.py,adapters/base.py,adapters/docker.py— register the provider in the node's guarded provider list, and move the sharedWorkerTypeenum onto the adapter base so the provider does not import it through the Docker adapter.src/server/env.py—K8S_*settings the adapter reads for Service names, namespaces, and the GPU resource name.sdk/stack/src/flowmesh_stack/manifests.py,kubernetes.py— manifest rendering and the kubectl wrapper, mirroring the existing compose wrapper.cli/stack/src/flowmesh_cli_stack/assets/k8s/— the shipped manifests (namespace, RBAC, Redis, server) and an example worker config.cli/stack/src/flowmesh_cli_stack/k8s.py,stack.py,utils.py,env_schema.py— the backend implementation,--backenddispatch on the lifecycle commands, the shared worker-drain helper, and the newSTACK_BACKEND/K8S_*schema section.docs/KUBERNETES.mdplusARCHITECTURE.md,CLI.md,ENV.md,AGENTS.md— deployment guide and routing.Design
The server Deployment is pinned to
replicas: 1with theRecreatestrategy. A worker's token lives in the in-memory registry of the supervisor that minted it, so a second replica behind the Service would reject registrations from workers the other replica started. Restarting the pod is still safe, since scheduling state is persisted to Redis and rebuilt on startup.The provider registers in the node's guarded provider list alongside
dockerandvastai, and resolves cluster access in its factory's initializer. That placement is load-bearing: a provider that constructs successfully is advertised byGET /stack/workers/providers, so deferring cluster resolution to first use would make a node claimkuberneteswith no kubeconfig at all and turn #129's 409 into a late failure.Manifests are ordinary YAML assets carrying compose-compatible
${VAR}substitution plus a small set of directives for what substitution alone cannot express: conditional inclusion, integers for fields the API rejects as strings, file contents for configuration compose bind-mounts, and the env file's own values for what compose passes throughenv_file. One.envtherefore drives both backends, and no templating dependency is added to a published wheel. Values handed to the cluster come from the env file rather than the process environment, so nothing else in the operator's shell reaches the namespace.Worker credentials go into a per-worker Secret consumed through
envFromrather than inline in the pod spec, and the secret set is derived from theSecretStrfields onWorkerConfigso a credential added later cannot silently land in a manifest. GPUs are requested as extended resources and left to the device plugin to assign; the server never pins device indices. Worker pods carry no owner reference to the server pod, so a server rollout does not take running workers with it, while pods orphaned by an unclean exit are reaped at supervisor startup because their tokens died with the previous registry.Two limitations are deliberate and documented. A
kubernetesworker is a bare Pod: it restarts in place but is not rescheduled if its node is lost, so pools on preemptible or frequently drained nodes are better served byexternal. And SSH tasks are unavailable on Kubernetes workers — without a Docker socket the executor never loads, so the task type is never advertised and the dispatcher will not route to it.Test Plan
Test Result
1789 passed; pre-commit (gitleaks, isort, black, ruff, mypy, codespell, requirements sync) clean; bandit reports no issues onsrc/; env examples in sync. Theflowmesh-cli-stackwheel builds withassets/k8s/*.yamlincluded, so the manifests ship with the CLI.No live cluster was involved. The provider is covered against a mocked Kubernetes API and the manifests are covered by rendering assertions over every shipped asset, so the deployment path itself is unexercised end to end and wants a real cluster before this leaves draft.
Pre-submission Checklist
pre-commit run --all-filesand fixed any issues.uv run pytest tests/passes locally.uv sync --all-packages --group ci --frozen).[BREAKING]and described migration steps above. — n/a, no breaking changes.🤖 Generated with Claude Code