本文中的服务名、命名空间和地址均为虚构示例。事故期间应按自己的变更审批流程操作。

一次微服务故障里,日志可能同时出现 Load balancer does not have available server、connect timed out、Nacos 重连和 Redis 连接失败。它们不必只有一个根因,更不代表应该同时重启 Nacos、Redis、业务 Pod 与 kube-proxy。

正确顺序是先确定故障发生在哪一跳。

flowchart TD
  A[业务请求失败] --> B{DNS 能解析 Service 吗?}
  B -- 否 --> C[检查 Pod DNS 与 CoreDNS]
  B -- 是 --> D{Endpoint 有 Ready 后端吗?}
  D -- 否 --> E[检查目标 Pod、selector、readiness]
  D -- 是 --> F{Pod 内可连接 Service 吗?}
  F -- 否 --> G[检查 kube-proxy、CNI、NetworkPolicy 与节点路径]
  F -- 是 --> H{客户端仍依赖 Nacos 实例吗?}
  H -- 是 --> I[检查注册、namespace、group 与实例健康]
  H -- 否 --> J[检查应用超时、连接池和下游延迟]

先把 Service 证据收齐

以 payment-api 为例:

kubectl -n <ns> get svc payment-api
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=payment-api
kubectl -n <ns> get pod -l app=payment-api -o wide
kubectl -n <ns> exec <caller-pod> -- \
  sh -c 'getent hosts payment-api.<ns>.svc.cluster.local'

DNS 解析正确但 Endpoint 为空,优先检查 selector、readiness 和目标 Pod;不应先改 Nacos 配置。Endpoint 正常但仅某些节点连接超时,才把范围收敛到 Service 转发、CNI 或节点网络。

把同一次取证的 YAML、事件和调用方连通性一起保存,才能判断问题是全局还是单节点:

export NS=<namespace>
export SVC=payment-api
export CALLER=<caller-pod>
export PORT=8080

kubectl -n "$NS" get svc "$SVC" -o yaml > /tmp/$SVC-service.yaml
kubectl -n "$NS" get endpointslice \
  -l kubernetes.io/service-name="$SVC" -o yaml > /tmp/$SVC-endpoints.yaml
kubectl -n "$NS" get events --sort-by=.lastTimestamp | tail -50

# DNS、TCP、HTTP 必须分开判断。镜像没有 nc 时可用临时调试容器。
kubectl -n "$NS" exec "$CALLER" -- getent hosts "$SVC.$NS.svc.cluster.local"
kubectl -n "$NS" exec "$CALLER" -- sh -ec \
  "timeout 3 sh -c '</dev/tcp/$SVC.$NS.svc.cluster.local/$PORT'"
kubectl -n "$NS" exec "$CALLER" -- \
  curl -fsS --connect-timeout 3 "http://$SVC.$NS.svc.cluster.local:$PORT/actuator/health"

若 CALLER 没有 shell 或 curl,不要为了排障改业务镜像。使用与业务 Pod 同命名空间、相同 ServiceAccount 的临时调试 Pod,完成后删除:

kubectl -n "$NS" run network-debug --rm -it --restart=Never \
  --image=<approved-debug-image> -- sh
# 在容器内依次运行 getent、nc -vz 和 curl;退出后 --rm 自动清理。

固定地址应该迁到 Service DNS

业务配置中不应长期保存 ClusterIP。它会因重新部署、Service 重建或环境迁移而变化。对于明确的内部 HTTP 依赖,优先使用:

payment.api.url=http://payment-api.platform.svc.cluster.local:8080
spring.data.redis.host=redis-service.platform.svc.cluster.local

这解决的是“地址稳定性”,并不能自动修复 Nacos 注册失败或客户端负载均衡空列表。两条调用链必须分别验证。

Nacos、Redis 和节点网络分别怎样验证

Load balancer does not have available server 是客户端没有获得可用实例的结果,不是 Nacos 本身必然故障。先确认应用连接的 Nacos namespace、group、serviceName 与实际注册信息一致。推荐临时本地转发管理接口,避免把管理端口长期暴露到集群外。

export NACOS_NS=<nacos-namespace>
kubectl -n "$NACOS_NS" port-forward svc/<nacos-service> 8848:8848

# 在另一个终端执行;认证参数从受控 Secret 注入,绝不写入 shell history。
curl -fsSG 'http://127.0.0.1:8848/nacos/v1/ns/instance/list' \
  --data-urlencode 'serviceName=payment-api' \
  --data-urlencode 'namespaceId=<namespace-id>'

Redis 问题同样先区分“Redis 不健康”与“到 Redis Service 的路径不通”。密码用环境变量或 Secret 文件传递,避免粘贴到终端和日志:

export REDIS_NS=<redis-namespace>
export REDIS_POD=<redis-pod>
kubectl -n "$REDIS_NS" exec "$REDIS_POD" -- redis-cli --no-auth-warning PING
kubectl -n "$NS" exec "$CALLER" -- \
  sh -ec "timeout 3 sh -c '</dev/tcp/redis-service.$REDIS_NS.svc.cluster.local/6379'"

只在“Endpoint 正常、调用方 DNS 正常、但故障稳定集中在同一节点”时检查 kube-proxy/CNI。先看版本、节点分布和日志,再决定是否做受控重建:

kubectl -n kube-system get pod -l k8s-app=kube-proxy -o wide
kubectl -n kube-system logs <kube-proxy-pod-on-affected-node> --tail=200
kubectl -n kube-system get pod -l k8s-app=calico-node -o wide

不要因为日志中同时出现 Feign、Nacos、Redis 就批量重启全部组件;那会抹掉事件证据,并扩大短暂网络抖动。

事故中的最小变更原则

每次只改变一个已经被证据指向的层,并在变更后复测原始请求:

  1. 保存 previous 日志、Events、Endpoint 与节点分布;
  2. 修复一个明确故障,例如无 Ready Endpoint 或单节点 kube-proxy 规则未同步;
  3. 从调用方 Pod 复测 DNS、TCP 和 HTTP;
  4. 最后才滚动业务服务或修改 Nacos。

这样即使 Nacos、Redis 和 Feign 同时出现在日志里,也不会把可恢复的小故障扩展成全环境重启。

每一步都要有可验证的回退

配置改动前先导出原 ConfigMap、Nacos 配置或 Deployment 修订号;改动后立即重放原始请求。若修改未改善对应证据,回退该单项而不是叠加下一项:

kubectl -n "$NS" get deploy <caller-deployment> -o yaml > /tmp/caller.before.yaml
kubectl -n "$NS" rollout history deploy/<caller-deployment>

# 仅在本次变更确实由 Deployment 触发时回退。
kubectl -n "$NS" rollout undo deploy/<caller-deployment>
kubectl -n "$NS" rollout status deploy/<caller-deployment> --timeout=5m

事故记录中应保留原始错误、Endpoint 快照、调用方复测结果、变更对象和回退条件。这些证据比“重启后好了”更能防止同类问题再次发生。