1
0
mirror of https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git synced 2026-11-03 01:13:24 +00:00
Files
deepseek-kubernetes-sandbox/deploy/k8s/sandbox-template.yaml
SREDevOps.org Agent a8ef211aa4 Fix DSH crash-loop after suspend/resume (fsGroup chmod) (#5)
Resuming a sandbox recreates its pod on the same PVC. The kubelet's default
fsGroup policy ("Always") recursively ORs 0660 into every file on each pod
start, so DSH's .credentials.yaml — created 0600 — comes back as 0660. The
credentials plugin refuses to boot in that case:

  credentials-local: .../.credentials.yaml is readable beyond its owner
  (mode 660); run "chmod 600 ..."

That makes the first resume after a suspend crash-loop, which breaks the
example's step 6 and any gateway wake-on-connect.

Set fsGroupChangePolicy: OnRootMismatch on both sandbox templates. The
kubelet then skips the recursive walk when the volume root already carries
the fsGroup ownership/mode applied on the first mount — exactly the
suspend/resume case, while a fresh PVC is still chowned correctly.

Document the upgrade path: Sandbox objects snapshot their pod template, so
existing sandboxes and warm-pool members need the policy patched (plus a
one-time chmod 600) or must be recreated.

Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
2026-10-21 13:40:47 -03:00

89 lines
3.5 KiB
YAML

# Minimal single-tenant SandboxTemplate for DeepSeek Harness Web.
#
# This is the "just one DSH Web" quick-start template. For the full
# multi-tenant platform (warm pool, per-user claims, gateway, idle-suspend),
# see examples/dsh-agents-as-a-service/.
#
# Prerequisites (create these first, or apply 00-prereqs.yaml from the
# examples directory):
# - Namespace dsh-demo.
# - Secret dsh-provider-keys in dsh-demo with key DEEPSEEK_API_KEY
# (injected via envFrom; the pod starts without it, only model calls fail).
# The agent-sandbox controller (v1.0.7, API v1beta1) must be installed.
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxTemplate
metadata:
name: dsh-web-template
namespace: dsh-demo
spec:
service: true # headless Service per Sandbox -> stable DNS (status.serviceFQDN)
podTemplate:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000 # Matches the UID of the 'node' user
runAsGroup: 1000
fsGroup: 1000
# DSH's credentials plugin refuses to start when .credentials.yaml has
# any group/other permission bit (it creates the file 0600). The
# default kubelet fsGroup policy ("Always") recursively ORs 0660 into
# every file each time the pod starts, so a recreated/resumed sandbox
# comes back with 0660 and crash-loops. OnRootMismatch skips that walk
# once the volume root already has the fsGroup ownership/mode from the
# first mount. Keep in sync with
# examples/dsh-agents-as-a-service/10-sandboxtemplate.yaml.
fsGroupChangePolicy: OnRootMismatch
containers:
- name: dsh-web
image: ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox:dsh-v0.2.1-rc.2
imagePullPolicy: IfNotPresent
ports:
- name: web
containerPort: 3080
envFrom:
- secretRef:
name: dsh-provider-keys # Secret with key DEEPSEEK_API_KEY
env:
# Empty => DSH derives links from the request Host. Keep it empty
# when fronting the sandbox with a proxy/gateway.
- name: DSH_PUBLIC_URL
value: ""
# DSH Host/Origin-fences /api. Empty trusts only localhost, which
# is right for `kubectl port-forward`. Set this to the public host
# (e.g. dsh.example.com) if you expose DSH behind an Ingress, so
# requests forwarded with the browser's Host are accepted.
- name: DSH_TRUSTED_HOSTS
value: ""
volumeMounts:
- name: dsh-home
mountPath: /home/node/.dsh # DSH_HOME
# / is behind DSH's launch-token cookie (401 without it); use an
# unauthenticated path for probes.
readinessProbe:
httpGet:
path: /favicon.svg
port: 3080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /favicon.svg
port: 3080
initialDelaySeconds: 15
periodSeconds: 15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeClaimTemplates:
- metadata:
name: dsh-home
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
# storageClassName omitted => cluster default