StatefulSet必须用于有状态组件,因Deployment无法保障Pod稳定身份、有序启停和专属PVC绑定,否则会导致数据丢失、DNS解析失败及PV错乱。

直接上生产环境的分布式应用,不能靠 Deployment 硬扛——有状态组件(比如数据库、消息队列、向量库)必须用 StatefulSet,否则扩缩容会丢数据、DNS 解析失败、PV 绑定错乱。
为什么不能全用 Deployment 部署分布式应用
Deployment 适合无状态服务,比如 API 网关、前端、计算型微服务。但分布式系统里常含以下组件,它们依赖稳定身份和存储:
- PostgreSQL / MySQL 主从集群:需要固定 Pod 名(
pg-0、pg-1)、独立 PVC、有序启停 - Redis 哨兵或集群模式:哨兵节点需固定端口映射和 DNS 可达性,
redis-sentinel-0.dify-redis-headless这类域名只在StatefulSet+ Headless Service 下有效 - Milvus / Elasticsearch / RocketMQ:每个实例需绑定专属 PV,且启动顺序影响元数据同步(如 Milvus 的
rootcoord必须先于datacoord)
用 Deployment 部署这类组件,常见报错包括:FailedAttachVolume(PVC 多次绑定冲突)、connection refused(Pod 重启后 DNS 名失效)、etcdserver: request timed out(节点间无法建立稳定 peer 连接)。
StatefulSet 必配三要素:Headless Service + volumeClaimTemplates + 稳定网络标识
缺一不可,否则无法形成真正可用的分布式拓扑:
-
headless Service(即clusterIP: None)是前提:它不分配 VIP,而是为每个 Pod 生成可解析的 DNS 记录,格式为<pod-name>.<service-name>.<namespace>.svc.cluster.local -
volumeClaimTemplates不是写死 PVC 名,而是模板:Kubernetes 会为每个 Pod 自动创建唯一 PVC(如www-web-0、www-web-1),绑定到对应 PV - Pod 名必须带序号(
web-0、web-1),且启动/删除严格按序:扩容时先起web-2,缩容时先删web-2,避免脑裂
示例关键片段:
针对 Kubernetes 仪表板和 Web UI 的浏览器自动化。适用于与 Kubernetes Dashboard、Grafana、ArgoCD UI 或其他 Web 界面交互。需要设置 MCP_BROWSER_ENABLED=true。
apiVersion: v1
kind: Service
metadata:
name: dify-pg-ha
spec:
clusterIP: None # ← 必须是 None
selector:
application: spilo
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: dify-postgres
spec:
serviceName: "dify-pg-ha" # ← 必须与 headless service 名一致
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gietcd、Spilo、Redis-Sentinel 这类组件要额外注意初始化顺序
它们不是“拉起就通”,必须等前序组件就绪后才开始主从协商或选举,否则卡在 Pending 或反复 CrashLoopBackOff:
- etcd 集群:3 节点需全部 Ready 后,
etcdctl member list才显示started;若仅 1–2 个 Ready,剩余节点会不断重试连接 - Spilo(PostgreSQL HA):依赖 etcd 或 Kubernetes 内置 leader 选举;若
etcd未就绪,所有 Pod 日志会出现failed to get leader lock - Redis 哨兵:sentinelConfig 中
quorum必须 ≤ 实际运行的 sentinel 数量;设为3但只部署了 2 个 Pod,哨兵永远无法达成多数派,主节点无法被识别
验证命令要进 Pod 执行,不能只看 kubectl get pods:
kubectl exec -n dify dify-postgres-0 -- patronictl list(检查 PostgreSQL 集群角色)kubectl exec -n dify redis-sentinel-0 -- redis-cli -p 26379 SENTINEL masters(确认哨兵已发现主库)
Service 和 Ingress 暴露方式要分层设计
对外暴露 ≠ 全部走 Ingress。分布式系统内部通信必须走 ClusterIP 或 headless Service,否则跨节点流量绕过 DNS 发现逻辑,导致连接错位:
- 组件间调用(如 Dify backend 连 PostgreSQL):用
postgres://dify-pg-ha:5432/dify,而不是postgres://dify-pg-ha.dify.svc.cluster.local:5432—— 后者冗余且易因 namespace 变更失效 - 外部访问 API 或 Dashboard:用
Ingress+ TLS,但路径要收敛(如/api/*转给 backend,/metrics转给 prometheus) - 管理端口(如 etcd client port 2379、Redis sentinel port 26379):禁止暴露到公网,用
ClusterIP或NodePort限定内网访问
最容易被忽略的是 readinessProbe 设置:PostgreSQL 的探针不能只查端口,得执行 SQL(SELECT 1);Redis 哨兵得用 SENTINEL get-master-addr-by-name,否则 Pod Ready 了但实际还没加入集群。

















