首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我把副本数改成了 3,然后学会了什么叫"假的可用"

我把副本数改成了 3,然后学会了什么叫"假的可用"

原创
作者头像
资源shanxueit.com
发布于 2026-11-01 13:59:26
发布于 2026-11-01 13:59:26
180
举报

去年我把一个内部服务迁上 K8s,照着教程写了 Deployment,把 replicas 从 1 改成 3,觉得这就算高可用了。三份实例,挂一份还有两份,天塌不下来。

上线第二周,一次滚动发布,服务整整不可用了四分钟。三个副本全在 CrashLoopBackOff 里排队重启,用户那边看到的是 502。我盯着 kubectl get pods 想不通:我明明有三个副本。

后来我才明白,K8s 给你的是"能同时跑三个容器"的能力,不是"你的应用高可用"的事实。这两件事之间,隔着整个应用架构。

一、副本数管的是数量,不是依赖

我那个服务的问题出在一个没人在意的单点上:三个副本共用一个后台定时任务,任务启动时会去抢同一把数据库锁,抢不到的那个进程直接退出。平时看不出问题,发布时三个副本同时启动、同时抢锁、两个退出——replicas: 3 实际只有一个健康。

K8s 的副本控制器只保证"有三个 Pod 在跑",它不知道你的进程为什么退出、依赖谁、能不能并行。它管的是编排,不管你的应用是不是真的能横着扩。

一个能横着扩的应用,至少要满足:无本地状态、无单点依赖、启动幂等、可被随时杀掉。这四条没有一条写在 YAML 里,全在你的代码里。

二、probe 是 K8s 最容易写错、也最贵的一行

迁移时我抄了一份"标准"配置:

代码语言:javascript
复制
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10

/healthz 里我顺手让它查了数据库连接。看着很负责任,其实是灾难的开始。

有一次数据库主库切换,抖动了两分钟。所有副本的 /healthz 都返回失败,livenessProbe 判定"应用死了",把三个容器全部重启。重启中的容器连不上数据库,起来又失败,又被杀——数据库抖动两分钟,被我放大成了服务全挂十分钟。

正确的分工是:liveness 只回答"我这个进程还要不要活着",readiness 才回答"我现在能不能接流量"。

代码语言:javascript
复制
livenessProbe:
  httpGet: { path: /healthz, port: 8080 }   # 只检查进程自身
readinessProbe:
  httpGet: { path: /readyz, port: 8080 }    # 检查依赖,失败就从 Service 摘掉

区别在于失败后的动作:liveness 失败会杀容器,readiness 失败只是摘流量。依赖抖动时你想要的是后者——别杀我,先别给我派活,等依赖好了我自己回来。把这两者混为一谈,是把"降级"升级成了"雪崩"。

三、不写 requests,调度器就是在猜

resources 那一段,很多人(包括当时的我)嫌麻烦就删了。删掉的后果不是"资源随便用",是调度器失去依据。

K8s 调度一个 Pod 时看的是 requests:这个 Pod 声明要多少 CPU、多少内存,调度器据此找一台"放得下"的节点。不写 requests,调度器默认你要 0,于是它把所有 Pod 都塞进同一台机器,那台机器上的应用互相抢 CPU,谁慢谁快全看运气。

代码语言:javascript
复制
resources:
  requests: { cpu: 200m, memory: 256Mi }   # 调度依据,也是 QoS 依据
  limits:   { memory: 512Mi }              # 防止单 Pod 吃爆节点

还有一层:requests 和 limits 的比值决定了 Pod 的 QoS 等级。内存 requests == limits 的 Pod 是 Guaranteed,节点内存紧张时最后被杀;什么都没写的是 BestEffort,第一个被 OOM 清退。你写的不是数字,是节点资源紧张时谁先死的顺序。

四、配置、状态、代码,三样要分开

迁完之后我复盘,发现所有事故都指向同一件事:把"应用的哪部分该由 K8s 管"想错了。

  • 配置不该打进镜像。同一份镜像,测试环境和生产环境只差一个 ConfigMap / Secret,这是 K8s 架构的基本盘。镜像里写死数据库地址,等于每个环境重新构建一次。
  • 状态不该放在容器本地。容器随时会被杀、会被调度到别的节点,本地磁盘上的东西说没就没。有状态的东西要么进 PVC,要么进外部存储,要么就别叫它无状态服务。
  • 代码只该管业务。重试、熔断、超时、优雅退出——这些在单体时代靠框架,在 K8s 里靠你对"进程随时会死"的假设。收到 SIGTERM 之后还在处理请求的进程,会在滚动发布时给用户返回连接重置。

这三条分开之后,replicas 才真正开始有意义。因为你终于保证了三份实例之间没有共享的可变状态、没有互相争抢的单点、启动不依赖"上一次运行的残留"。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、副本数管的是数量,不是依赖
  • 二、probe 是 K8s 最容易写错、也最贵的一行
  • 三、不写 requests,调度器就是在猜
  • 四、配置、状态、代码,三样要分开
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档