跳到主要内容

gRPC 生产落地:从 ClusterIP 长连接倾斜到 Headless DNS、Envoy 与全链路治理

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
21 MIN READ... VIEWS

gRPC 在 Kubernetes 中“能调用成功”并不等于“已经具备生产级负载均衡”。当客户端只解析到一个 ClusterIP,再通过一条长期存在的 HTTP/2 连接发送请求时,多个 Pod 很可能长期冷热不均。

本文以 service-a 这一通用 Java 服务为例,复盘一条典型的生产治理路径:保留现有 ClusterIP Service 兼容原有调用,再增加只供内部 gRPC 发现的 Headless Service,让 Java gRPC 客户端和 Envoy 看见每一个 Pod,并把服务发现、健康检查、摘流、连接排空、超时重试、可观测性和发布验收串成闭环。

如果需要先补齐协议和 Java 基础,可以依次阅读 HTTP/2 协议层Java 与 Netty 实现gRPC 生产治理;本文专注 Kubernetes 与 Envoy 的端点级落地。

这不是一份只解决“DNS 返回几个 IP”的配置说明。生产落地需要同时回答六个问题:

  1. 客户端究竟发现了 VIP,还是每个 Pod?
  2. 负载均衡发生在 TCP 连接、HTTP/2 Stream,还是 RPC 级别?
  3. 不可用 Pod 会不会继续出现在发现结果里?
  4. Pod 下线时,存量长连接和在途 RPC 如何处理?
  5. Java 客户端与 Envoy 谁负责重试、健康检查和摘除?
  6. 如何证明流量真的分散,而不是只证明 DNS 能解析?

一、事故起点:ClusterIP 没坏,但层级不对

原来的调用链如下:

Java / Envoy

│ dns:///service-a.application.svc.cluster.local:8080

ClusterIP 10.96.120.20:8080

│ kube-proxy / dataplane:按连接选择后端

Pod A / Pod B / Pod C

普通 Kubernetes Service 的 DNS 返回 Service 虚拟 IP。客户端看到的后端集合只有一个地址:10.96.120.20:8080。kube-proxy 或集群数据面会把新连接转发到某个 Pod,但它并不理解每个 gRPC RPC 的业务边界。

问题来自 gRPC 的传输模型:

  • gRPC 通常在 HTTP/2 长连接上复用多个并发 Stream;
  • kube-proxy 的负载均衡通常发生在新建连接时,而不是每个 RPC 开始时;
  • 一条已经建立的连接会继续流向当初选中的 Pod;
  • Java 客户端即使配置了 round_robin,如果名称解析器只交给它一个 ClusterIP,它也只有一个地址可选。

所以,round_robin + ClusterIP 并不会自动变成 Pod 级轮询。它最多在“一个虚拟 IP”这一层轮询,真正的 Pod 选择仍留给连接级转发。

这不是 ClusterIP 的缺陷

ClusterIP 非常适合普通 HTTP、短连接、由 Sidecar 负责端点发现,或不要求客户端直接感知 Pod 的场景。这里的问题是:我们希望 gRPC 客户端或 Envoy 自己执行端点级负载均衡,却只给了它一个 VIP。


二、目标架构:保留 ClusterIP,增加 gRPC 专用 Headless Service

不要为了 gRPC 直接把原 Service 改成 Headless。更稳妥的方式是保留兼容入口,再新增一个内部发现入口:

Headless Service 没有 ClusterIP,也不依赖 kube-proxy 提供虚拟 IP 转发。带 selector 的 Headless Service 会生成 EndpointSlice,集群 DNS 会把 Service 名称解析为后端 Pod IP。客户端因此能把每个 Pod 视为独立地址。

这个架构有三个好处:

  • 原有 HTTP、运维脚本和未迁移调用方继续使用 service-a
  • Java gRPC 客户端可以对多个地址执行 round_robin
  • Envoy STRICT_DNS 可以把每个 DNS 地址建模为独立上游并管理连接池。

2.1 Headless Service 清单

service-a-grpc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: service-a-grpc-headless
namespace: application
labels:
app.kubernetes.io/managed-by: Helm
spec:
clusterIP: None
publishNotReadyAddresses: false
selector:
app: service-a
ports:
- name: grpc
port: 8080
targetPort: 8080
protocol: TCP

这里最重要的不是 clusterIP: None,而是以下三个条件必须同时成立:

  1. selector 必须与 Deployment 的 Pod label 完全一致;
  2. targetPort 必须命中真实监听 gRPC 的 8080
  3. publishNotReadyAddresses: false 必须与正确的 gRPC readinessProbe 配合。

如果 selector 漂移,Service 会存在、DNS 名也可能存在,但 EndpointSlice 没有可用地址。如果 readinessProbe 仍检查 HTTP 8200,则“HTTP 已就绪、gRPC 端口不可服务”的 Pod 仍可能进入 Headless DNS。

2.2 必须进入 Helm,而不是只在控制台创建

假设当前资源由 Helm release application-stack 管理,Headless Service 也应成为 Chart 的声明式资源。控制台手工创建的问题不只是“下次可能被覆盖”,还包括:

  • 测试、预发和生产环境无法复现;
  • helm template、代码审查和 Git 历史看不到它;
  • selector、端口或标签变化时不会同步演进;
  • 回滚 release 时资源状态与应用版本不一致。

可以把它做成可开关的 Chart 模板:

values.yaml
grpcHeadlessService:
enabled: true
name: service-a-grpc-headless
port: 8080
publishNotReadyAddresses: false
templates/service-grpc-headless.yaml
{{- if .Values.grpcHeadlessService.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.grpcHeadlessService.name }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
clusterIP: None
publishNotReadyAddresses: {{ .Values.grpcHeadlessService.publishNotReadyAddresses }}
selector:
app: service-a
ports:
- name: grpc
port: {{ .Values.grpcHeadlessService.port }}
targetPort: 8080
protocol: TCP
{{- end }}

实际 Chart 若已有 _helpers.tpl 和统一 selector helper,应复用同一 helper,不要复制一套可能漂移的标签。


三、Java 客户端:发现多个地址之后还要真正启用负载均衡

调用方配置改为:

application:
grpc:
enabled: true
governance:
enabled: true
load-balancing-policy: round_robin

grpc:
client:
service-a:
address: dns:///service-a-grpc-headless.application.svc.cluster.local:8080
negotiation-type: plaintext

dns:/// 的三个 / 不是笔误。它表达的是使用 DNS NameResolver,并把后面的 FQDN 作为 target path。解析结果应是一组 Pod IP,而不是一个 ClusterIP。

仅修改 YAML 仍不足以证明生效,还要核实以下调用链:

application.yml
→ Spring ConfigurationProperties / starter 配置绑定
→ ManagedChannelBuilder.forTarget(...)
→ defaultLoadBalancingPolicy("round_robin") 或 Service Config
→ 长生命周期 ManagedChannel
→ 复用 Stub 发起 RPC

生产中最常见的两个“配置看起来正确但没有效果”问题是:

  • 自定义治理属性没有真正传给 ManagedChannelBuilder,客户端仍使用默认策略;
  • 每次请求都创建并关闭 Channel,既失去连接复用,也制造握手、DNS 和线程资源开销。

Channel 应按目标服务长生命周期复用。Stub 通常是轻量、线程安全的调用入口,可以在调用时派生 deadline 等选项,不需要每个请求重建 Channel。

3.1 round_robin 不等于每个业务请求绝对平均

round_robin 以可用 Subchannel 为基础选择后端,但以下因素会让短窗口内请求数不完全相等:

  • Pod 加入和退出的时间不同;
  • RPC 时长、流式调用和并发度不同;
  • 某些地址尚未进入 READY;
  • 客户端实例数量和各自连接状态不同;
  • 重试可能把一次业务请求变成多次 attempt。

验收目标应该是“无单 Pod 长期独占且负载与容量合理”,而不是强求每个 Pod 的计数精确相等。


四、Envoy:必须同时配置 STRICT_DNS、HTTP/2 与端点级健康治理

Helm values 可以这样表达:

grpcBackend:
host: service-a-grpc-headless.application.svc.cluster.local
port: 8080
dnsRefreshRate: 5s
lbPolicy: LEAST_REQUEST

但这些只是业务 Chart 的字段名。最终渲染出的 Envoy cluster 必须真正包含对应语义:

clusters:
- name: service_a_grpc
type: STRICT_DNS
connect_timeout: 1s
dns_refresh_rate: 5s
lb_policy: LEAST_REQUEST
load_assignment:
cluster_name: service_a_grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: service-a-grpc-headless.application.svc.cluster.local
port_value: 8080
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
health_checks:
- timeout: 1s
interval: 5s
unhealthy_threshold: 2
healthy_threshold: 2
grpc_health_check:
service_name: ""
outlier_detection:
consecutive_5xx: 5
interval: 10s
base_ejection_time: 30s
max_ejection_percent: 50

三个容易遗漏的点:

  1. 只有 host 改成 Headless DNS,但 cluster 仍是 STATICLOGICAL_DNS,不会得到期望的多端点模型;
  2. 上游没有显式启用 HTTP/2,gRPC 转发会失败或协议不匹配;
  3. 只依赖 DNS 移除而没有主动健康检查和异常实例摘除,故障收敛速度完全受 readiness、EndpointSlice 和 DNS 刷新链路影响。

STRICT_DNS 每次解析都会把返回的每个 IP 作为独立上游。IP 从解析结果消失后,Envoy 会认为该 host 已被移除,并开始排空关联连接池。dns_refresh_rate: 5s 表示最终一致性窗口,不代表故障发生后 5 秒内所有存量 Stream 都必然消失。

示例里的 consecutive_5xx 主要覆盖 HTTP/传输层 5xx,不能把所有非 OK 的 gRPC status 都等同为 HTTP 5xx。业务级 gRPC 失败仍要通过 gRPC status 指标、主动 Health Check 和必要的重试/异常摘除策略单独治理。

LEAST_REQUEST 适合 RPC 耗时差异较大的服务,因为它倾向选择当前活动请求更少的实例。对执行时间高度一致的一元 RPC,ROUND_ROBIN 也可能足够。策略选择要结合压测,而不是看到“最少请求”就默认更优。


五、健康检查:检查真实 gRPC 能力,而不是“另一个端口还活着”

示例服务的 gRPC 端口是 8080,HTTP 端口是 8200。如果 Pod readinessProbe 只检查 HTTP 8200,会出现这种错误状态:

HTTP 8200 Ready
gRPC 8080 未监听 / Health NOT_SERVING
Pod Ready = True
EndpointSlice ready = True
Headless DNS 返回这个 Pod IP
Java / Envoy 把 RPC 发给坏端点

服务端应实现标准 grpc.health.v1.Health 协议,并让 Kubernetes 直接探测 gRPC 8080。Kubernetes 原生 gRPC Probe 从 v1.27 起进入稳定状态:

containers:
- name: service-a
ports:
- name: grpc
containerPort: 8080
- name: http
containerPort: 8200

startupProbe:
grpc:
port: 8080
service: ""
periodSeconds: 2
failureThreshold: 60
timeoutSeconds: 1

readinessProbe:
grpc:
port: 8080
service: ""
periodSeconds: 5
failureThreshold: 2
timeoutSeconds: 1

livenessProbe:
grpc:
port: 8080
service: ""
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 1

探针语义应有所区分:

探针回答的问题失败结果设计原则
startupgRPC Server 是否已完成启动启动期继续等待,超限后重启覆盖最慢启动和预热时间
readiness现在是否应接收新 RPC从 Service Endpoint 摘除可以包含关键依赖与业务预热状态
liveness进程是否已不可恢复重启容器不要因短暂下游故障制造重启风暴

原生 gRPC Probe 使用数值端口,不能引用命名端口。旧版本集群可以使用 grpc-health-probe exec 探针或至少使用 tcpSocket: 8080 过渡,但 TCP 成功只能证明端口可建立连接,不能证明 gRPC 服务处于 SERVING

对于只提供 TLS 的 gRPC 服务,还要先确认集群版本和 gRPC Probe TLS 能力;不能默认明文原生探针一定适用。本文示例中的集群内调用明确配置为 plaintext,因此探针与服务协议一致。


六、发布与下线:从“摘出 DNS”到“排空在途 RPC”

readiness 失败只会阻止新流量继续进入,并不会瞬间终止已经建立的 HTTP/2 Stream。正确的优雅下线顺序是:

Java 服务端应在关闭钩子中执行有上限的 graceful shutdown:

healthStatusManager.enterTerminalState();
server.shutdown();

if (!server.awaitTermination(25, TimeUnit.SECONDS)) {
server.shutdownNow();
}

Deployment 同时要提供足够的终止窗口:

spec:
template:
spec:
terminationGracePeriodSeconds: 40

如果应用无法在收到 SIGTERM 时先切换 readiness,可使用短 preStop 作为传播缓冲,但固定 sleep 只是兜底,不是完整的连接排空机制。真正的完成条件应是:停止接收新请求、等待在途 RPC,并在预算耗尽后强制关闭。

滚动发布还要配置合理的 maxUnavailablemaxSurge 和 PodDisruptionBudget,避免 DNS 中同时失去过多 Ready 地址。


七、Deadline、重试与 Wait-for-Ready:必须共同受预算约束

gRPC 默认不会替你设置业务 deadline。没有 deadline 的调用可能长期占用 Stream、线程、内存和下游连接。

ServiceReply reply = serviceAStub
.withDeadlineAfter(2, TimeUnit.SECONDS)
.execute(request);

deadline 应来自业务 SLO 和压测结果,并沿调用链传播。服务端检测到取消后,还要主动停止自己启动的数据库查询、外部请求或计算任务;仅仅让客户端超时,不会自动回收所有业务工作。

重试必须同时满足:

  • 方法具备幂等性,或使用幂等键、任务 ID、唯一约束去重;
  • 仅重试明确的瞬时状态,例如部分场景下的 UNAVAILABLE
  • 单次 attempt、退避和总 deadline 有清晰预算;
  • Java 客户端、Envoy 和业务代码只保留一个主要重试层,避免倍增;
  • 监控区分 logical RPC 与 attempt 数量。

如果 Java 最多 3 次、Envoy 再最多 3 次,一次业务调用在最坏情况下可能变成 9 次上游 attempt。对创建任务、扣费、文件写入等非幂等操作,这既是容量风险,也是数据一致性风险。

wait-for-ready 适合可以等待短暂恢复的批处理或后台任务,但必须配合 deadline。在线请求通常更需要快速失败,把时间留给降级或上游返回。


八、Keepalive、连接与背压:不要用 Ping“修复”负载均衡

Keepalive 的作用是发现失效连接、穿越会回收空闲连接的中间网络设备,不是让请求更平均。过于激进的 HTTP/2 PING 会浪费 CPU 和网络资源,还可能被服务端判定为滥用并发送 GOAWAY

上线前应统一客户端、Envoy、服务端和云负载设备的连接策略:

  • 空闲连接多久会被关闭;
  • 客户端多久发送 Keepalive;
  • 服务端允许的最小 Ping 间隔;
  • 最大连接年龄和优雅关闭时间;
  • HTTP/2 每连接最大并发 Stream;
  • 最大入站消息大小和元数据大小。

对于可能携带较大文件或执行长耗时任务的服务,更推荐 gRPC 只传任务元数据、对象存储地址和状态,不要把无限大小文件直接塞进单个 RPC。入口必须限制消息大小、并发数和业务队列长度。

Java 服务端还要把阻塞 I/O 与网络处理解耦,并使用有界业务线程池或并发许可。无限线程、无限队列和无限 deadline 叠加,是比负载不均更危险的事故组合。


九、安全:plaintext 是部署选择,不是默认安全结论

示例使用:

negotiation-type: plaintext

它只适用于经过威胁建模后确认可信的集群内网络。生产环境至少要明确:

  • Namespace 与 NetworkPolicy 是否限制非预期调用方;
  • 服务端是否校验 JWT、租户、用户和调用方身份;
  • Envoy 是否完整转发 authorizationx-tenant-id、trace context 等元数据;
  • 日志和指标是否避免记录敏感文档、Token 和原始 PII;
  • 跨集群、跨 VPC 或高敏数据链路是否应启用 TLS/mTLS;
  • 证书轮换是否会触发连接重建,并经过故障演练。

Headless Service 让调用方直接连接 Pod IP,也意味着安全策略不能只保护 ClusterIP 入口。NetworkPolicy、服务端鉴权和 mTLS 应覆盖 Pod 级直连路径。


十、可观测性:不要只看成功率,要看每个端点的行为

生产指标至少覆盖四层:

层级关键观测项要回答的问题
KubernetesReady Pod 数、EndpointSlice 地址数、Pod 重启、终止状态发现集合是否正确
DNS / Resolver解析成功率、返回地址数、刷新失败、Channel connectivity state客户端是否持续看到端点变化
gRPClogical RPC、attempt、status code、deadline exceeded、active streams、消息大小调用是否健康、重试是否放大
Envoy / Podhealthy host、upstream connect failure、active requests、P50/P95/P99、按 Pod QPS/CPU流量是否合理分布

建议日志字段:

trace_id
rpc.service
rpc.method
grpc.status_code
deadline_ms
attempt
peer.address
upstream_host
duration_ms
request_size
response_size

不要把 user_id、文件名或任务 ID 直接作为 Prometheus label,否则会制造高基数。它们可以进入受控日志或 Trace 属性。

10.1 推荐告警

  • Ready Endpoint 数低于期望副本数;
  • 5 分钟窗口内 UNAVAILABLEDEADLINE_EXCEEDED 明显上升;
  • attempt / logical RPC 比例持续升高;
  • 单 Pod QPS、active streams 或 CPU 长期显著偏离同组实例;
  • Envoy DNS 解析失败或 healthy host 归零;
  • graceful shutdown 超时、强制关闭次数增加;
  • gRPC 消息大小接近限制或服务端拒绝率上升。

十一、生产发布顺序与验收命令

建议按以下顺序发布,任何一步异常都可以切回原 ClusterIP 地址:

  1. 服务端先实现标准 gRPC Health 和优雅停机;
  2. 将正确的 gRPC startup/readiness/liveness Probe 发布到所有 Pod;
  3. application-stack Chart 中加入 Headless Service;
  4. 用 Helm 渲染和 lint 验证资源,再升级测试环境;
  5. 验证 EndpointSlice 与 DNS 确实返回所有 Ready Pod IP;
  6. 先灰度 Envoy 或少量 Java 调用方;
  7. 验证 RPC 分布、错误率、P99 和重试放大;
  8. 执行 Pod 删除、滚动发布、单 Pod gRPC 故障和 DNS 短暂失败演练;
  9. 扩大到全部调用方,并保留原 ClusterIP 作为回滚路径。

11.1 Helm 静态验证

helm lint ./application-stack

helm template application-stack ./application-stack \
--namespace application \
-f values-production.yaml \
| grep -A30 'name: service-a-grpc-headless'

确认渲染结果而不是 values 文件本身。对 Envoy 同样要检查最终 ConfigMap 或启动配置中确实出现 STRICT_DNS、Headless FQDN、HTTP/2 和预期的 LB policy。

11.2 EndpointSlice

kubectl -n application get endpointslice \
-l kubernetes.io/service-name=service-a-grpc-headless \
-o wide

kubectl -n application get endpointslice \
-l kubernetes.io/service-name=service-a-grpc-headless \
-o yaml

应看到多个地址,且 conditions.ready: true。地址数量应与 Ready 且被 selector 命中的 Pod 数一致。

11.3 集群内 DNS

kubectl -n application exec <caller-or-envoy-pod> -- \
nslookup service-a-grpc-headless.application.svc.cluster.local

DNS 应返回多个 Pod IP,而不是 10.96.120.20。如果镜像带 dig,可进一步查看:

kubectl -n application exec <caller-or-envoy-pod> -- \
dig +short service-a-grpc-headless.application.svc.cluster.local A

11.4 gRPC Health

grpcurl -plaintext \
-d '{}' \
service-a-grpc-headless.application.svc.cluster.local:8080 \
grpc.health.v1.Health/Check

再逐个 Pod IP 验证,确保不是只有其中一个 Pod 能返回 SERVING

11.5 真实负载分布

持续发送足够长时间、足够并发的 RPC,然后按 peer.address、Envoy upstream_host 或服务端 Pod label 聚合请求数、active streams、耗时和错误率。

nslookup 只能证明发现结果,不能证明 Java 的 round_robin 已生效;EndpointSlice 正确也不能证明 Envoy 最终配置已加载。必须用真实 RPC 分布完成最后一跳验收。

11.6 故障演练

至少完成以下测试:

  • 删除一个正在接收请求的 Pod,观察 EndpointSlice、DNS、Envoy 和 Java Channel 的收敛;
  • 让某个 Pod 的 gRPC Health 返回 NOT_SERVING,但保持进程和 HTTP 8200 存活;
  • 连续滚动发布,确认在途 RPC 不出现集中失败;
  • 暂时阻断一个 Pod 的 8080,验证主动健康检查和异常摘除;
  • 注入慢响应,验证 deadline、并发限制和重试预算;
  • 恢复 Pod,确认它能重新加入发现集合并平稳承接流量。

十二、这次 gRPC 生产落地踩过的坑

表象根因正确处理
把 ClusterIP 当成 Pod 发现DNS 正常、调用也成功,但单 Pod 很热DNS 只返回 VIPgRPC 专用 Headless Service
配了 round_robin 仍不均客户端日志显示策略已配置Resolver 只有一个 ClusterIP 地址dns:///headless-fqdn:8080
只手工创建 Service当天有效,后续环境不一致资源不在 Helm/Git 声明中加入 application-stack Chart
Headless 有名无端点Service 存在但解析不到 IPselector 与 Pod label 不匹配对照 Deployment label 与 EndpointSlice
错误发布 NotReady 地址启动中或故障 Pod 仍收到 RPCpublishNotReadyAddresses: true保持 false,并修正 readiness
readiness 检查错端口HTTP 正常但 gRPC 大量失败只探测 8200标准 gRPC Health 探测 8080
只改 Envoy hostDNS 有多个 IP,Envoy 仍只用一个cluster 类型不是 STRICT_DNS检查最终渲染配置
Envoy 上游不是 HTTP/2连接或协议错误未声明上游 HTTP/2配置 HTTP/2 protocol options
每次请求新建 ChannelCPU、线程和连接数上升把 Channel 当成短生命周期客户端复用长生命周期 Channel
用 Keepalive 修复倾斜Ping 增多,分布仍不稳定Keepalive 不负责 RPC 负载均衡修复服务发现与 LB policy
没有 deadline故障时请求和线程长期堆积gRPC 默认不提供业务超时按方法设置和传播 deadline
多层同时重试下游故障时流量反而暴涨Java、Envoy、业务代码叠加重试选定主重试层并设总预算
非幂等写被自动重试重复任务、重复扣费或重复写入未区分方法语义幂等键、唯一约束,默认不重试写
readiness 一摘就退出滚动发布仍丢在途 RPCEndpoint 移除不等于 Stream 已完成gRPC graceful shutdown + 终止预算
liveness 检查所有依赖下游故障引发本服务重启风暴把“暂不可接流量”当成“进程已死”依赖故障进 readiness,liveness 保守
plaintext 被当成理所当然Pod 直连绕过边界保护没有威胁建模NetworkPolicy、鉴权,必要时 mTLS
只用 nslookup 验收DNS 多 IP,但流量仍倾斜配置绑定或 LB policy 未生效观察真实 RPC 的 upstream_host 分布
追求绝对平均短窗口内计数不同就判定失败请求时长、健康和连接状态不同观察长期容量、延迟与热点,而非机械均分

十三、最终生产基线

一套可交付的 gRPC 生产基线应至少包含:

  • 服务发现:Headless Service、正确 selector、Ready EndpointSlice;
  • 客户端治理:DNS target、Pod 级 LB、长生命周期 Channel;
  • 代理治理:Envoy STRICT_DNS、HTTP/2、LB、健康检查、异常摘除;
  • 健康模型:标准 gRPC Health、startup/readiness/liveness 分工;
  • 生命周期:先摘流、再停止新请求、最后排空在途 RPC;
  • 韧性:deadline、单层受预算重试、幂等、背压和有界资源;
  • 安全:身份与租户元数据、NetworkPolicy、TLS/mTLS 决策;
  • 可观测性:状态码、attempt、端点分布、活动 Stream、P99 与 DNS/连接状态;
  • 交付方式:Helm/Git 管理、分阶段灰度、可回滚、故障演练;
  • 验收证据:EndpointSlice、DNS、Health、真实 RPC 分布和 Pod 下线收敛。

真正的生产落地不是“把 Service 改成 Headless”这一行 YAML,而是让 发现、选择、健康、摘流、排空、重试和观测使用同一套端点事实。只要其中一个环节仍停留在 ClusterIP、错误端口或未生效的配置上,流量倾斜和发布抖动就可能再次出现。


参考资料

Logo
RainLib

探索技术、设计与分布式系统的边界。构建面向未来的开发者工具。

留言与建议

© 2026 RainLib. 为未来构建。(Built for the Future)
版权所有。
系统正常