第 11 课:Kubernetes 第一课——为什么 Agent API 用 Deployment,而 Coding Agent 任务更适合 Job
前面已经把 Mini Agent Platform 做到了:
Nginx → API → Redis → Runner → PostgreSQL
↓
Agent / Shell
今天把 Docker Compose 的思维迁移到 entity["software","Kubernetes","container orchestration platform"]。重点不是背 YAML,而是回答企业 Agent 部署最重要的问题之一:
哪些组件应该长期活着,哪些 Agent workload 应该“任务来了创建、完成后销毁”?
Kubernetes 官方对两者的定义非常适合这个问题:Deployment 管理一组持续运行的 Pod,并维持期望状态;Job 则代表“运行到完成然后停止”的一次性任务。citeturn0search2turn0search1
1. 先把 Compose 翻译成 Kubernetes
上一阶段是:
Docker Compose
api container
redis container
postgres container
runner container
迁移后不要机械变成“四个永远运行的 Pod”。
更合理的第一版是:
Kubernetes Cluster
│
├── Deployment: agent-api
│ ├── Pod api-1
│ └── Pod api-2
│
├── Service: agent-api
│
├── Deployment/Stateful workload: redis
│
├── PostgreSQL
│
└── Job: agent-task-101
│
└── Pod
├── Agent Runtime
├── Git workspace
├── shell
└── tests
其中最大的变化是:
以前:
一个Runner一直运行
→ 从Redis拿很多任务
现在还可以:
一个Agent Task
→ 一个Job
→ 一个隔离Pod
→ 做完退出
两种模式都存在,但 Coding Agent 尤其值得理解后一种。
2. Pod 到底是什么
很多初学者把:
Pod = Container
这是不准确的。
更接近:
Pod
│
├── Container A
├── Container B
│
├── shared network namespace
└── shared volumes
最简单的 Pod 通常只有一个 container:
Pod
└── agent-runner container
但以后 Agent workload 可以出现:
Agent Pod
│
├── agent container
│
├── telemetry sidecar
└── shared workspace volume
所以 Kubernetes 真正调度的基本工作负载单位是:
Pod
而不是直接调度某个 Docker container。
Pod 又是短暂资源。Kubernetes 官方明确提醒,不应把单个 Pod 当作可靠、永久存在的实体;Pod 可以被创建和销毁。citeturn0search0
这对 Agent 是好事:
Task 101
↓
Pod 101
↓
执行
↓
结束
我们本来就不希望它永久存在。
3. 为什么 API 用 Deployment
Agent API 是:
POST /tasks
GET /tasks/{id}
GET /tasks/{id}/events
它应该:
一直在线
一个 API Pod 如果崩溃,我们希望:
Kubernetes
↓
发现实际Pod数量少于期望值
↓
创建新Pod
这就是 Deployment 擅长的事情。
例如:
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-api
spec:
replicas: 2
selector:
matchLabels:
app: agent-api
template:
metadata:
labels:
app: agent-api
spec:
containers:
- name: api
image: mini-agent-api:0.1
ports:
- containerPort: 8000
核心不是 YAML,而是:
replicas: 2
表达:
我希望任何时候都有两个 API Pod。
Kubernetes Deployment Controller 会持续把实际状态向这个声明的期望状态调整;它也支持 rollout、扩缩容和 rollback。citeturn0search2
这叫:
Desired State
4. Kubernetes 和 docker run 最大的思维差异
Docker:
docker run agent-api
你的思维容易变成:
“启动这个进程。”
Kubernetes:
replicas: 2
思维变成:
“系统应该始终有两个健康实例。”
例如:
Desired:
2
Actual:
2
正常。
某个 Pod crash:
Desired:
2
Actual:
1
Controller:
发现差异
↓
创建Pod
↓
Desired:
2
Actual:
2
这就是:
reconciliation loop
这个思想非常重要,因为后面你自己写 Agent Orchestrator,也会出现类似模式:
Task desired state:
running
Actual:
worker disappeared
↓
reconcile
重新调度
所以 Kubernetes Controller 思维和 Agent Platform Controller 思维其实高度相关。
5. 那为什么 Coding Agent Task 不直接也用 Deployment
假设任务:
修复
HttpClient.cs的 timeout bug,运行测试后提交 patch。
它的生命周期是:
start
↓
clone / checkout
↓
LLM
↓
tool calls
↓
tests
↓
result
↓
exit
这是典型:
run-to-completion
如果使用 Deployment:
Agent完成
↓
process exit 0
↓
Deployment发现Pod没了
↓
再启动一个
↓
Agent任务又执行一次
这显然不是我们想要的。
Job 正好相反。Kubernetes 官方定义:
Job 创建一个或多个 Pod,并持续追踪它们,直到指定数量的 Pod 成功结束;最简单的 Job 就是可靠地运行一个 Pod 直到完成。citeturn0search1
因此:
Long-lived service
→ Deployment
Run-to-completion task
→ Job
这是今天必须形成的判断。
6. 第一个 Agent Job
apiVersion: batch/v1
kind: Job
metadata:
name: agent-task-101
spec:
template:
spec:
restartPolicy: Never
containers:
- name: agent
image: mini-agent-runner:0.1
env:
- name: TASK_ID
value: "task-101"
执行:
kubectl apply -f agent-job.yaml
观察:
kubectl get jobs
再:
kubectl get pods
可能看到:
agent-task-101-x7abc Running
一会儿:
agent-task-101-x7abc Completed
Job:
0/1
↓
1/1
完成。
Kubernetes Job 对 Pod 失败也提供重试机制;如果 Pod 因节点故障等原因消失,Job Controller 可以重新创建 Pod。citeturn0search1
7. 这里出现 Agent 一个很危险的问题:Retry
假设 Agent:
修改代码
↓
git push
↓
Pod突然挂掉
Job Controller:
发现任务未成功完成
↓
重新创建Pod
新的 Agent:
再次运行
↓
再次git push?
这就是分布式系统非常重要的:
retry
+
side effect
问题。
因此不能简单认为:
Kubernetes Job自动retry
= Agent可靠性解决了
反而要问:
这个Agent步骤能重复执行吗?
例如:
git status
重复通常问题不大。
read file
问题也不大。
但:
git push
创建PR
发送邮件
删除云资源
数据库UPDATE
发布生产版本
都具有:
side effect
。
所以以后会正式学习:
idempotency
即:
同一个操作重复执行,系统仍保持可接受的一致结果。
这正是 Agent 工程比“调用一次模型”复杂很多的地方。
8. restartPolicy 与 Job 重试不是一回事
Job 允许:
restartPolicy:
Never
或者:
OnFailure
``` citeturn0search1
要区分两层:
```text
Container失败
↓
同一个Pod内部是否restart
和
Pod失败
↓
Job Controller是否再创建Pod
所以以后看到:
Agent运行了两次
不能只查:
restartPolicy
还要查:
Job backoff
Pod restart count
controller events
queue retry
应用自身retry
企业系统经常存在:
LLM SDK retry
+
Agent runtime retry
+
Redis retry
+
Kubernetes Job retry
四层重试。
如果设计不好:
1次故障
↓
3 × 3 × 3 × 3
↓
81次尝试
这叫:
retry amplification
对 LLM 特别危险,因为还意味着:
Token Cost Amplification
9. Service 又解决什么
Deployment 创建的 Pod:
agent-api-f8d7c
agent-api-q91jk
可能随时变化。
每个 Pod 也有自己的 IP。
客户端不能写:
http://10.244.2.17:8000
因为这个 Pod 明天可能不存在。
所以:
Service
提供稳定网络抽象。
例如:
apiVersion: v1
kind: Service
metadata:
name: agent-api
spec:
selector:
app: agent-api
ports:
- port: 80
targetPort: 8000
于是内部其它服务只访问:
http://agent-api
Service:
agent-api
│
├── Pod A
└── Pod B
Pod 换了:
Pod A deleted
↓
Pod C created
客户端仍然:
http://agent-api
不用修改。
这正是 Kubernetes Service 的核心目的:Pod 是 ephemeral 的,而 Service 提供稳定的网络入口与服务发现抽象。citeturn0search0turn0search4
10. Compose Service 和 Kubernetes Service 不完全是一回事
前面 Docker Compose:
services:
api:
这里:
service
主要是 Compose 应用模型里的组件。
Kubernetes:
kind: Service
是一个明确的 Kubernetes API object,主要解决:
稳定endpoint
+
service discovery
+
traffic routing
不要因为名字一样就混为一谈。
11. Agent 为什么尤其需要资源限制
普通 Agent 一旦开始:
npm install
dotnet build
pytest
ripgrep
LLM context construction
资源使用可能突然升高。
更危险:
while true; do ...; done
或者测试程序内存泄漏。
如果没有限制:
Agent Pod
↓
吃光Node RAM
↓
影响API
↓
影响Redis
↓
影响其他Agent
所以 Pod container 可以定义:
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "2Gi"
Kubernetes 官方区分:
requests
与:
limits
并支持 CPU、memory、ephemeral storage 等资源的 requests/limits。citeturn0search6
先这样理解:
request
≈ 调度时“我至少需要多少”
limit
≈ “最多允许我用多少”
虽然实际 CPU 和 memory 的 enforcement 行为还有细节,后面专门讲。
12. 为什么 Agent 比普通 Web API 更需要 limit
API:
通常:
接请求
查数据库
返回
Agent:
模型决定下一步
↓
模型可能运行build
↓
build又启动几十个process
↓
测试又可能并行
即:
resource consumption
具有更大的:
dynamic / model-directed
特征。
因此 Agent Platform 不能只控制:
LLM Token Limit
还必须控制:
CPU
RAM
Disk
Process count
Execution time
Network
最终一个任务预算会变成:
Agent Budget
Token:
100k
Cost:
$2
Wall Time:
20 min
CPU:
2 cores
Memory:
4 GiB
Disk:
10 GiB
Network:
allowlist
Tool calls:
500
这才是真正的:
Agent Resource Governance
。
13. 这与 Codex Sandbox 有什么关系
entity["software","OpenAI Codex","coding agent"] 的公开 system card 对 cloud agent 的描述非常适合拿来对照:云端任务运行在隔离 container 中,默认关闭网络访问,并限制其与用户宿主环境及 workspace 外敏感数据的交互;本地环境则依据操作系统采用不同 sandbox 机制。citeturn0search35turn0search37
因此我们可以把自己的 Kubernetes Agent workload 理解为:
Kubernetes Pod
↓
Container
↓
Agent Sandbox
↓
Agent Runtime
↓
Shell
注意:
Kubernetes
≠
Codex sandbox
它们属于不同层。
例如:
K8s:
Pod最多2GiB RAM
Sandbox:
只能写/workspace
Agent Policy:
git push需要approval
三者同时工作。
这就是前面一直强调的:
Defense in Depth
。
14. 一个企业 Agent Pod 最终可能长这样
Agent Job Pod
│
├── Security
│ ├── non-root UID
│ ├── restricted filesystem
│ └── reduced capabilities
│
├── Resources
│ ├── CPU limit
│ ├── memory limit
│ └── ephemeral storage limit
│
├── Workspace
│ └── /workspace
│
├── Network Policy
│ └── allowed endpoints only
│
├── Secrets
│ └── short-lived credentials
│
└── Agent Runtime
├── model
├── shell
├── git
├── MCP
└── tests
以后我们会逐项实现,而不是今天一次塞完。
15. API 如何动态创建 Job
最终 API 不应该让管理员手工:
kubectl apply -f task101.yaml
而是:
POST /tasks
↓
API创建task
↓
Scheduler
↓
调用Kubernetes API
↓
Create Job
伪代码:
def start_task(task):
job = build_job_spec(
task_id=task.id,
image="agent-runner:0.1",
)
kubernetes.create_job(job)
于是:
用户:
“修复Bug”
↓ HTTP
Task API
↓ Kubernetes API
Job task-101
↓ scheduler
Node 2
↓ Pod
Agent开始执行
这里第一次真正出现:
Agent Orchestration
。
16. 不要让 Agent 自己随便访问 Kubernetes API
一个很危险的设计:
Agent Pod
↓
拥有cluster-admin
↓
kubectl
那么 Agent 可以:
删除Pod
读取Secret
创建高权限Pod
修改Deployment
这和前面:
Agent + docker.sock
本质非常类似。
都是:
给 Agent 一个能够控制宿主编排系统的高权限接口。
更合理的是:
Agent Pod
│
│ 没有K8s管理权限
▼
执行自己的任务
而:
Scheduler / Controller
单独拥有:
create Job
get Job
delete Job
等必要权限。
这就是后面要正式学习的:
ServiceAccount
RBAC
Role
RoleBinding
。
17. 这里第一次理解 Control Plane
Kubernetes 本身有:
Control Plane
负责:
API
Scheduling
Controllers
Cluster state
Worker Node:
真正运行Pod
而我们的 Agent Platform 也可以类比:
Agent Control Plane
│
├── Task API
├── Scheduler
├── Policy
├── Approval
├── LLM routing
└── Audit
Agent Data/Execution Plane
│
└── Agent Jobs
├── shell
├── git
├── build
└── tests
这个分离非常重要。
因为:
模型产生的、不完全可信的执行
应该尽可能放在:
Execution Plane
而不是让它直接控制:
Control Plane
。
18. 今日真实 Agent 对比:Codex 与 OpenCode
entity["software","OpenCode","open-source coding agent"] 与 Codex 都可以最终执行 shell、编辑文件,但“Agent permission”与“基础设施隔离”应该继续分开理解。
例如:
Agent层:
shell command
→ ask / allow / deny
下面仍然可以:
Kubernetes Job
↓
non-root container
↓
resource limit
↓
network restriction
这意味着即使 Agent policy 出现配置错误:
误允许了一个危险shell
基础设施仍可以限制:
它最多影响这个task pod
。
Codex 云端公开设计同样体现了“Agent 在隔离环境中执行、网络默认受限”的思路。citeturn0search35
因此企业 Agent 平台绝不能把:
Agent自己的permission UI
当成全部安全边界。
19. 今日 10~15 分钟实验
如果本机已有 Docker Desktop,可以启用它自带的 Kubernetes;也可以后面使用 kind 或 minikube。
今天只做一个最小实验。
建立:
agent-job.yaml
内容:
apiVersion: batch/v1
kind: Job
metadata:
name: agent-lab
spec:
template:
spec:
restartPolicy: Never
containers:
- name: agent
image: alpine:latest
command:
- /bin/sh
- -c
args:
- |
echo "Agent task started"
id
pwd
sleep 5
echo "Agent task completed"
执行:
kubectl apply -f agent-job.yaml
然后:
kubectl get jobs
以及:
kubectl get pods
找到 Pod 后:
kubectl logs <pod-name>
应该看到:
Agent task started
...
Agent task completed
最后:
kubectl get jobs
观察:
Complete
。
这一实验的重点不是 Alpine,而是亲眼看到:
Job
↓
创建Pod
↓
运行Container
↓
进程退出0
↓
Pod Completed
↓
Job Complete
。
20. 再故意制造失败
把:
echo "Agent task completed"
后面加入:
exit 1
再执行一个新 Job。
观察:
kubectl get pods
你可能看到不止一个失败/重试相关 Pod,具体数量受 Job 配置影响。
再看:
kubectl describe job <job-name>
重点观察:
Events
Pods Statuses
Failed
。
你第一次会真正看到:
Controller在处理失败
而不只是:
shell返回exit code 1
。
21. PowerShell 用户需要注意
官方 Kubernetes 教程特别提醒,很多示例采用 POSIX shell:
export POD_NAME=...
$(...)
这些不能原样复制到 PowerShell。citeturn0search3
例如 Bash:
POD=$(kubectl get pods -o name | head -1)
PowerShell 更接近:
$pod = kubectl get pods `
-o jsonpath='{.items[0].metadata.name}'
然后:
kubectl logs $pod
这也是为什么课程会持续并行讲:
Bash
PowerShell
——Agent 经常生成“语义正确但 shell 方言错误”的命令。
22. 今日企业故障案例:Agent 一直重新执行
现象:
task-101本来应该执行一次
但日志发现:
执行了4次
不要先问模型:
为什么重复工作?
先建立故障树:
模型内部retry?
│
├─ No
↓
Agent Runtime retry?
│
├─ No
↓
Queue redelivery?
│
├─ No
↓
Job创建了几个Pod?
│
↓
kubectl describe job
│
↓
Pod为什么失败?
│
├─ OOMKilled
├─ exit code != 0
├─ Node failure
├─ timeout
└─ eviction
假设最终发现:
Pod:
OOMKilled
那么真正链路可能是:
Agent运行dotnet test
↓
Memory > limit
↓
Container被kill
↓
Pod失败
↓
Job重试
↓
Agent重新开始
真正原因是:
resource limit
而不是:
LLM hallucination
。
这正是整个课程最终要训练出的能力:
Agent“行为异常”不等于模型异常。
23. 再看一个非常现实的问题:为什么 Job 完成后 Pod 还在
很多初学者会问:
Job已经Complete
为什么kubectl get pods还能看到它?
这是正常的。
Kubernetes 官方说明,Job 完成后通常不会立刻删除完成的 Pod,这样仍然可以查看日志和诊断信息;旧 Job 可以由用户或自动清理机制之后删除。citeturn0search1
这对 Agent 非常有价值:
Agent失败
↓
Pod已经停止
↓
仍可:
kubectl logs
kubectl describe
查看exit code
这就是:
post-mortem debugging
。
以后生产系统会再把这些日志发送到:
Loki / Elasticsearch / Cloud Logging
因为 Pod 最终还是会被删除。
24. 今天的平台架构升级
现在 Mini Agent Platform 已经可以设计成:
Internet
│
TLS
│
Ingress
│
Service
│
┌──────┴──────┐
│ │
API Pod API Pod
│
▼
Task Queue
│
▼
Agent Scheduler
│
Kubernetes API
│
┌─────────┼──────────┐
▼ ▼ ▼
Job 101 Job 102 Job 103
│ │ │
Pod Pod Pod
│
Agent
│
┌─────┼────────┐
Git LLM Shell
这已经非常接近企业 Agent 平台最核心的执行模型。
岗位能力映射
今天真正训练的是:
Pod、Deployment、Service、Job、Controller、desired state、reconciliation、resource requests/limits、run-to-completion workload、Agent retry、side effect、idempotency、control plane 与 execution plane。
面试如果问:
为什么企业 Coding Agent 每个任务适合 Kubernetes Job,而不是 Deployment?
你应该能回答:
API、Gateway 等长期服务需要持续维持期望副本数,因此适合 Deployment;一次 Coding Agent 执行属于 run-to-completion workload,完成后应该停止,因此更符合 Job。Job 还能跟踪完成状态并处理 Pod 失败重试。不过 Agent 往往有 Git push、PR、数据库修改等副作用,所以不能简单依赖自动重试,还必须考虑幂等性、重试层级和审计。
这已经是 Agent Deployment Engineer 应该具备的判断。
今日检查题
- Pod 和 Container 为什么不能简单画等号?
- 为什么 Agent API 更适合 Deployment,而一次 Coding Task 更适合 Job?
- Service 解决了 Pod 的什么问题?
- Agent Pod 被
OOMKilled后为什么可能导致任务重复执行? - 为什么不能直接给 Agent Pod 一个
cluster-adminServiceAccount?
答案核心:Pod 可以包含一个或多个共享网络/存储上下文的容器;Deployment维持长期服务的期望状态而Job运行到完成;Service为易变Pod提供稳定网络入口;OOM导致Pod失败后Job可能重试;cluster-admin 会让模型驱动的工具执行获得控制整个集群的高权限能力。
下一课进入 Kubernetes Agent 安全核心:Namespace + ServiceAccount + RBAC + ConfigMap + Secret + SecurityContext。我们会实际设计两个身份:agent-scheduler 可以创建受限 Job,而 agent-runner 不能创建 Pod、不能读取集群 Secret,并亲手验证一次 kubectl auth can-i 的 Allow/Deny。这会把前面 Linux UID、Docker non-root、Agent Allow/Ask/Deny 和 Kubernetes RBAC 第一次完整串成一条权限链。