Make the K8s batch target ready to test on Nautilus/NRP - #512
Conversation
Every batchRemote/preprocessBatch dispatch created a josh-creds-<jobId> Secret holding live object-store credentials and nothing ever deleted it. ttlSecondsAfterFinished reaps the Job and its pods but not the Secret, so a shared namespace accumulated working credentials indefinitely. Add an ownerReferences entry pointing at the created Job so K8s garbage collection removes the Secret whenever the Job goes away. This needs the Job's server-assigned UID, so it happens after the Job is created — the Secret itself is still written first, otherwise a pod can be scheduled against a secretKeyRef that does not exist yet and land in CreateContainerConfigError, which the poller reports as a failure. Binding failures warn instead of throwing: by then the API server has already accepted the Job, so a namespace that withholds patch rights on Secrets should degrade to the old behaviour rather than fail a live dispatch. Both K8s targets had their own copy of the Secret code, which is why the leak existed twice. Extract the shared path into KubernetesJobSecret so both dispatch flows clean up identically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…leapis.com Both entrypoints waited for DNS on a hardcoded GCS hostname before staging. The probe exists because a pod's resolver can be briefly unusable right after start, and losing stageFromMinio costs a full JVM startup — but resolving storage.googleapis.com says nothing about reaching an in-cluster MinIO, NRP's Ceph RGW, or any other endpoint, and on a cluster with no route to it the loop burns 20s per pod for no signal. Parse the host out of $MINIO_ENDPOINT, which pods already receive from the credential Secret, and probe that instead. Skips the wait when the variable is empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README and llms-full.txt already advertised Nautilus support and used --target=nautilus in every example, but no such profile existed and the K8s path had only ever run against kind and GKE Autopilot. Add the profile so testing on an NRP namespace is a matter of filling in two values. It cannot just be a copy of the GKE profile: - NRP policy requires resource limits within 20% of requests, which the GKE profile's 2Gi request / 4Gi limit violates. Requests and limits are set equal here, and include ephemeral-storage — staging writes inputs to the pod's ephemeral disk, so a job with large .jshd inputs is evicted without a request for it. - Pods stage over the in-cluster Ceph RGW service while the client uses the public HTTPS gateway, which is what pod_minio_endpoint is for. - No "spot": that emits a cloud.google.com/gke-spot selector and toleration and would leave pods unschedulable on NRP. Cover the committed profile with a loader test so a typo or a schema change does not reach whoever runs the first NRP job, and record in BATCH_REMOTE.md what is still unverified — including that the batch image runs as root and would be rejected outright by a namespace enforcing Pod Security `restricted`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Note on deploy ordering, since the two fixes land through different paths:
For Nautilus specifically that gap is close to harmless: NRP nodes have public egress, so CI note: the kind integration test builds the image locally from the Dockerfile, so the new entrypoint was exercised here — it just is not published yet. 🤖 Generated with Claude Code |
All five checklist items pass on namespace schmidtdse against the committed profile, unchanged apart from the two documented substitutions. N4 confirms the Secret ownerReference works on a real API server for both the run and preprocess paths — deleting the Job removed the Secret — and N5 resolves the open question: the namespace carries no pod-security label, so the root-running batch image is admitted and no securityContext is needed. The one finding that changes the story for automation is auth. NRP has no static credential in the kubeconfig; it execs kubectl oidc-login, so the kubelogin binary has to be installed alongside the jar and the first login is a browser round-trip. Fabric8 shells out to the same plugin, so josh inherits it. Unattended dispatch therefore needs a pre-warmed token cache, and a namespace ServiceAccount token is the better answer for CI. Also records the endpoint/bucket asymmetry that cost time in testing: stageToMinio takes no --target and reads endpoint and bucket from the environment, while batchRemote reads them from the profile, and the minio:// export path in the .josh script has to agree with both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Validated on Nautilus. All five checklist items pass on namespace
N4 is the one worth calling out, since it is this PR's actual fix verified against a real API server rather than mocks or kind. Both dispatch paths bound their Secret: Deleting the Job removed the Secret with no manual cleanup — the cascade works. Zero N5 resolves the open question in the PR body. The namespace carries no Two findings beyond the checklist: Pods scheduled across institutions. The three N2 replicates landed on nodes at SDSC, SDSC-HaoSu and UC Santa Cruz, all writing back to the west Ceph pool through the in-cluster RGW service. The in-cluster endpoint resolves from every site, so Auth is interactive, and that is the real limit on automation. NRP has no static credential in the kubeconfig — it execs The DNS probe commit is also load-bearing for this config: pods stage from 🤖 Generated with Claude Code |
Groundwork for testing
batchRemote/preprocessBatchon Nautilus, plus one credential-hygiene bug found while reading the dispatch path. The K8s target has only ever run against kind (CI) and GKE Autopilot, thoughREADME.mdandllms-full.txtalready advertise Nautilus support.1. The credential Secret was never cleaned up
Every dispatch created a
josh-creds-<jobId>Secret holding live object-store credentials, and nothing deleted it.ttlSecondsAfterFinishedreaps the Job and its pods but not the Secret, so a shared namespace accumulated working credentials indefinitely — and because both K8s targets carried their own copy of the Secret code, the leak existed twice.Fixed by adding an
ownerReferencesentry pointing at the created Job, so K8s garbage collection removes the Secret with it. Two ordering details worth a reviewer's attention:secretKeyRefthat does not exist yet, landing the container inCreateContainerConfigError— whichKubernetesPollingStrategyreports as a job failure. There's a regression test pinning the order.Shared logic extracted into
KubernetesJobSecretso both dispatch flows clean up identically.2. Nautilus/NRP target profile
examples/test/nautilus/nautilus.json— fill inkubernetes.namespaceandminio_bucketand it's ready. It is deliberately not a copy of the GKE profile:limitswithin 20% ofrequests. The GKE profile's 2Gi request / 4Gi limit violates that. Requests and limits are equal here, and includeephemeral-storage, since staging writes inputs to the pod's ephemeral disk and a job with large.jshdinputs gets evicted without a request for it.http://rook-ceph-rgw-nautiluss3.rook) while the client uses the public gateway (https://s3-west.nrp-nautilus.io). This is exactly whatpod_minio_endpointwas built for — the in-cluster path talks to the OSDs directly for higher bandwidth."spot".applySpotConfigemits acloud.google.com/gke-spotselector and toleration; on NRP that just makes pods unschedulable.A loader test covers the committed profile so a typo or schema change doesn't reach whoever runs the first NRP job.
3. DNS probe followed a hardcoded GCS hostname
Both entrypoints waited for
storage.googleapis.comto resolve before staging. The probe is worth having — a pod's resolver can be briefly unusable right after start, and losingstageFromMiniocosts a full JVM startup — but resolving a GCS hostname says nothing about reaching an in-cluster MinIO or NRP's Ceph, and on a cluster with no route to it the loop burns 20s per pod for no signal. Now parses the host out of$MINIO_ENDPOINT, which pods already receive from the credential Secret.Separate commit, so it can be dropped independently if you'd rather not touch the image in this PR.
Still unverified
Recorded as a checklist in
BATCH_REMOTE.md. The one that could be structural rather than cosmetic: the batch image is bareeclipse-temurin:21-jreand runs as root with nosecurityContext, so a namespace enforcing Pod Securityrestrictedwould reject the pod at admission and no profile setting could rescue it. NRP's published policies don't mention PSA, so this needs an empirical check rather than a code change up front.Related gaps I did not touch, since they're beyond this scope: no
serviceAccountName(blocks workload identity — forces static keys, fine on NRP but not for a keyless EKS/GKE partner), noimagePullSecrets, no generictolerations, andbackoffLimithardcoded at 3.Testing
Full suite green. New coverage: Secret→Job ordering, owner reference contents, graceful degradation when binding is refused (both targets), and the committed profile parsing.
🤖 Generated with Claude Code