十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

容量规划实战:从压测评估到Kubernetes HPA自动扩容

容量规划实战:从压测评估到Kubernetes HPA自动扩容 你有没有遇到过这种情况线上服务平时跑得很稳一到业务高峰就告警CPU 持续飙高接口延迟翻了几倍甚至出现大量 5xx 响应。运维紧急扩容结果发现加机器也没用瓶颈根本不在应用实例数量上。这个场景背后往往不是某一次故障导致的而是容量规划长期缺位。最近看到一句话很有意思Almost nowhere in California is building enough。说的是某个区域的基础设施建设跟不上实际需求。放到软件系统里其实也一样很多系统并不是功能设计有问题而是容量构建没有跟上业务增长。一个服务能不能抗住流量不在于初始搭建时候选了多强的机器而在于有没有一套“容量评估 - 压测验证 - 弹性伸缩 - 持续观测”的闭环机制。本文就来完整拆解这套容量规划与弹性伸缩实战方案。我会从一个可运行的 FastAPI 示例应用开始讲解如何压测、如何根据指标判断容量瓶颈再结合 Kubernetes HPA 实现自动扩容最后给出实际项目中最容易踩的坑和工程建议。内容适合后端开发、运维和 SRE 同学收藏参考。1. 容量规划从“不够用”到“刚刚好”1.1 什么是容量规划容量规划简单来说就是根据业务流量预测、服务质量目标和资源上限合理规划系统所需的计算、存储、网络等资源。它不是单纯“买更多机器”而是一个持续动态的过程。在单体应用时代容量规划通常靠经验估算根据日活、订单量、峰值流量乘以一个冗余系数得到机器数量。但在微服务和容器化时代服务数量变多流量模型更复杂单纯靠估算已经很难准确。更常见的做法是通过压测得到单实例能力再结合业务流量预测计算副本数再通过自动伸缩机制应对突发流量。容量规划解决的核心问题有三个系统能承受多大的流量。当前配置下哪些资源最先成为瓶颈。流量增长后如何平滑地增加容量。1.2 容量不足的典型表现容量不足并不是突然发生的它通常有一些前期信号。我整理了几个最常见的表现表现可能原因影响CPU 使用率长期超过 80%单实例处理能力不足接口响应变慢请求排队内存占用持续上升频繁 GC内存配置不合理或泄漏应用卡顿甚至 OOM数据库连接数被打满应用实例数增加连接池配置未调整后端服务不可用依赖服务超时率升高下游服务容量不足导致上游堆积级联故障扩容后 QPS 没有明显提升瓶颈在数据库或第三方依赖应用层扩容无效如果你发现系统出现过以上情况就需要认真做一次容量评估了。1.3 容量规划需要关注的三个维度做容量规划不要只盯着 CPU。我建议至少关注以下三个维度资源维度CPU、内存、磁盘、网络带宽。这是最基础的容量指标。在 Kubernetes 中我们通常通过requests和limits来声明资源需求这也直接影响调度和伸缩。流量维度QPS每秒请求数、TPS每秒事务数、并发连接数、峰值因子。流量维度的核心是识别“峰值流量”和“均值流量”很多系统不是死于平均流量而是死于瞬间尖峰。依赖维度数据库连接数、缓存命中率、消息队列堆积量、第三方接口延迟。应用本身扩容很容易但下游依赖是否具备同等扩容能力往往是容量规划中最容易被忽略的部分。2. 实验环境与项目结构2.1 环境依赖为了完整走一遍容量评估和弹性伸缩流程我们需要准备以下环境Docker用于构建示例应用镜像。Kubernetes 集群生产环境可以使用云厂商托管集群本地实验推荐 Minikube 或 Kind。kubectl 命令行工具用于管理集群资源。k6用于做压力测试支持压测脚本和结果指标输出。Python 3.10用于运行 FastAPI 示例应用。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你使用的 Kubernetes 版本较低HPA 的 API 版本可能会有差异在复制配置前请先确认集群版本。2.2 项目目录结构我们先创建一个实验目录目录结构如下capacity-demo/ ├── app.py ├── requirements.txt ├── Dockerfile ├── k8s/ │ ├── deployment.yaml │ ├── service.yaml │ └── hpa.yaml └── load-test.js后面所有代码都围绕这套结构展开你可以直接复制到本地按步骤执行。2.3 示例应用简介示例应用是一个简单的 FastAPI 服务提供两个接口/api/hello快速返回一段 JSON用于模拟普通接口。/api/compute执行一个 CPU 密集型计算任务用于模拟高负载场景。这两个接口可以在同一份压测脚本中分别观察不同行为的资源消耗。为了方便压测这里不再加入数据库和缓存等外部依赖避免干扰容量测试结果。3. 写一个可压测的示例应用3.1 FastAPI 示例代码创建app.py内容如下# 文件路径capacity-demo/app.py from fastapi import FastAPI import time import random app FastAPI() app.get(/health) def health(): return {status: ok} app.get(/api/hello) def hello(): return {message: hello world} app.get(/api/compute) def compute(): 模拟一个 CPU 密集型任务。 实际业务中这类接口可能是加密计算、复杂报表、图片处理等。 start time.time() total 0.0 for i in range(2_000_000): total random.random() * i cost time.time() - start return { cost_seconds: round(cost, 3), total: round(total, 2), }/api/compute的循环次数可以根据机器性能调整。如果你的机器性能较强可以适当提高循环次数让接口在压测时能产生稳定的 CPU 开销。创建依赖文件requirements.txtfastapi uvicorn3.2 Dockerfile 与镜像构建创建Dockerfile# 文件路径capacity-demo/Dockerfile FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py . EXPOSE 8000 CMD [uvicorn, app:app, --host, 0.0.0.0, --port, 8000]构建镜像并打上标签docker build -t demo-app:latest .构建完成后可以先用docker run本地启动docker run -d -p 8000:8000 --name demo-app demo-app:latest访问http://localhost:8000/health如果返回{status:ok}说明应用启动正常。实验结束后可以删除临时容器docker rm -f demo-app3.3 本地启动验证如果不想通过 Docker也可以直接在本地用 uvicorn 启动pip install -r requirements.txt uvicorn app:app --host 0.0.0.0 --port 8000访问两个接口分别观察返回时间和资源消耗。这里的目的是确认服务可以运行为后续压测做准备。4. 使用 k6 做容量压测4.1 压测脚本编写k6 是一个开源压测工具脚本使用 JavaScript 编写非常方便。下面创建一个压测脚本load-test.js// 文件路径capacity-demo/load-test.js import http from k6/http; import { check, sleep } from k6; export const options { // 阶段的含义先爬升到 50 VU再维持 100 VU再降到 0 stages: [ { duration: 1m, target: 50 }, { duration: 2m, target: 100 }, { duration: 1m, target: 0 }, ], thresholds: { // 错误率低于 1%95% 响应时间小于 500ms http_req_failed: [rate0.01], http_req_duration: [p(95)500], }, }; export default function () { // 这里同时压测普通接口和 CPU 密集型接口 const res http.get(http://localhost:8000/api/compute); check(res, { status is 200: (r) r.status 200, }); sleep(1); }脚本里设置了三个阶段先让并发从 0 升到 50再持续 2 分钟的 100 并发最后降到 0。这样可以看到系统在流量爬坡和持续压力下的表现。thresholds是硬性门槛一旦不满足k6 会在最终结果中使用非零退出码方便集成到 CI/CD 流程。如果你想同时压测/api/hello可以定义多个请求const helloRes http.get(http://localhost:8000/api/hello); check(helloRes, { hello status is 200: (r) r.status 200 });多个请求会共享同一个虚拟用户的执行循环更接近真实业务场景。4.2 执行压测在项目目录下执行k6 run load-test.js注意压测压力从小开始不要一开始就使用极高并发否则容易把服务打挂也得不到有效数据。4.3 分析压测结果k6 跑完后会输出类似下面的摘要指标http_req_duration请求平均耗时和 p95。http_req_failed请求失败率。iterations完成的迭代次数。vus虚拟用户数。如果 p95 响应时间超过预期比如超过 500ms说明当前实例容量已经不足以支撑当前并发。如果错误率高于 1%说明系统已经出现不可用需要降低压测压力先解决瓶颈。4.4 容量阈值判定容量阈值需要根据业务目标来定。通常建议关注以下两个指标SLO服务级别目标比如 p95 响应时间小于 500ms错误率低于 1%。资源水位比如 CPU 使用率超过 70% 就认为需要扩容。在压测中我们可以通过持续增加并发观察“拐点”。当 CPU 使用率稳步上升但 QPS 不再同步上升时基本就达到了单实例的容量上限。这一步得到的数据可以用于后续 Kubernetes HPA 中的目标水位配置。5. 在 Kubernetes 中配置 HPA 实现弹性伸缩5.1 HPA 的基础概念HPA 是 Horizontal Pod Autoscaler 的缩写即水平 Pod 自动伸缩器。它会周期性检查 Pod 的指标数据然后根据当前副本数与指标数值计算出需要的副本数并更新 Deployment 的副本数。HPA 的工作流程可以概括为通过 Metrics API 获取 Pod 的监控指标。对比当前指标和目标指标。计算期望副本数。调用 Kubernetes API 调整 Deployment 副本数。期望副本数不是简单的“当前副本数翻倍”而是按照比例计算。例如当前 CPU 使用率为 90%目标为 60%那么期望副本数大约是当前副本数的 1.5 倍。5.2 部署应用在k8s目录下创建deployment.yaml# 文件路径capacity-demo/k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo-app labels: app: demo-app spec: replicas: 1 selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app spec: containers: - name: demo-app image: demo-app:latest imagePullPolicy: IfNotPresent ports: - containerPort: 8000 resources: requests: cpu: 250m memory: 256Mi limits: cpu: 500m memory: 512Mi这里有一个关键配置resources.requests和limits。HPA 依赖 Pod 的资源请求值来计算 CPU 使用率所以如果没有配置requests.cpuHPA 将无法正常进行 CPU 指标扩缩容。创建service.yaml# 文件路径capacity-demo/k8s/service.yaml apiVersion: v1 kind: Service metadata: name: demo-app spec: selector: app: demo-app ports: - port: 80 targetPort: 8000部署示例kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml5.3 配置 HPA创建hpa.yaml# 文件路径capacity-demo/k8s/hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-app-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-app minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60这里配置的含义是以demo-appDeployment 为伸缩目标最少保留 1 个副本最多扩展到 10 个副本。当所有 Pod 的平均 CPU 使用率超过 60% 时HPA 会尝试扩容。应用 HPAkubectl apply -f k8s/hpa.yaml查看 HPA 状态kubectl get hpa启动后可能看到TARGETS列显示unknown/60%这是因为 metrics 数据尚未采集完全等待一段时间后会自动恢复。5.4 验证自动扩容为了让 Pod 暴露到集群外部可以临时使用端口转发kubectl port-forward svc/demo-app 8000:80然后重新执行 k6 压测k6 run load-test.js压测过程中打开另一个终端观察 Pod 和 HPA 情况kubectl get pods -w kubectl get hpa预期效果是随着并发量升高CPU 使用率超过 60%HPA 开始扩容 Pod直到系统能够支撑当前压力。压测结束后CPU 使用率下降HPA 会逐步缩容。5.5 扩展基于自定义指标的弹性伸缩CPU 和内存是基础指标但很多场景下业务指标更重要例如消息队列堆积数、接口 QPS、订单数等。这类指标需要借助 Prometheus Adapter 或 KEDA 来实现。以 Prometheus Adapter 为例HPA 中可以通过type: Object引用外部指标例如metrics: - type: Object object: metric: name: http_requests_per_second describedObject: apiVersion: apps/v1 kind: Deployment name: demo-app target: type: Value value: 1000不过这类配置依赖你的监控组件和指标暴露方式不能直接照搬。建议先让 CPU 和内存指标的自动伸缩跑通再逐步扩展自定义指标。6. 常见问题与排查思路问题现象常见原因解决思路HPA 显示unknownPod 没有设置资源 requests或 metrics-server 未安装检查 Deployment 资源声明检查 metrics-server 状态扩容后 QPS 反而下降资源 limits 设置过低导致 Pod 被限制 CPU合理设置 requests 和 limits压测验证上限频繁扩容缩容HPA 目标值设置过高或过低缺少冷却时间调整目标水位或配置behavior控制伸缩速率压测发现 CPU 不高但接口慢瓶颈在数据库连接、锁、外部调用不要只依赖 CPU HPA增加依赖监控和自定义指标k6 压测时错误率很高压测并发过高服务达到极限降低并发先找到稳定的容量阈值容器启动后一直 CrashLoopBackOff镜像问题或启动命令错误查看 Pod 日志确认应用启动方式6.1 metrics-server 未安装导致 HPA 无法采集指标如果kubectl get hpa一直显示unknown先检查集群是否安装 metrics-serverkubectl top nodes kubectl top pods如果提示无法获取指标说明 metrics-server 没有正确运行。在 Minikube 中可以执行minikube addons enable metrics-server生产环境需要按照集群版本安装对应的 metrics-server并确保其可以正常采集 kubelet 指标。6.2 资源配额设置不当导致扩容后仍然失败有些场景下HPA 扩容了但新增 Pod 一直处于 Pending 状态。最常见原因是集群节点资源不足无法调度新的 Pod。排查思路kubectl describe pod pod-name kubectl describe nodes如果看到Insufficient cpu或Insufficient memory说明集群总容量不够。此时即使 HPA 配置正确也无法继续扩容。这种情况下需要先增加节点或者降低 Pod 的资源请求值。6.3 压测流量经过 Service 时的端口问题如果压测请求的是 Service 的 ClusterIP需要确认 Service 的 port 和 targetPort 映射正确。本文示例中 Service 的 port 是 80targetPort 是 8000所以在集群内部访问http://demo-app时会转发到容器的 8000 端口。7. 最佳实践与工程建议7.1 建立容量基线数据不要等到线上出问题再临时压测。在服务上线前建议通过压测形成容量基线记录以下内容单实例最大支撑 QPS。不同并发下的 p95 响应时间。CPU、内存使用率的拐点。数据库连接数和外部依赖耗时。这份基线数据是后续容量规划和自动伸缩的重要依据。7.2 HPA 目标值不要追求过满很多团队把 HPA 目标 CPU 设置成 80% 或 90%认为这样能最大化利用机器。但要注意Kubernetes 的 CPU 使用率是平均值且存在指标采集延迟。当流量突然上涨时90% 的目标水位可能来不及扩容导致长时间服务不可用。我建议目标水位设置在 50% 到 70% 之间留出一定的缓冲空间避免扩容动作跟不上流量变化。7.3 使用behavior控制伸缩速率HPA 在 v2 API 中支持behavior配置可以控制扩容和缩容的速率。比如希望缩容慢一些避免抖动behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60这个配置的含义是缩容时每分钟最多减少当前副本数的 50%且至少等待 300 秒的稳定窗口。这样可以在流量回落后避免 Pod 被快速回收造成抖动。7.4 压测数据要和监控系统联动压测只能反映某一时刻的状态容量规划必须依赖持续的监控数据。建议在集群中部署 Prometheus 和 Grafana把应用指标、JVM/运行时指标、Pod 资源指标统一采集。只有监控数据是完整的容量规划才有据可依。7.5 容量规划是持续过程不是一次性任务业务流量会变化代码性能会调整依赖服务也会升级。容量规划不能只做一次建议每隔一个迭代周期或每次大版本发布前重新执行压测和容量评估。同时关注历史监控数据中的峰值趋势提前扩容避免被动应对。8. 总结与下一步这篇文章从容量规划的基本概念出发完整走了一遍“编写示例应用 - 压测评估 - Kubernetes HPA 自动伸缩 - 问题排查”的流程。核心收获有几点容量规划不是简单加机器而是要关注资源、流量、依赖三个维度。压测是容量评估的核心手段重点看 QPS、响应时间、错误率和资源使用率。HPA 的配置需要依赖 Pod 资源请求同时要合理设置目标水位和伸缩策略。自动伸缩不是万能的集群总容量和下游依赖容量同样需要提前规划。如果你刚接触这部分内容下一步建议先在自己的 Kubernetes 环境中把本文示例完整跑一遍再尝试给一个业务服务配置 HPA并通过压测观察扩容过程。熟悉基础伸缩后可以进一步学习 KEDA、Prometheus Adapter 和自定义指标伸缩真正把容量构建能力补起来。希望这篇教程能帮你少踩一些容量规划和自动扩容的坑。如果你在实际配置中遇到其他问题也欢迎留言交流。
返回列表