Kubernetes 大规模集群的规模上限、SLO 与瓶颈定位
scalability 是 Kubernetes 的规模话题:集群能装多少节点、多少 Pod、多少对象,官方承诺什么样的响应延迟,以及最先撑不住的通常是哪个组件。
Kubernetes 的规模承诺建立在"你保证、我保证"的框架上 —— 官方在 Kubernetes 社区仓库里写得很直白:如果你正确配置集群、合理使用扩展能力、把负载控制在推荐阈值内,那么我们保证 SLO 达标。反过来,超阈值不意味着集群立刻挂掉,而是"性能开始劣化、SLO 不再有保证"。
这份文档的数值来自 SIG-Scalability 的官方文件(sig-scalability/configs-and-limits/thresholds.md 与 sig-scalability/slos/slos.md),不是经验值。
Kubernetes 设计上支持同时满足以下全部条件的集群:
| 维度 | 上限 |
|---|---|
| 节点数 | 5,000 |
| Pod 总数 | 150,000 |
| 容器总数 | 300,000 |
| 单节点 Pod 数 | 110 |
单节点 Pod 数在社区阈值文件里的写法更细:min(110, 10 × 该节点核数) —— 也就是说 8 核节点建议不超过 80 个 Pod,而不是无脑顶到 110。
集群级阈值(超出后 SLO 不再保证),取自 sig-scalability/configs-and-limits/thresholds.md:
| 资源 | 命名空间级 | 集群级 |
|---|---|---|
| 节点 | — | 5,000 |
| 命名空间 | — | 10,000 |
| Pod | 3,000 | 150,000 |
| 单节点 Pod | — | min(110, 10 × 核数) |
| Service | 5,000 | 10,000 |
| 单个 Service 的 Endpoint | 250 | — |
| Deployment | 2,000 | — |
| ServiceAccount Token | 2,000 | 2,000 |
| Token 校验 | — | 5,000 QPS |
| 单类型对象数(不含 Event) | — | 150,000 |
| Event 对象数(按类型) | — | 1,000,000 |
| 单个对象大小 | 1.5MB | 1.5MB |
| 单类型对象总大小 | 1.5GB | — |
三条容易忽略的说明:
pods、secrets 各算各的)。| 指标 | 目标 |
|---|---|
| 变更类 API 调用(每 resource/verb 对) | 99 分位 ≤ 1s(按 cluster-day 统计) |
只读非流式 API 调用,scope=resource |
99 分位 ≤ 1s |
只读非流式 API 调用,scope=namespace/scope=cluster |
99 分位 ≤ 30s(大范围 list 本来就慢) |
| 无状态 Pod 启动延迟(创建到全部容器 started 并被 watch 观察到,不含拉镜像与 init 容器) | 99 分位 ≤ 5s |
| 集群 churn | ≤ 20 /秒 |
churn 的定义是:每秒 Pod spec 创建 + 更新 + 删除 的次数,加上用户发起的请求数。这是最容易被忽视的一条 —— 大量短命 Job、滚动更新风暴、或者一个不停 reconcile 的控制器,都能在节点数没变的情况下把 churn 打爆。
社区文档列出的环境要求,不满足就不必指望 SLO:
etcd-tuning);规模上不去时,通常按下面的顺序撞墙:
etcd_disk_backend_commit_duration_seconds 抬头、etcd_server_leader_changes_seen_total 增长,先查磁盘和 compaction,而不是加 apiserver。list 大对象时会进一步放大)。表现为 apiserver_request_duration_seconds 上升、请求排队。--kube-api-qps / --kube-api-burst 太小会让控制器明显滞后,表现为"Pod 删了但 ReplicaSet 半天不补"。percentageOfNodesToScore 影响;节点多了以后每个 Pod 都要遍历可行节点,调度延迟会上升。SLO 不是口号,是可以被算出来的。下面是最接近官方口径的查询方式:
# 1. 变更类请求的 99 分位(按 resource / verb 分组,看是谁在拖后腿)
histogram_quantile(0.99, sum by (le, resource, verb) (
rate(apiserver_request_duration_seconds_bucket{verb=~"POST|PUT|PATCH|DELETE"}[5m])))
# 2. 只读请求要按 scope 分开看:resource 级目标 1s,namespace/cluster 级目标 30s
histogram_quantile(0.99, sum by (le, resource, scope) (
rate(apiserver_request_duration_seconds_bucket{verb=~"GET|LIST"}[5m])))
# 3. 当前在途请求与长时间运行请求(watch、exec)的数量
apiserver_current_inflight_requests
apiserver_longrunning_requests
# 4. churn 的近似:每秒的 Pod spec 变更数 + 用户请求数
sum(rate(apiserver_request_total{resource="pods",verb=~"POST|PUT|PATCH|DELETE"}[1m]))
# 5. 对象数量要逐个资源类型统计 —— 这才是最容易超限的维度
for r in pods services secrets configmaps serviceaccounts pvcs; do
printf '%-16s %s\n' "$r" "$(kubectl get "$r" -A --no-headers 2>/dev/null | wc -l)"
done
# 6. CRD 也要单独数一遍(它们不受官方 SLO 保护,最容易失控)
kubectl get crd --no-headers | wc -l
kubectl get crd -o custom-columns=NAME:.metadata.name | tail -n +2 | while read c; do
printf '%-40s %s\n' "$c" "$(kubectl get "$c" -A --no-headers 2>/dev/null | wc -l)"
done
# 先看当前控制面负载,再决定是"加机器"还是"加副本"
kubectl top node -l node-role.kubernetes.io/control-plane
kubectl get --raw /metrics | grep -E 'apiserver_current_inflight_requests|process_resident_memory_bytes'
# 1. Event 分离到独立 etcd —— apiserver 侧的写法
--etcd-servers-overrides=/events#https://etcd-events-1:2379;https://etcd-events-2:2379
# 2. 给控制面组件更高的 QPS 预算(controller-manager / scheduler)
--kube-api-qps=100 --kube-api-burst=200
# 3. 让关键组件不被驱逐、优先调度
# CoreDNS、metrics-server 等:priorityClassName: system-cluster-critical
# 每节点守护进程:priorityClassName: system-node-critical
# 4. 检查当前实际规模
kubectl get nodes --no-headers | wc -l
kubectl get pods -A --no-headers | wc -l
kubectl get svc -A --no-headers | wc -l
kubectl get ns --no-headers | wc -l
kubectl get crd --no-headers | wc -l
# 5. 观察 apiserver 的实际负载与延迟
kubectl get --raw /metrics | grep -E 'apiserver_current_inflight_requests|apiserver_request_total'
kubectl get --raw /metrics | grep apiserver_request_duration_seconds_bucket | head
# 6. 观察 etcd 的大小与延迟
etcdctl endpoint status --write-out=table --cluster
etcdctl endpoint health --write-out=table --cluster
# 7. 检查是否有对象逼近 1.5MB 上限
kubectl get secrets -A -o json | jq '.items[] | select((.data|tostring|length) > 1000000) | .metadata.name'
# 8. 确认节点上的 Pod 数是否超过建议密度
kubectl get pods -A --field-selector spec.nodeName=<node> --no-headers | wc -l
secrets 类型就能吃掉几十万。上面给的检查命令值得定期跑。scope=namespace / scope=cluster 的只读请求 SLO 是 30 秒而不是 1 秒。全集群 list(比如没有加 --field-selector 的 kubectl get pods -A)慢是设计使然,不是 bug;要快就加过滤条件或改用 informer。--max-request-bytes 默认 1.5MiB 与 apiserver --max-request-bytes 默认 3MiB 的配套。放不进去的对象(常见于超大 ConfigMap、内嵌证书的 Secret、巨型 CR)会在写入时报 request too large,只能拆分。apiserver_request_duration_seconds、etcd_disk_backend_commit_duration_seconds、调度延迟与 Pod 启动延迟的 p99,这样才能在劣化发生时立刻指出是哪一层先动的。kubectl get pods -A 本身就是一次压测。排查问题时尽量加 -l、--field-selector 或指定命名空间;同理,少用不带过滤条件的全量 list 写脚本做巡检,那会把 apiserver 与 etcd 一起拖慢。kubectl — Kubernetes集群管理工具etcd — 集群数据存储kube-apiserver — 集群 API 服务器kube-scheduler — 调度器metrics-server — 集群资源指标cluster-autoscaler — 节点自动扩缩容watch-cache — apiserver 的 watch 缓存机制capacity-planning — 节点规格与容量规划