从零开始的云原生之旅(九):云原生的核心优势:自动弹性伸缩实战
从零开始的云原生之旅(九):云原生的核心优势:自动弹性伸缩实战
终于体验到云原生的真正威力!流量来了自动扩容,流量下降自动缩容!
📖 文章目录
- 前言
- 一、为什么需要弹性伸缩?
- 1.1 传统方式的痛点
- 1.2 弹性伸缩的价值
- 1.3 真实场景
- 二、Kubernetes 弹性伸缩全景
- 2.1 三种伸缩方式
- 2.2 HPA 工作原理
- 2.3 架构图解
- 三、准备工作:理解资源管理
- 3.1 Resources Requests 和 Limits
- 3.2 为什么 HPA 依赖 Requests?
- 3.3 配置建议
- 四、安装 Metrics Server
- 4.1 什么是 Metrics Server?
- 4.2 安装步骤
- 4.3 我踩的坑:Metrics API not available
- 4.4 验证安装
- 五、添加负载测试接口
- 5.1 为什么需要负载接口?
- 5.2 实现 CPU 密集型接口
- 5.3 实现内存密集型接口
- 5.4 代码解析
- 六、优化资源配置
- 6.1 调整 Deployment 资源
- 6.2 优化探针配置
- 6.3 为什么这样配置?
- 七、初次测试
- 7.1 构建和部署
- 7.2 手动触发负载
- 7.3 观察资源使用
- 结语
前言
在完成了 v0.2 的所有 Kubernetes 工作负载实战后,我意识到:
手动管理副本数太低效了!
流量高峰时手动扩容,凌晨时手动缩容?
这不就是把自己变成了"人肉运维"吗?
这就是为什么我要学习 v0.3 - 弹性伸缩版。这篇文章记录我:
- ✅ 理解为什么弹性伸缩是云原生的核心能力
- ✅ 安装和配置 Metrics Server
- ✅ 优化应用的资源配置
- ✅ 添加负载测试接口
- ✅ 为 HPA 做好准备工作
下一篇将详细讲解 HPA 的配置和实战。
一、为什么需要弹性伸缩?
1.1 传统方式的痛点
在没有弹性伸缩之前,我是这样运维的:
# 早上 9 点,流量上来了
kubectl scale deployment api-server --replicas=10# 晚上 11 点,流量下去了
kubectl scale deployment api-server --replicas=2# 周末活动,临时加机器
kubectl scale deployment api-server --replicas=50# 活动结束,再手动缩回去
kubectl scale deployment api-server --replicas=2
问题一大堆:
❌ 需要时刻盯着监控
❌ 半夜流量突增怎么办?我在睡觉啊!
❌ 扩容不及时 → 服务卡顿,用户流失
❌ 缩容忘记了 → 资源浪费,成本增加
❌ 每个服务都要手动管理 → 累死人
简单说:手动伸缩 = 低效 + 不可靠 + 浪费资源
1.2 弹性伸缩的价值
启用弹性伸缩后:
# 只需要配置一次
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:name: api-hpa
spec:minReplicas: 2 # 最少 2 个maxReplicas: 10 # 最多 10 个metrics:- type: Resourceresource:name: cputarget:type: UtilizationaverageUtilization: 70 # CPU 超过 70% 就扩容
然后,魔法发生了:
✅ 流量增加 → CPU 超过 70% → 自动扩容 → 性能恢复
✅ 流量下降 → CPU 低于 70% → 自动缩容 → 节省资源
✅ 半夜突发流量 → 自动处理,不需要运维起床
✅ 多个服务 → 各自独立伸缩,互不影响
✅ 按需使用资源 → 成本优化
这就是云原生的核心能力之一!
1.3 真实场景
以我的 API 服务为例:
| 时间段 | 流量 | 传统方式 | 弹性伸缩 |
|---|---|---|---|
| 凌晨 2-6 点 | 低 | 10 个 Pod(浪费) | 2 个 Pod(自动缩容) |
| 工作日 9-18 点 | 中 | 10 个 Pod(勉强够) | 4-6 个 Pod(按需) |
| 晚上 20-22 点 | 高峰 | 10 个 Pod(不够!) | 8-10 个 Pod(自动扩容) |
| 周末活动 | 爆发 | 10 个 Pod(卡死) | 动态扩容到 50+(自动) |
节省资源:
- 传统方式:按峰值配置,平均利用率只有 30%
- 弹性伸缩:平均利用率 60-70%,资源成本降低 40%
二、Kubernetes 弹性伸缩全景
2.1 三种伸缩方式
Kubernetes 提供了 3 个层次的伸缩:
┌─────────────────────────────────────────────┐
│ 1. Pod 水平伸缩 (HPA) │
│ 调整 Pod 副本数 │
│ ↓ 最常用,本系列重点! │
├─────────────────────────────────────────────┤
│ 2. Pod 垂直伸缩 (VPA) │
│ 调整 Pod 的 CPU/内存配置 │
│ ↓ 适合无状态应用 │
├─────────────────────────────────────────────┤
│ 3. 集群节点伸缩 (Cluster Autoscaler) │
│ 增加或减少节点数量 │
│ ↓ 云平台自动管理 │
└─────────────────────────────────────────────┘
对比:
| 类型 | 调整对象 | 适用场景 | 本系列 |
|---|---|---|---|
| HPA | Pod 副本数 | 无状态应用、流量波动大 | ✅ v0.3 重点 |
| VPA | Pod 资源配置 | 资源需求变化的应用 | ❌ 暂不涉及 |
| CA | 集群节点数 | 云环境、资源池不足 | ❌ 暂不涉及 |
为什么选择 HPA?
- ✅ 最常用,覆盖 90% 的场景
- ✅ 响应快(秒级)
- ✅ 风险低(不会改变 Pod 配置)
- ✅ 适合我的 API 服务
2.2 HPA 工作原理
HPA 的核心逻辑很简单:
期望副本数 = 当前副本数 × (当前指标 / 目标指标)
举例:
当前: 2 个 Pod,CPU 使用率 140%
目标: 70%
计算: 2 × (140% / 70%) = 4 个 Pod→ HPA 扩容到 4 个 Pod
→ 流量分散,CPU 降到 70%
→ 达到平衡
完整流程:
1. Metrics Server 每 15 秒采集一次指标↓
2. HPA 控制器每 15 秒检查一次(可配置)↓
3. 计算当前指标 vs 目标指标↓
4. 决定扩容/缩容/不变↓
5. 更新 Deployment 的 replicas↓
6. Kubernetes 创建/删除 Pod
2.3 架构图解
┌──────────────┐
│ kubelet │ ← 运行在每个节点
│ (cAdvisor) │ ← 收集容器指标
└──────┬───────┘│ 指标↓
┌──────────────┐
│ Metrics │ ← 聚合并暴露指标
│ Server │ ← /apis/metrics.k8s.io/v1beta1
└──────┬───────┘│ 查询↓
┌──────────────┐
│ HPA │ ← 根据指标计算副本数
│ Controller │ ← 更新 Deployment
└──────┬───────┘│ 调整↓
┌──────────────┐
│ Deployment │ ← 创建/删除 Pod
└──────────────┘
关键点:
- cAdvisor:内置在 kubelet 中,负责收集容器指标
- Metrics Server:聚合指标,提供 API(必须安装!)
- HPA Controller:Kubernetes 内置,自动运行
- Deployment:被 HPA 控制,副本数会自动变化
三、准备工作:理解资源管理
3.1 Resources Requests 和 Limits
在配置 HPA 之前,必须理解资源管理:
resources:requests: # 调度和计费的基准cpu: "100m" # 100 毫核 = 0.1 核memory: "128Mi" # 128 兆字节limits: # 硬性限制cpu: "500m" # 最多用 0.5 核memory: "512Mi" # 最多用 512 MB
requests vs limits:
| 配置 | 作用 | 超出后果 |
|---|---|---|
| requests | 调度保证、HPA 计算基准 | 可以超出(只要节点有余量) |
| limits | 硬性上限 | CPU 被限流,内存被 OOMKilled |
为什么重要?
HPA 的 CPU 利用率 = 实际使用 / requests例如:
- requests: 100m
- 实际使用: 70m
- 利用率: 70%如果没设置 requests → HPA 无法工作!
3.2 为什么 HPA 依赖 Requests?
原因:HPA 需要一个"基准"来计算利用率。
# ❌ 错误:没有 requests,HPA 无法工作
resources:limits:cpu: "500m"
# HPA: "我不知道 100m 的实际使用算多少利用率!"# ✅ 正确:有 requests,HPA 可以计算
resources:requests:cpu: "100m" # 基准limits:cpu: "500m" # 上限
# HPA: "70m / 100m = 70% 利用率,很好!"
3.3 配置建议
基于我的实践经验:
# 推荐配置(适用于大多数 API 服务)
resources:requests:cpu: "100m" # 保守一点,确保能调度memory: "128Mi" # 基础内存limits:cpu: "500m" # 5 倍突发空间memory: "512Mi" # 4 倍突发空间(防止 OOM)
为什么这样配置?
- requests 小:Pod 容易调度,HPA 容易触发(CPU 容易超过 70%)
- limits 大:给突发流量足够的缓冲空间
- 比例合理:避免资源浪费
错误配置示例:
# ❌ 错误 1:limits = requests(无突发空间)
resources:requests:cpu: "500m"limits:cpu: "500m" # 没有突发空间,容易卡顿# ❌ 错误 2:requests 过大(HPA 难以触发)
resources:requests:cpu: "500m" # 太大了,CPU 很难超过 70%limits:cpu: "1000m"# ❌ 错误 3:limits 过小(容易 OOM)
resources:requests:memory: "128Mi"limits:memory: "200Mi" # 太小,高负载时容易 OOMKilled
四、安装 Metrics Server
4.1 什么是 Metrics Server?
Metrics Server 是 Kubernetes 的核心组件,负责:
- ✅ 收集节点和 Pod 的 CPU、内存使用情况
- ✅ 提供 Metrics API(
kubectl top和 HPA 依赖它) - ✅ 每 15 秒更新一次数据
依赖关系:
kubectl top nodes/pods → Metrics Server → HPA↓必须先安装!
4.2 安装步骤
Step 1: 下载并安装
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Step 2: 等待部署完成
kubectl get deployment metrics-server -n kube-system
预期输出:
NAME READY UP-TO-DATE AVAILABLE AGE
metrics-server 1/1 1 1 30s
Step 3: 验证 Pod 状态
kubectl get pods -n kube-system -l k8s-app=metrics-server
预期输出:
NAME READY STATUS RESTARTS AGE
metrics-server-xxxxxxxxx-xxxxx 1/1 Running 0 1m
4.3 我踩的坑:Metrics API not available
问题现象:
$ kubectl top nodes
Error from server (ServiceUnavailable): the server is currently unable to handle the request (get nodes.metrics.k8s.io)
原因:
- 本地 Kubernetes 环境(Docker Desktop、Minikube、Kind)
- Metrics Server 默认配置需要 TLS 证书
- 本地环境没有有效的证书 → 连接失败
解决方案:禁用 TLS 验证
kubectl patch deployment metrics-server -n kube-system --type='json' -p='[{"op": "add","path": "/spec/template/spec/containers/0/args/-","value": "--kubelet-insecure-tls"}
]'
等待 Pod 重启:
kubectl rollout status deployment metrics-server -n kube-system
再次验证:
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
docker-desktop 250m 3% 1500Mi 19%
✅ 成功!
注意:
- ⚠️
--kubelet-insecure-tls仅用于本地开发环境 - ⚠️ 生产环境必须配置正确的 TLS 证书
- ⚠️ Minikube 用户可能还需要添加
--kubelet-preferred-address-types=InternalIP
4.4 验证安装
测试 1: 查看节点指标
kubectl top nodes
测试 2: 查看 Pod 指标
kubectl top pods --all-namespaces
测试 3: 查看 Metrics API
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq .
如果都能正常返回数据,说明 Metrics Server 安装成功!
五、添加负载测试接口
5.1 为什么需要负载接口?
为了测试 HPA,我需要:
- 能够主动触发 CPU 负载(让 CPU 使用率上升)
- 能够主动触发内存负载(让内存使用率上升)
- 观察 HPA 的自动扩缩容行为
所以我在 API 服务中添加了 3 个测试接口:
/api/v1/workload/cpu- CPU 密集型任务/api/v1/workload/memory- 内存密集型任务/api/v1/workload- 混合负载
5.2 实现 CPU 密集型接口
核心思路:通过大量数学计算消耗 CPU。
// src/handler/workload.go
package handlerimport ("math""runtime""strconv""time""github.com/gin-gonic/gin"
)// CPUIntensiveHandler CPU 密集型接口
func CPUIntensiveHandler(c *gin.Context) {iterations := getIntParam(c, "iterations", 10000000)startTime := time.Now()result := cpuWorkload(iterations / 1000000)duration := time.Since(startTime)c.JSON(200, gin.H{"result": result,"iterations": iterations,"duration_ms": duration.Milliseconds(),"goroutines": runtime.NumGoroutine(),"cpu_cores": runtime.NumCPU(),"message": "CPU intensive task completed",})
}// cpuWorkload 执行 CPU 密集型计算
func cpuWorkload(intensity int) float64 {result := 0.0iterations := intensity * 1000000for i := 0; i < iterations; i++ {result += math.Sqrt(float64(i))result += math.Sin(float64(i))result += math.Cos(float64(i))result += math.Tan(float64(i))if i%10000 == 0 {result += math.Pow(float64(i), 2)result += math.Log(float64(i + 1))}}return result
}
使用示例:
# 低强度(10 百万次迭代)
curl "http://localhost:8080/api/v1/workload/cpu?iterations=10000000"# 高强度(50 百万次迭代)
curl "http://localhost:8080/api/v1/workload/cpu?iterations=50000000"
5.3 实现内存密集型接口
核心思路:分配大量内存并持有一段时间。
// MemoryIntensiveHandler 内存密集型接口
func MemoryIntensiveHandler(c *gin.Context) {sizeMB := getIntParam(c, "size", 50)durationSec := getIntParam(c, "duration", 3)// 限制最大内存分配(防止 OOM)if sizeMB > 200 {c.JSON(400, gin.H{"error": "Size too large","max_mb": 200,"message": "请求的内存大小超过限制",})return}startTime := time.Now()// 分配内存data := make([]byte, sizeMB*1024*1024)// 填充数据(防止编译器优化掉)for i := range data {data[i] = byte(i % 256)}// 持有内存一段时间time.Sleep(time.Duration(durationSec) * time.Second)elapsed := time.Since(startTime)// 获取内存统计var m runtime.MemStatsruntime.ReadMemStats(&m)c.JSON(200, gin.H{"allocated_mb": sizeMB,"duration_sec": durationSec,"actual_ms": elapsed.Milliseconds(),"memory_stats": gin.H{"alloc_mb": m.Alloc / 1024 / 1024,"total_alloc_mb": m.TotalAlloc / 1024 / 1024,"sys_mb": m.Sys / 1024 / 1024,"num_gc": m.NumGC,},"message": "Memory intensive task completed",})
}
使用示例:
# 分配 50MB,持有 3 秒
curl "http://localhost:8080/api/v1/workload/memory?size=50&duration=3"# 分配 100MB,持有 5 秒
curl "http://localhost:8080/api/v1/workload/memory?size=100&duration=5"
5.4 代码解析
为什么 CPU 接口要做这么多计算?
result += math.Sqrt(float64(i)) // 平方根
result += math.Sin(float64(i)) // 正弦
result += math.Cos(float64(i)) // 余弦
result += math.Tan(float64(i)) // 正切
- 数学计算是 CPU 密集型操作
- 多种运算增加 CPU 负载
- 迭代次数越多,CPU 使用率越高
为什么内存接口要填充数据?
for i := range data {data[i] = byte(i % 256) // 填充每个字节
}
- 防止 Go 编译器优化掉未使用的内存
- 确保内存真实分配和占用
- 模拟真实的内存使用场景
为什么要限制最大值?
if sizeMB > 200 {return error
}
- 防止恶意请求导致 OOM
- 保护 Pod 稳定性
- 200MB 已经足够测试 HPA
六、优化资源配置
6.1 调整 Deployment 资源
为了支持负载测试和 HPA,我调整了 Deployment 配置:
# k8s/v0.3/api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: cloudnative-api
spec:replicas: 2 # 初始 2 个副本template:spec:containers:- name: apiimage: cloudnative-api:v0.3ports:- containerPort: 8080# ⭐ 关键:资源配置resources:requests:cpu: "100m" # HPA 基准memory: "128Mi"limits:cpu: "500m" # 5 倍突发memory: "512Mi" # 4 倍突发(从 256Mi 提升)# ... 省略其他配置
关键变化:
| 配置项 | v0.2 | v0.3 | 原因 |
|---|---|---|---|
| memory limits | 256Mi | 512Mi | 支持负载测试(防止 OOM) |
| cpu limits | 300m | 500m | 更大的突发空间 |
| memory requests | 64Mi | 128Mi | 提高基准,更合理 |
6.2 优化探针配置
在高负载下,探针可能会失败导致 Pod 重启。我优化了探针配置:
# 存活探针(优化:增加超时和失败阈值)
livenessProbe:httpGet:path: /healthport: 8080initialDelaySeconds: 15 # 增加初始延迟periodSeconds: 15 # 降低检查频率timeoutSeconds: 10 # 增加超时时间failureThreshold: 5 # 允许更多失败(75 秒)# 就绪探针(优化:适应负载测试)
readinessProbe:httpGet:path: /readyport: 8080initialDelaySeconds: 5periodSeconds: 10 # 降低检查频率timeoutSeconds: 10 # ⭐ 关键:增加超时failureThreshold: 6 # 允许更多失败(60 秒)
6.3 为什么这样配置?
问题:高负载下探针超时
场景:30 个并发请求,CPU 100%
↓
就绪探针 3 秒超时
↓
应用忙于处理请求,无法及时响应探针
↓
探针失败 3 次
↓
Kubernetes: "Pod 不健康,重启!"
↓
Pod 重启 → 正在处理的请求全部丢失
↓
恶性循环:CrashLoopBackOff
解决方案:放宽探针配置
timeoutSeconds: 3 → 10 # 给 7 秒额外时间
failureThreshold: 3 → 6 # 允许失败 60 秒
periodSeconds: 5 → 10 # 降低检查频率
效果:
- ✅ Pod 不会因为短暂的高负载被误杀
- ✅ 给应用足够的缓冲时间
- ✅ 压测期间 Pod 稳定运行
七、初次测试
7.1 构建和部署
# 1. 构建新镜像(包含负载接口)
docker build -t cloudnative-api:v0.3 .# 2. 如果是 Minikube,设置 Docker 环境
eval $(minikube docker-env)
docker build -t cloudnative-api:v0.3 .# 3. 部署应用
kubectl apply -f k8s/v0.3/api/deployment.yaml
kubectl apply -f k8s/v0.3/api/service.yaml# 4. 等待 Pod 就绪
kubectl wait --for=condition=available --timeout=60s deployment/cloudnative-api# 5. 查看状态
kubectl get pods -l app=cloudnative-api
7.2 手动触发负载
测试 CPU 接口:
# 使用 Minikube Service(推荐)
export SERVICE_URL=$(minikube service cloudnative-api-service --url)# 轻量测试(10 百万次迭代)
curl "$SERVICE_URL/api/v1/workload/cpu?iterations=10000000"# 重量测试(30 百万次迭代)
curl "$SERVICE_URL/api/v1/workload/cpu?iterations=30000000"
测试内存接口:
# 分配 50MB
curl "$SERVICE_URL/api/v1/workload/memory?size=50&duration=3"# 分配 100MB
curl "$SERVICE_URL/api/v1/workload/memory?size=100&duration=5"
7.3 观察资源使用
实时监控 Pod 资源:
# 终端 1:实时查看资源使用
watch -n 1 kubectl top pods -l app=cloudnative-api# 输出示例:
NAME CPU(cores) MEMORY(bytes)
cloudnative-api-xxxxxxxxx-xxxxx 15m 85Mi # 空闲
cloudnative-api-xxxxxxxxx-xxxxx 250m 180Mi # 负载中
发送多个请求观察变化:
# 终端 2:循环发送请求
for i in {1..20}; doecho "Request $i"curl -s "$SERVICE_URL/api/v1/workload/cpu?iterations=20000000" > /dev/nullsleep 1
done
观察现象:
- CPU 使用从 15m 上升到 150m+
- 内存使用从 85Mi 上升到 150Mi+
- 请求完成后,资源逐渐降低
计算利用率:
CPU 利用率 = 实际使用 / requests = 150m / 100m = 150%
内存利用率 = 实际使用 / requests = 150Mi / 128Mi = 117%
✅ 这就是 HPA 会看到的指标!
结语
这篇文章中,我完成了 HPA 的所有准备工作:
✅ 我学到了什么
-
弹性伸缩的价值
- 自动应对流量变化
- 节省资源成本
- 提高系统可靠性
-
Kubernetes 弹性伸缩体系
- HPA(Pod 水平伸缩)← 本系列重点
- VPA(Pod 垂直伸缩)
- Cluster Autoscaler(节点伸缩)
-
资源管理的重要性
- requests 是 HPA 的计算基准
- limits 防止资源耗尽
- 合理配置才能发挥 HPA 效果
-
Metrics Server
- 提供资源指标 API
- kubectl top 和 HPA 的依赖
- 本地环境需要禁用 TLS 验证
-
负载测试接口
- CPU 密集型:数学计算
- 内存密集型:分配和持有内存
- 为 HPA 测试做好准备
-
探针优化
- 高负载下需要放宽超时
- 避免误杀繁忙的 Pod
- failureThreshold 很重要
🚀 下一步
下一篇文章,我会:
- ✅ 详细讲解 HPA 的配置
- ✅ 实战部署 HPA
- ✅ 观察自动扩缩容过程
- ✅ 调优 HPA 策略
敬请期待《HPA 完全指南:从原理到实践》!
相关文章:
- 上一篇:CronJob 实战:定时清理 Redis 过期数据
- 下一篇:HPA 完全指南:从原理到实践
- 再下一篇:压测实战:验证弹性伸缩效果
项目代码:GitHub - cloudnative-go-journey
