在Kubernetes中编排Agent,真正影响稳定性的往往不是镜像本身,而是控制器选型。Agent有的只负责执行无状态任务,比如调用外部API、消费消息队列;有的则需要记住会话上下文、持有任务状态或维护本地缓存。用Deployment去跑有状态Agent,Pod重建后名称和IP都会变,客户端无法继续用旧地址访问;反过来,用StatefulSet跑纯无状态Agent,会增加不必要的PVC和顺序约束。下面基于这两种控制器的行为差异,说明配置方法。

Deployment配置无状态Agent的关键项
无状态Agent适合Deployment,因为Deployment管理ReplicaSet,副本可以任意替换。关键配置在于副本数、资源限制、探针和优雅停机。副本数不要拍脑袋,要根据单Pod处理能力评估。比如一个Agent平均每秒处理20个请求,目标QPS是100,那么至少需要5个副本,再加20%冗余取6个。资源请求requests决定调度,limits防止单个Pod拖垮节点。CPU限制不宜设得过低,否则会出现throttling,表现为P99延迟升高。内存limit可以略高于平均使用,但不要给到节点内存上限。
探针配置对Agent的可用性影响很大。readinessProbe控制Service是否转发流量,livenessProbe负责进程卡死后的重启。对Agent来说,readinessProbe可以检查健康端点或队列连接状态,失败时把Pod从Endpoints摘除,而不是杀死进程;livenessProbe要宽松一些,因为GC停顿或短暂的外部依赖抖动可能被误判。一个常见的错误是给livenessProbe设置过短的timeoutSeconds,导致Pod频繁重启。建议readinessProbe每10秒检查一次,失败阈值3次;livenessProbe每30秒检查一次,失败阈值5次。下面是Deployment的基础示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-worker
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: agent-worker
template:
metadata:
labels:
app: agent-worker
spec:
terminationGracePeriodSeconds: 30
containers:
- name: agent
image: myregistry.local/agent:1.4.2
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 30
failureThreshold: 5
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 15"
优雅停机很容易被忽略,但它直接决定滚动更新期间是否出现请求错误。preStop里的sleep通常要大于Endpoints传播时间,否则Service还在转发请求,Pod已经退出。Deployment默认terminationGracePeriodSeconds是30秒,如果preStop超过30秒,需要调大。更新策略方面,maxUnavailable: 0可以保证滚动更新期间不减少可用副本,但要保证集群有资源创建新Pod。如果想在一批更新时观察错误率,可以把maxSurge设大,或使用kubectl rollout pause暂停。Deployment还应该配合PodDisruptionBudget,比如设置minAvailable: 2,避免节点排水时副本被同时驱逐。
StatefulSet如何满足有状态Agent
有状态Agent的典型特征是每个副本拥有独立身份。例如一个Agent负责维护某一类任务的分片,或者与外部系统保持长连接,外部系统要求客户端地址固定。StatefulSet会给Pod分配从0开始的序号,名称固定为agent-store-0、agent-store-1。即使Pod被删除重建,名字依然不变。配合无头服务,集群内可以通过agent-store-0.agent-store-headless访问指定Pod。这种稳定DNS对需要点对点通信的Agent非常关键。StatefulSet必须设置serviceName,并且Service的clusterIP为None。先看无头服务的定义:
apiVersion: v1
kind: Service
metadata:
name: agent-store-headless
spec:
clusterIP: None
selector:
app: agent-store
ports:
- port: 9090
targetPort: 9090
存储配置由volumeClaimTemplates完成。每个Pod会根据模板创建独立PVC,例如data-agent-store-0。这样即使Pod被重新调度到其他节点,云盘或分布式存储仍会跟随。删除Pod不会删除PVC,因此状态可以保留。但要注意,如果PVC的回收策略是Delete,删除StatefulSet时PVC会被删除,数据也就没了。生产环境应把StorageClass的reclaimPolicy设置为Retain,或者至少在删除前备份。StatefulSet模板示例如下:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: agent-store
spec:
serviceName: agent-store-headless
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: agent-store
template:
metadata:
labels:
app: agent-store
spec:
containers:
- name: agent
image: myregistry.local/agent-stateful:1.4.2
ports:
- containerPort: 9090
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: data
mountPath: /var/lib/agent
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
StatefulSet的更新顺序与Deployment不同。默认RollingUpdate会从最高编号开始,逐个更新,确认ready后再继续下一个。这对数据库类或分片Agent是安全的,因为一次只影响一个副本。还可以使用updateStrategy.rollingUpdate.partition做金丝雀更新。例如设置partition: 2,则只有序号大于等于2的Pod会被更新,0和1保持旧版本。验证新版本稳定后把partition改为0完成全量更新。无头服务的DNS记录只有在Pod ready后才会发布,所以滚动更新期间旧Pod暂时不可用,客户端需配置重试。如果业务对中断极敏感,需要额外设计会话迁移逻辑。
混合场景下的控制器组合与选型
实际系统很少单独使用某一种控制器。常见的组合是前端接入层用Deployment承载无状态Agent,后端状态层用StatefulSet。例如一个客服Agent系统,消息网关Agent负责接收WebSocket连接,可以随意扩缩容,使用Deployment;会话记忆Agent需要保存上下文摘要,每个Pod对应一部分用户分片,使用StatefulSet。前端Deployment通过无头服务或普通Service访问后端StatefulSet的稳定DNS。不要在Deployment下挂载ReadWriteOnce PVC来模仿StatefulSet,这会让多个Pod调度到不同节点时出现卷冲突,或者只有一个Pod能正常挂载。
选型可以按几个维度判断。Pod重启后能否接受新的随机身份?任务是否幂等?如果需要保存独立状态并且要求稳定网络标识,就选StatefulSet。如果任务可重入、所有副本等价,Deployment更简单。下面给出对比表:
| 维度 | Deployment | StatefulSet |
|---|---|---|
| Pod名称 | 随机后缀 | 固定序号,如agent-store-0 |
| 网络标识 | 无稳定DNS | 通过无头服务提供稳定A记录 |
| 存储 | 通常使用临时卷或共享存储 | 每个Pod独立PVC |
| 更新顺序 | 滚动更新,无严格顺序 | 默认逆序滚动,可分区 |
| 适用场景 | 无状态API、消息消费 | 会话缓存、任务队列、分片存储 |
有一点需要澄清:StatefulSet并不适合所有有状态场景。如果状态在外部Redis或数据库中,Pod本身不保存数据,那么Deployment加上外部依赖即可。StatefulSet的价值在于状态必须跟随Pod生命周期,或者需要稳定的Peer间通信。反过来,对于StatefulSet中的Agent,负载均衡策略也可能不同。如果客户端通过无头服务自定义选择Pod,需要自己在客户端实现哈希或轮询;如果使用普通Service,只会随机负载到某个Pod,可能无法保证会话粘滞。所以选型时还要考虑服务暴露方式。
常见配置错误与排查
StatefulSet最常见的错误是忘记配置serviceName,这会让控制器直接拒绝创建。另一个高频问题是PVC未绑定导致Pod一直Pending,通常是StorageClass没有可用供给或存储容量不足。无头服务的selector如果和Pod标签不匹配,DNS解析会得到空结果,客户端看起来像是访问不到服务。readinessProbe过严也会让Pod长期处于NotReady状态,StatefulSet后续不再更新,形成卡住的现象。
Deployment滚动更新卡住,多数原因是maxSurge和maxUnavailable设置不合理,或者集群资源不足导致新Pod无法启动。preStop脚本里如果写成/bin/sh但镜像不包含shell,也会失败。terminationGracePeriodSeconds小于preStop中的sleep时长时,Pod会在优雅退出完成前被强制终止,导致请求中断。排查这类问题时,先看kubectl get events,再用kubectl describe pod检查Events和Conditions,通常能快速定位是调度失败、探针失败还是卷挂载问题。
两个控制器的配置参数容易记混,但核心逻辑可以归纳为:Deployment只关注无差别副本的可用数量,StatefulSet则额外维护序号、稳定标识和独立存储。把这条逻辑想清楚,再配合探针、滚动策略和PVC回收策略,基本可以覆盖大部分Agent编排场景。
Kubernetes编排Deployment配置StatefulSet配置修改时间:2026-09-20 04:22:36