Use Templates, Claims, and Warm Pools

The optional extension controllers provide reusable templates, prepared workloads, and a claim workflow. They are disabled by default.

This guide uses one consistent resource chain:

python-template -> python-pool -> python-claim -> claimed Sandbox

Enable Extension Controllers

Connect kubectl to the intended workload cluster and inspect the controller arguments:

kubectl config current-context

kubectl get deployment agent-sandbox-controller \
  -n agent-sandbox-system \
  -o jsonpath='{.spec.template.spec.containers[0].args}{"\n"}'

Continue only when the output contains --extensions=true. Extension CRDs can exist while extension reconciliation is disabled.

If the flag is absent, open the installed Cluster Plugin in ACP, select Update, enable Extension controllers, wait for the Deployment rollout, and check the arguments again. Do not edit ModuleInfo, the Helm release, or the controller Deployment directly.

Set and verify the business namespace used by the remaining commands:

export BUSINESS_NAMESPACE=agent-demo
kubectl get namespace "$BUSINESS_NAMESPACE"

Create a SandboxTemplate

Create a restricted-compatible Python template. The policies disallow claim-time environment-variable and volume-template injection, which keeps the initial claim path narrow.

cat <<'EOF' | kubectl apply -n "$BUSINESS_NAMESPACE" -f -
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxTemplate
metadata:
  name: python-template
spec:
  networkPolicyManagement: Unmanaged
  envVarsInjectionPolicy: Disallowed
  volumeClaimTemplatesPolicy: Disallowed
  podTemplate:
    metadata:
      labels:
        app.kubernetes.io/name: agent-sandbox-workload
    spec:
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: python
          image: docker-mirrors.alauda.cn/library/python:3.12-alpine
          command: ["sh", "-c", "sleep 1800"]
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
EOF

The template uses networkPolicyManagement: Unmanaged, so the business namespace is responsible for NetworkPolicy.

Before creating prepared workloads, apply and verify a minimal egress policy that allows DNS. Extend the same policy with explicitly approved business destinations when the workload needs them.

cat <<'EOF' | kubectl apply -n "$BUSINESS_NAMESPACE" -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: agent-sandbox-egress
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: agent-sandbox-workload
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

kubectl get networkpolicy agent-sandbox-egress \
  -n "$BUSINESS_NAMESPACE"

Stop if the policy is not created successfully. Do not create a warm pool first and add its required network access later.

Create a SandboxWarmPool

Create one prepared workload from python-template:

cat <<'EOF' | kubectl apply -n "$BUSINESS_NAMESPACE" -f -
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxWarmPool
metadata:
  name: python-pool
spec:
  replicas: 1
  sandboxTemplateRef:
    name: python-template
  updateStrategy:
    type: OnReplenish
EOF

kubectl wait --for=jsonpath='{.status.readyReplicas}'=1 \
  sandboxwarmpool/python-pool \
  -n "$BUSINESS_NAMESPACE" \
  --timeout=300s

A warm pool reduces preparation latency. It does not add a stronger runtime boundary. Runtime isolation still comes from the Sandbox Pod security settings and selected RuntimeClass.

Claim a Sandbox

Create an absolute expiration time and a claim that references python-pool:

export CLAIM_EXPIRE_AT="$(python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) + timedelta(minutes=30)).strftime("%Y-%m-%dT%H:%M:%SZ"))')"

cat <<EOF | kubectl apply -n "$BUSINESS_NAMESPACE" -f -
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxClaim
metadata:
  name: python-claim
spec:
  warmPoolRef:
    name: python-pool
  lifecycle:
    shutdownPolicy: Delete
    shutdownTime: "${CLAIM_EXPIRE_AT}"
EOF

shutdownTime is an absolute expiration timestamp, not an idle timeout.

Verify the Claimed Sandbox

Wait for the claim and resolve the resulting Sandbox identity:

kubectl wait --for=condition=Ready sandboxclaim/python-claim \
  -n "$BUSINESS_NAMESPACE" \
  --timeout=180s

SANDBOX_NAME="$(kubectl get sandboxclaim python-claim \
  -n "$BUSINESS_NAMESPACE" \
  -o jsonpath='{.status.sandbox.name}')"

test -n "$SANDBOX_NAME"

kubectl wait --for=condition=Ready "sandbox/${SANDBOX_NAME}" \
  -n "$BUSINESS_NAMESPACE" \
  --timeout=180s

Warm-pool adoption can publish the Pod name as an annotation. A cold start can use the stable Sandbox identity as the Pod name fallback:

POD_NAME="$(kubectl get sandbox "$SANDBOX_NAME" \
  -n "$BUSINESS_NAMESPACE" \
  -o jsonpath='{.metadata.annotations.agents\.x-k8s\.io/pod-name}')"

if [ -z "$POD_NAME" ]; then
  POD_NAME="$SANDBOX_NAME"
fi

test -n "$POD_NAME"

kubectl exec -n "$BUSINESS_NAMESPACE" "$POD_NAME" -c python -- \
  python -c 'print("claim-sandbox-check=ok")'

Expected output:

claim-sandbox-check=ok

Clean Up

Verify the active cluster and namespace before deleting resources. Delete resources in reverse dependency order:

kubectl config current-context
kubectl delete sandboxclaim python-claim -n "$BUSINESS_NAMESPACE"
kubectl delete sandboxwarmpool python-pool -n "$BUSINESS_NAMESPACE"
kubectl delete sandboxtemplate python-template -n "$BUSINESS_NAMESPACE"
kubectl delete networkpolicy agent-sandbox-egress -n "$BUSINESS_NAMESPACE"

Do not delete Agent Sandbox CRDs as part of workload cleanup. Removing a CRD deletes the API definition and can remove all instances of that resource type.