Kubernetes 安全跑 AI Agent 的四種隔離架構

Kubernetes 安全跑 AI Agent 的四種隔離架構

📌 本文重點

  • 不要讓 Agent 直接在應用 Pod 裡執行 shell
  • 把程式碼執行抽象成獨立 Exec API
  • 依需求從 Sidecar 演進到 Dispatcher + microVM

在 Kubernetes 上跑 AI Agent,最大痛點不是「模型怎麼接」,而是:我要讓 Agent 能執行程式碼,但又不想整個 cluster 變成 root shell 即時互動環境。這一篇用四種實戰隔離模式,從 完全禁止 exec短暫 sandbox,給你一個能落地、能演進、不會把未來自己鎖死的設計路線。

兩個基本原則先講清楚:
1. 不要讓 Agent 直接在應用 Pod 裡 exec /bin/sh
2. 把「執行程式碼」當成一個獨立產品線,至少要有 API、隔離與審計


重點說明

1. 四種 Exec 隔離模式與威脅模型

  1. No-Exec 基線
  2. 威脅模型:Agent 只能讀資料、呼叫 API,不允許任意程式碼或 shell。防止「自動化腳本變挖礦機」。
  3. 使用情境:報表、客服、內部 FAQ、只讀資料查詢。
  4. 成本/延遲:最低;只要你有 Agent,就應該先有這個 baseline

💡 關鍵: 先建立 No-Exec 基線,把「不執行程式碼也能運作」當成預設安全狀態,之後才有空間加能力而不是拆炸彈。

  1. Sidecar Exec Server
  2. Agent Pod 旁邊掛一個 sidecar 容器,提供 受限的程式碼執行 API(例如只允許 Python,禁網路)。
  3. 威脅模型:Agent 若被 prompt 注入,最多傷到 該 Pod 的 sandbox,不會拿到整個 node。
  4. 使用情境:需要頻繁、小量運算(轉檔、格式化、查詢小 DB)。
  5. 成本/延遲:啟動快、延遲低,但隔離仍與主容器共享同個 Pod 命名空間,需嚴控權限。

  6. 獨立 Exec Pod(長駐)

  7. 單獨 Deployment/Pod 提供 Exec API,Agent 透過 Service/HTTP 呼叫。
  8. 威脅模型:即使 Agent 被攻擊,影響範圍收斂在 Exec Pod 的 namespace / RBAC
  9. 使用情境:多 Agent 共用運算資源、內部「程式碼執行服務」。
  10. 成本/延遲:多一跳網路,但隔離與資源配額更好控制。

  11. 短暫性 Exec Dispatcher(Job / ephemeral Pod)

  12. 每次高風險程式碼執行,Agent 呼叫 Dispatcher API → 建立一次性 Job/Pod → 執行完就刪
  13. 威脅模型:攻擊者很難長期駐留,每次都是新 sandbox;配合 NetworkPolicy、seccomp,接近 microVM 的防護。
  14. 使用情境:自動修 production bug、CI 內部 code 修補、批次資料轉換。
  15. 成本/延遲:啟動開銷高,但安全性最佳、審計最容易。

💡 關鍵: 短暫性 Dispatcher 每次執行都重建 sandbox,換取較高延遲,換來接近 microVM 等級的隔離與容易審計。


2. 把 Exec 抽象成 API:Agent 只看見一個能力

不論選哪種模式,推薦都做成一個 Exec API 抽象層

  • Agent 視角:呼叫 /exec,送程式碼和限制(languagetimeoutresources),拿回 stdout/stderr
  • 基礎設施視角:背後可以從 Sidecar → 獨立 Pod → Dispatcher/Job 演進,而介面不變。

這樣可以避免那種常見悲劇:

「先在應用容器裡 subprocess.run 上線,等 Agent 用到 everywhere 之後才發現安全有洞,想拆出來卻動不了。」

💡 關鍵: 先穩定 Exec API 介面,再替換背後實作,可以避免一開始圖方便埋下日後無法重構的安全技術債。


3. 與 microVM(如 SuperHQ)怎麼搭配?

像 SuperHQ 這種 microVM 沙盒 的隔離更硬(虛擬化層級),但成本與管理更重。實務上可以:

  • Kubernetes 內部先用 短暫性 Exec Dispatcher 做 cluster 級隔離。
  • 對於「真的可能動 production」的改動,Dispatcher 再往下調用像 SuperHQ 的 microVM sandbox,做二層隔離。

關鍵是:不要一開始就把 microVM 當銀彈,反而要先把 API、審計、RBAC 的基本盤打好。


實作範例

1. 給 Agent 的 Exec API 抽象

假設你在後端提供一個 POST /exec 給 Agent 使用:

// TypeScript / pseudo-code
interface ExecRequest {
  language: 'python' | 'bash';
  code: string;
  timeout_ms?: number;
  memory_mb?: number;
  audit_metadata?: {
    agent_id: string;
    user_id: string;
    task_id: string;
  };
}

interface ExecResponse {
  stdout: string;
  stderr: string;
  exit_code: number;
  sandbox_id: string;
  started_at: string;
  finished_at: string;
}

// Agent 只呼叫這個
async function agentExec(req: ExecRequest): Promise<ExecResponse> {
  const resp = await fetch("https://exec-gateway.internal/exec", {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(req),
  });
  return resp.json();
}

背後可以是 Sidecar、獨立 Pod 或 Dispatcher,Agent 完全不需要知道。


2. Sidecar Exec Server:最小可行安全版

Pod 範例(主容器 + Sidecar)

apiVersion: v1
kind: Pod
metadata:
  name: agent-with-sidecar
spec:
  containers:
    - name: agent
      image: myorg/agent:latest
      env:
        - name: EXEC_SERVER_URL
          value: "http://127.0.0.1:8080"  # 只在 Pod 內可達

    - name: exec-sidecar
      image: myorg/exec-sandbox:py3
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        seccompProfile:
          type: RuntimeDefault
        capabilities:
          drop: ["ALL"]
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}

注意幾點:

  • Sidecar 用 readOnlyRootFilesystem: true,只給一個 emptyDir 當 scratch space。
  • seccompProfile: RuntimeDefault + capabilities: drop: ["ALL"] 擋掉大多數系統呼叫。
  • exec server 只在 localhost 對 agent 開放。

Sidecar 容器內部可用像這樣的 server:

# exec-sidecar main.py (簡化示意)
from fastapi import FastAPI
import subprocess, tempfile, textwrap

app = FastAPI()

@app.post("/exec")
async def exec_code(req: dict):
    code = req["code"]
    timeout = min(req.get("timeout_ms", 5000) / 1000, 10)
    with tempfile.NamedTemporaryFile(suffix=".py", dir="/tmp", delete=False) as f:
        f.write(code.encode("utf-8"))
        path = f.name
    p = subprocess.run(
        ["python", path],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        timeout=timeout,
        check=False,
        text=True,
    )
    return {
        "stdout": p.stdout,
        "stderr": p.stderr,
        "exit_code": p.returncode,
    }

3. 獨立 Exec Pod + NetworkPolicy 限制網路

Exec Service Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: exec-service
  namespace: agent-exec
spec:
  replicas: 2
  selector:
    matchLabels:
      app: exec-service
  template:
    metadata:
      labels:
        app: exec-service
    spec:
      securityContext:
        runAsNonRoot: true
      containers:
        - name: exec
          image: myorg/exec-sandbox:py3
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            seccompProfile:
              type: RuntimeDefault
          resources:
            limits:
              cpu: "1"
              memory: "1Gi"

NetworkPolicy:只允許 Agent Namespace 打進來,不允許對外上網

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: exec-deny-egress
  namespace: agent-exec
spec:
  podSelector: { matchLabels: { app: exec-service } }
  policyTypes: ["Ingress", "Egress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: agent
  egress: []  # 不允許任何外連

搭配 RBAC:只給 Agent ServiceAccount 能呼叫 Exec Service,不給其他 Pod 用。


4. 短暫性 Exec Dispatcher:一次一個 Job

Dispatcher 可以是一個常駐服務,收到 Agent 的 Exec Request 後,動態建一個 Job:

apiVersion: batch/v1
kind: Job
metadata:
  generateName: exec-task-
  namespace: agent-exec
spec:
  ttlSecondsAfterFinished: 60
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: runner
          image: myorg/exec-runner:py3
          command: ["python", "/runner/run.py"]
          env:
            - name: CODE_B64  # 程式碼以 base64 傳入
              value: "{{ .code_b64 }}"
          securityContext:
            runAsNonRoot: true
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            seccompProfile:
              type: RuntimeDefault

Dispatcher 伺服器(簡化 pseudo-code):

// create Job + watch logs/exit code
async function handleExec(req: ExecRequest): Promise<ExecResponse> {
  const jobName = await k8sCreateJobFromTemplate(req.code);
  const { logs, exitCode } = await waitForJobAndCollectLogs(jobName);
  await writeAuditLog({
    sandbox_id: jobName,
    ...req.audit_metadata,
    code_hash: hash(req.code),
    exit_code: exitCode,
  });
  return {
    stdout: logs.stdout,
    stderr: logs.stderr,
    exit_code: exitCode,
    sandbox_id: jobName,
    started_at: new Date().toISOString(), // 真實實作請用 Job status
    finished_at: new Date().toISOString(),
  };
}

好處:

  • 多租戶隔離:可依 tenant 建不同 namespace,Dispatcher 根據 audit_metadata.tenant_id 選 namespace。
  • 審計完整:所有指令、結果都在 Job + audit log 裡,符合合規需求。

建議與注意事項

1. 多租戶 / 多 Agent:Namespace + RBAC 要先設計好

  • 每個 tenant / 敏感業務線,用不同 namespace + ServiceAccount
  • RBAC 控制:
  • 哪些 Agent 可以呼叫哪些 Exec Service / Dispatcher API。
  • 哪些 ServiceAccount 可以在哪些 namespace 建 Job。
  • 禁用 kubectl exec 這類廣泛權限,改成只允許建立特定 label 的 Job。

2. 記錄與審計:把 Agent 當「能下命令的人」對待

最少要記:

  • 誰發的指令agent_id, user_id, tenant_id
  • 執行了什麼:程式碼 hash / snippet、image name、namespace。
  • 結果:exit code、stdout/stderr 片段、耗時、資源消耗。

實務建議:

  • 把審計資料寫到 集中 log(如 Loki / Elasticsearch),設 index pattern 方便 incident 回溯。
  • 敏感環境可以啟用 只讀審計存儲(append-only S3、WORM 存儲)

3. 選型決策表:怎麼組合四種模式?

公司安全等級 / 場景 建議模式組合
內網 demo、無敏感資料 No-Exec 基線;需要運算再加 Sidecar Exec
一般內部工具,允許 Agent 自動改測試 / 小腳本 獨立 Exec Pod + NetworkPolicy + RBAC;高風險任務用 Dispatcher。
有合規需求(金融、醫療)、多租戶 SaaS 短暫性 Exec Dispatcher + 多 namespace + 強 RBAC + 完整改審計。
能直接動 production(自動修 bug、自動部署) Dispatcher + microVM(如 SuperHQ)疊加;需要人類 review gate + 強審計。

實務路線建議:

  1. 先上 No-Exec baseline:把所有「執行程式碼」需求集中到一個 Exec API 服務。
  2. 需求變多 → 用 獨立 Exec Pod + NetworkPolicy 接手 Sidecar。
  3. 需要自動動 production → 引入 短暫性 Exec Dispatcher +(選配)microVM

最後再提醒一次:最危險的不是沒用 sandbox,而是先圖方便在應用容器裡直接 exec shell,等 Agent 真的有價值時,整個 cluster 已經跟它綁死,誰都不敢動。現在就把 Exec 抽出來,之後演進才有空間。


🚀 你現在可以做的事

  • 檢查現有 Agent 是否在應用 Pod 內直接 execsubprocess.run,列出需遷移的路徑
  • 在開發環境先實作一個簡單的 POST /exec 抽象層,後端先接到一個獨立 Exec Pod
  • 為高風險任務 PoC 一個「短暫性 Exec Dispatcher + Job」流程,並加上最基本的審計欄位

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *