Add @tenki compute backend (Tenki Sandbox microVMs)#3311
Open
AlvaroDeleglise wants to merge 4 commits into
Open
Add @tenki compute backend (Tenki Sandbox microVMs)#3311AlvaroDeleglise wants to merge 4 commits into
AlvaroDeleglise wants to merge 4 commits into
Conversation
Run each flow step inside a disposable Tenki Sandbox Linux microVM, analogous to @kubernetes/@Batch. Adds metaflow/plugins/tenki/ (decorator + trampoline CLI + runner + a lazily-imported SDK wrapper with a >=0.4.0 version guard), the TENKI_* config block, backend registration (two lines in plugins/__init__.py), and unit tests. One line adds "tenki" to conda_decorator's remote-backend list so @pypi/@conda treats it like the other remote backends. Closes Netflix#3310.
Contributor
Greptile SummaryThis PR adds Tenki Sandbox microVMs as an opt-in Metaflow compute backend. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "Classify launch failures so transient on..." | Re-trigger Greptile |
An unscoped `tenki kill` filtered only on the shared "metaflow" tag, so in a shared Tenki project it could terminate sandboxes of other flows/users. Tag each sandbox with the flow at launch (metaflow-flow:<flow>) and adopt the @kubernetes/@Batch parse_cli_options semantics (flow-scoped, --my-runs, default to the latest run of the current flow). `tenki kill` with no flags now only touches the current flow's latest run — never another flow's or user's sandboxes.
The kill loop broke out after the first teardown failure, so a sandbox could be left running without trying the remaining methods, and it printed a "Failed" line eagerly even when a later method would have succeeded. Extract the loop into _terminate_sandboxes: try all methods, report a failure only when none succeeds (no false alarm), and print an accurate terminated/failed summary. Mirrors tenki.py._cleanup.
The launch path exited with METAFLOW_EXIT_DISALLOW_RETRY on any exception, so a transient Tenki API/network failure aborted the flow even under @Retry — launch_job does synchronous network I/O (Client auth, who_am_i, create), where such blips are a real mode. Add is_permanent_launch_error(): defer to the SDK's own `retryable` flag for SDK errors (UNAVAILABLE / rate-limit are retryable; auth / permission / quota / bad-image are not), treat command/session timeouts and a client-side deadline as transient (consistent with _interpret_result), and default unknown/non-SDK errors to permanent so @Retry never loops on a misconfig. Permanent errors still exit DISALLOW_RETRY; transient ones exit non-zero so the runtime retries.
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.
PR Type
Summary
Add
@tenki, a new compute backend that runs each flow step inside a disposableTenki Sandbox Linux microVM — the Tenki
analogue of
@kubernetes/@batch. Entirely additive and opt-in; the rest ofMetaflow (datastore, metadata, cards,
@retry,@resources,@catch,resume,@pypi/@conda) is unchanged.Issue
Fixes #3310.
Reproduction
Not a bug fix — this adds a backend, so "before" = the feature does not exist and
"after" = a step runs on a Tenki microVM. Minimal end-to-end exercise:
Runtime: tenki (new); datastore = S3 (validated with MinIO reached over a
temporary tunnel, so the microVM can read/write it).
Commands to run:
Where evidence shows up: parent console (per-step task logs streamed from the
datastore), artifacts via the Client API, and
python tenki_flow.py tenki list.Before (feature absent)
After (runs on real Tenki microVMs)
Each step ran in a fresh microVM on the stock Tenki image; the artifact
persisted across steps (
self.x = 21→RESULT 42).Root Cause
N/A — this is a new feature, not a bug fix. For Core-Runtime context, the only
change to a shared core file, and its causal chain:
metaflow/plugins/pypi/conda_decorator.pykeeps a hardcoded allowlist of remotebackends for which it leaves the trampoline interpreter alone (
interpreter = None). A backend not on the list has its localtenki steptrampolineswapped into the resolved
@pypi/@condaenvironment, which then cannot importthe datastore/metadata dependencies — so
@pypi+@tenkifails. Adding"tenki"(one line, alongsidebatch/kubernetes/nvidia) gives it identicaltreatment.
Everything else is additive: the new
metaflow/plugins/tenki/package, tworegistration lines in
plugins/__init__.py, and aTENKI_*block inmetaflow_config.py.Why This Fix Is Correct
@kubernetesremote-execution contract verbatim (thebash -c "…"entrypoint +METAFLOW_*env vars, via the sameshlex.splittransformation), so remote behavior matches an established backend; the
Tenki-specific surface is small (create/exec/teardown + credential forwarding).
ones; no behavior change for other backends.
result.ok(exit_code == 0 and not signal), not a partial exit-code heuristic, so a signalled task is never readas success.
tenki-sandboxSDK is imported lazily (not ininstall_requires), consistent with howkubernetes/azure-*/google-cloud-*are handled, with a
>= 0.4.0version guard.Failure Modes Considered
retry never collides with a still-terminating prior VM);
@retryis owned bythe runtime; concurrent
foreach(3 parallel microVMs) validated live.forwarded into the VM (s3 creds are not leaked into an azure/gs run);
per-datastore isolation is unit-tested.
finally+atexit,which retries on the atexit pass and warns if it still fails), the
tenki list/tenki killCLI over tagged sandboxes, and a server-sidemax_durationcap. In-process teardown does not run on a hardSIGKILL; thecap /
tenki killcover that. (Note: for project-scoped sandboxesmax_durationpauses rather than terminates — a Tenki-side terminate-on-expiry would be the ideal backstop.)
(exit 0 but signalled) is treated as a failure;
signal/reason/errnoaresurfaced; timeout arrives as either
CommandTimeoutErroror a builtinTimeoutErrorand both map to a retryable timeout.image ships
python3but nopython; a startup shim provisionspython/pip(no-op on images that already have them) and fails with a clear error if the
image has no
python3at all.shlex.splittheexact way
@kubernetesdoes, so the\"-escaped inner quotes resolvecorrectly through
sb.exec(*argv).Tests
test/unit/test_tenki.py, 41 tests, allgreen; no regressions to the existing
@kubernetes/ resource-merge tests.black-clean; no regressions.tenki-sandboxSDK is a softdependency, so CI unit tests run against a stubbed SDK (real-SDK
integration cannot run in CI without a Tenki account/API key). Live validation
is therefore manual (below).
Unit coverage: resource merge; datastore /
@parallelvalidation;sandbox-name sanitization; the
_commandbash contract + runtime-shim ordering +no-
python3guard; runner behavior with a stubbed SDK (result.oksuccess,non-zero / OOM(137) / segfault(139) / unknown / signalled results,
signal/reason/errnodiagnostics, both timeout types, session-lost,permission-denied non-retryable, stderr surfacing);
_cleanupretry +warn-on-final-pass; per-datastore credential isolation + GCP JSON materialization;
secrets/S3-SSE/OTEL forwarding; tag sanitization;
list/killclientcredentials; SDK minimum-version enforcement; and a behavioral regression for the
conda_decoratorchange (drives the realCondaStepDecorator, assertsinterpreter is Nonefor a@tenkistep).Manual / end-to-end (real Tenki microVMs; datastore = MinIO over a tunnel):
happy path (linear +
foreach+ join) and plain@tenkisteps on the stockimage;
@retry(fresh sandbox per attempt); concurrency (--max-workers 3);@timeout;--metadata=service(real Postgres-backed service — integer run-ids +task/metadatum registration);
@catch;resume;@pypi(ran a real packageinside the microVM); and orphan cleanup (
tenki list/tenki kill).Not validated live: Azure/GS (forwarding is implemented + unit-tested; needs a
real account) and a custom
TENKI_CONTAINER_IMAGE(Tenki serves images only fromits own registry, so it needs a Metaflow-ready image pushed there).
Non-Goals
@parallel/ gang scheduling, GPU, and persistent volumes/snapshots — deferred.meet the datastore contract; a shared blob store (S3/Azure/GS) is still required.
soft dependency here (matching existing backends like
kubernetes/azure-*);glad to expose an
extras_require={"tenki": ["tenki-sandbox>=0.4.0"]}extrainstead if maintainers prefer.
AI Tool Usage
No AI tools were used in this contribution
AI tools were used (describe below)
Tool: Claude Code.
Used for: implementation scaffolding, unit tests, and the live-validation
harness (MinIO/tunnel setup, e2e probes).
Reviewed/understood/tested: yes — all code was reviewed and is understood by
me, and the design decisions, failure modes, and tests reflect my own judgment.