高扩展-弹性伸缩设计
在云原生时代,弹性伸缩(Elastic Scaling)是应对流量波动、提升资源利用率的核心能力。通过自动增减服务实例,系统可以在流量高峰时快速扩容,在低谷时缩容节省成本,真正实现按需使用资源。
据统计,合理的弹性伸缩策略可以将资源利用率从 30% 提升到 70%,成本降低 40% 以上。本文将深入探讨弹性伸缩的设计原理、实现方式和最佳实践。
# 一、弹性伸缩的价值
# 1、业务流量的波动性
典型流量波动场景:
电商促销:
平时流量: 1000 QPS
双11零点: 50000 QPS (50倍)
促销结束后: 2000 QPS
新闻资讯:
平时流量: 5000 QPS
突发热点: 100000 QPS (20倍)
热点消退: 6000 QPS
在线教育:
工作日白天: 2000 QPS
晚上8-10点: 20000 QPS (10倍)
凌晨时段: 200 QPS
# 2、传统固定容量的问题
按峰值规划容量:
┌────────────────────────────────────┐
│ 固定容量: 100台服务器 │
│ │
│ ▲ 流量 │
│ │ 峰值 │
│ │ ▲ │
│ │ ╱ ╲ │
│ │ ╱ ╲ │
│ │───╱─────╲─── 容量线 (浪费) │
│ │ ╱ ╲ │
│ │_╱_________╲________________► 时间│
└────────────────────────────────────┘
问题:
- 平时资源利用率低(30%)
- 成本浪费严重
- 固定成本无法优化
按平均容量规划:
┌────────────────────────────────────┐
│ 固定容量: 30台服务器 │
│ │
│ ▲ 流量 │
│ │ 峰值(超载) │
│ │ ▲ │
│ │ ╱ ╲ │
│ │─────╱───╲───── 容量线 │
│ │ ╱ ╲ │
│ │___╱_______╲_______________► 时间 │
└────────────────────────────────────┘
问题:
- 峰值时系统过载
- 用户体验差
- 可能导致服务崩溃
# 3、弹性伸缩的优势
按需伸缩:
┌────────────────────────────────────┐
│ 弹性容量: 10-100台 │
│ │
│ ▲ 流量与容量 │
│ │ ▲ │
│ │ ╱█╲ ← 容量跟随流量 │
│ │ ╱███╲ │
│ │ ╱█████╲ │
│ │___╱███████╲_______________► 时间 │
│ 流量曲线 │
└────────────────────────────────────┘
优势:
✅ 资源利用率高(70%+)
✅ 成本优化(降低40%)
✅ 性能稳定
✅ 自动化运维
成本对比:
| 策略 | 服务器数 | 月成本 | 利用率 | 峰值性能 |
|---|---|---|---|---|
| 按峰值固定 | 100台 | ¥50万 | 30% | 优秀 |
| 按平均固定 | 30台 | ¥15万 | 80% | 差(过载) |
| 弹性伸缩 | 平均50台 | ¥25万 | 70% | 优秀 |
弹性伸缩在保证性能的同时,成本降低 50%。
# 二、弹性伸缩的核心概念
# 1、伸缩维度
水平伸缩(Horizontal Scaling):
- 增加或减少实例数量
- 适用于无状态服务
- 是弹性伸缩的主要方式
扩容: [实例1] [实例2] → [实例1] [实例2] [实例3] [实例4]
缩容: [实例1] [实例2] [实例3] [实例4] → [实例1] [实例2]
垂直伸缩(Vertical Scaling):
- 调整单个实例的资源配置
- 需要重启实例
- 较少用于自动伸缩
扩容: 2核4G → 4核8G
缩容: 4核8G → 2核4G
# 2、伸缩指标
资源指标:
- CPU 使用率: 最常用的指标
- 内存使用率: 内存密集型应用
- 网络带宽: 带宽敏感型应用
- 磁盘 I/O: I/O 密集型应用
业务指标:
- QPS(每秒请求数): 流量型应用
- 并发连接数: WebSocket 等长连接
- 消息队列长度: 异步处理场景
- 响应时间: 性能敏感型应用
自定义指标:
- 订单数量: 电商场景
- 在线用户数: 社交应用
- 转码任务数: 视频处理
# 3、伸缩策略
目标追踪(Target Tracking):
- 维持指标在目标值附近
- 最常用的策略
目标: CPU 使用率 70%
当前: CPU 使用率 85% → 扩容
当前: CPU 使用率 40% → 缩容
步进伸缩(Step Scaling):
- 根据指标区间执行不同动作
- 更精细的控制
CPU < 30%: 缩容 2 个实例
30% ≤ CPU < 50%: 缩容 1 个实例
50% ≤ CPU < 70%: 不变
70% ≤ CPU < 85%: 扩容 1 个实例
CPU ≥ 85%: 扩容 3 个实例
定时伸缩(Scheduled Scaling):
- 按时间计划伸缩
- 适合可预测的流量模式
周一至周五:
08:00 扩容到 50 个实例
20:00 缩容到 20 个实例
周末:
全天保持 10 个实例
预测性伸缩(Predictive Scaling):
- 基于历史数据预测未来流量
- 提前扩容,避免流量突增
分析过去 7 天的流量模式:
→ 预测明天 20:00 会有流量高峰
→ 提前 10 分钟扩容
# 4、伸缩边界
最小实例数(Min Instances):
- 保证基本可用性
- 避免缩容到零导致冷启动
minReplicas: 2 # 至少保持 2 个实例
最大实例数(Max Instances):
- 控制成本上限
- 避免无限扩容
maxReplicas: 100 # 最多 100 个实例
缩容冷却时间(Scale Down Cooldown):
- 缩容后等待一段时间再次评估
- 避免频繁缩容
scaleDownStabilizationWindowSeconds: 300 # 5分钟
扩容冷却时间(Scale Up Cooldown):
- 扩容后等待新实例就绪
- 避免频繁扩容
scaleUpStabilizationWindowSeconds: 60 # 1分钟
# 三、Kubernetes 弹性伸缩
# 1、HPA (Horizontal Pod Autoscaler)
基于 CPU 的 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 目标 CPU 70%
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却 5 分钟
policies:
- type: Percent
value: 50 # 每次最多缩容 50%
periodSeconds: 60
- type: Pods
value: 2 # 每次最多缩容 2 个 Pod
periodSeconds: 60
selectPolicy: Min # 选择最保守的策略
scaleUp:
stabilizationWindowSeconds: 60 # 扩容冷却 1 分钟
policies:
- type: Percent
value: 100 # 每次最多扩容 100%
periodSeconds: 30
- type: Pods
value: 5 # 每次最多扩容 5 个 Pod
periodSeconds: 30
selectPolicy: Max # 选择最激进的策略
基于多指标的 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 50
metrics:
# CPU 指标
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# 内存指标
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# QPS 指标(自定义指标)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000" # 每个 Pod 平均 1000 QPS
# 响应时间指标
- type: Pods
pods:
metric:
name: http_request_duration_p95
target:
type: AverageValue
averageValue: "200m" # P95 响应时间 200ms
计算公式:
期望副本数 = ceil[当前副本数 × (当前指标值 / 目标指标值)]
示例:
当前副本数: 10
当前 CPU: 140%
目标 CPU: 70%
期望副本数 = ceil[10 × (140 / 70)] = ceil[20] = 20
需要扩容到 20 个副本
# 2、VPA (Vertical Pod Autoscaler)
VPA 配置:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto" # Auto: 自动更新, Recreate: 重建, Initial: 仅初始化, Off: 仅推荐
resourcePolicy:
containerPolicies:
- containerName: web-app
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 2000m
memory: 2Gi
controlledResources: ["cpu", "memory"]
VPA 推荐结果:
# VPA 分析后的推荐配置
status:
recommendation:
containerRecommendations:
- containerName: web-app
lowerBound: # 最低建议
cpu: 250m
memory: 256Mi
target: # 推荐值
cpu: 500m
memory: 512Mi
uncappedTarget: # 无上限推荐
cpu: 800m
memory: 1Gi
upperBound: # 最高建议
cpu: 1000m
memory: 1.5Gi
# 3、CA (Cluster Autoscaler)
集群级别自动扩缩容:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
replicas: 1
template:
spec:
containers:
- name: cluster-autoscaler
image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.27.0
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=aws
- --skip-nodes-with-local-storage=false
- --expander=least-waste
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-cluster
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false
- --scale-down-enabled=true
- --scale-down-delay-after-add=10m
- --scale-down-unneeded-time=10m
- --max-node-provision-time=15m
工作原理:
Pod 调度失败(资源不足)
↓
Cluster Autoscaler 检测到
↓
向云平台申请新节点
↓
新节点加入集群
↓
Pod 调度到新节点
节点利用率低
↓
Cluster Autoscaler 检测到
↓
驱逐 Pod 到其他节点
↓
删除空闲节点
↓
释放云资源
# 4、KEDA (Kubernetes Event-driven Autoscaling)
基于消息队列的伸缩:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-processor-scaler
spec:
scaleTargetRef:
name: order-processor
minReplicaCount: 1
maxReplicaCount: 50
pollingInterval: 30 # 检查间隔 30 秒
cooldownPeriod: 300 # 冷却时间 5 分钟
triggers:
# RabbitMQ 触发器
- type: rabbitmq
metadata:
queueName: orders
queueLength: "10" # 每个 Pod 处理 10 条消息
host: amqp://guest:guest@rabbitmq:5672/
# Kafka 触发器
- type: kafka
metadata:
bootstrapServers: kafka:9092
consumerGroup: order-processor
topic: orders
lagThreshold: "100" # lag 超过 100 则扩容
基于 HTTP 请求的伸缩:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-scaler
spec:
scaleTargetRef:
name: api-server
minReplicaCount: 2
maxReplicaCount: 100
triggers:
# Prometheus 查询
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
metricName: http_requests_per_second
query: sum(rate(http_requests_total[1m]))
threshold: "1000" # 总 QPS 超过 1000 则扩容
基于 Cron 的定时伸缩:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: scheduled-scaler
spec:
scaleTargetRef:
name: web-app
minReplicaCount: 2
maxReplicaCount: 100
triggers:
# 工作日高峰期
- type: cron
metadata:
timezone: Asia/Shanghai
start: 0 9 * * 1-5 # 周一到周五 9:00
end: 0 22 * * 1-5 # 周一到周五 22:00
desiredReplicas: "50"
# 周末
- type: cron
metadata:
timezone: Asia/Shanghai
start: 0 0 * * 0,6 # 周末全天
end: 0 0 * * 0,6
desiredReplicas: "10"
# 四、应用层弹性伸缩
# 1、Spring Boot 应用优化
快速启动配置:
# application.yml
spring:
main:
lazy-initialization: true # 懒加载,加快启动
jpa:
hibernate:
ddl-auto: none # 生产环境不自动建表
open-in-view: false # 关闭 OSIV
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
initialization-fail-timeout: -1 # 启动时数据库不可用不报错
# JVM 参数优化
JAVA_OPTS: >
-Xms512m -Xmx1024m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled
-XX:InitiatingHeapOccupancyPercent=70
-Djava.security.egd=file:/dev/./urandom
健康检查接口:
/**
* 健康检查配置
*/
@Configuration
public class HealthCheckConfig {
@Bean
public HealthIndicator customHealthIndicator() {
return new AbstractHealthIndicator() {
@Override
protected void doHealthCheck(Health.Builder builder) {
// 快速健康检查,避免阻塞
builder.up()
.withDetail("status", "UP")
.withDetail("timestamp", System.currentTimeMillis());
}
};
}
}
# Kubernetes Probe 配置
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
优雅启动与关闭:
/**
* 优雅关闭配置
*/
@Configuration
public class GracefulShutdownConfig {
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ServletWebServerFactory servletContainer(GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (this.connector == null) {
return;
}
// 停止接收新请求
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
// 等待现有请求处理完成(最多 30 秒)
if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("Tomcat 线程池未在 30 秒内完成关闭");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
# application.yml
server:
shutdown: graceful # Spring Boot 2.3+ 支持优雅关闭
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 关闭超时时间
# 2、连接池动态调整
数据库连接池:
/**
* 动态数据库连接池
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public HikariConfig hikariConfig() {
HikariConfig config = new HikariConfig();
// 基于副本数动态调整连接池大小
int replicas = getReplicaCount();
int maxPoolSize = Math.max(10, 200 / replicas); // 总共 200 个连接
config.setMaximumPoolSize(maxPoolSize);
config.setMinimumIdle(maxPoolSize / 4);
return config;
}
private int getReplicaCount() {
// 从环境变量或配置中心获取当前副本数
String replicas = System.getenv("REPLICA_COUNT");
return replicas != null ? Integer.parseInt(replicas) : 10;
}
}
Redis 连接池:
/**
* 动态 Redis 连接池
*/
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
int replicas = getReplicaCount();
int maxActive = Math.max(20, 500 / replicas);
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxIdle(maxActive / 2);
poolConfig.setMinIdle(maxActive / 4);
poolConfig.setMaxWaitMillis(3000);
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.poolConfig(poolConfig)
.build();
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration();
serverConfig.setHostName("redis-server");
serverConfig.setPort(6379);
return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}
# 3、预热机制
应用预热:
/**
* 应用启动预热
*/
@Component
@Slf4j
public class ApplicationWarmup implements ApplicationRunner {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
@Override
public void run(ApplicationArguments args) {
log.info("开始应用预热...");
// 1. 预加载热点数据到缓存
warmupCache();
// 2. 预编译正则表达式
warmupRegex();
// 3. 初始化连接池
warmupConnectionPools();
log.info("应用预热完成");
}
private void warmupCache() {
// 加载热门商品到缓存
List<Long> hotProductIds = Arrays.asList(1L, 2L, 3L, 100L, 200L);
for (Long productId : hotProductIds) {
try {
productService.getById(productId);
} catch (Exception e) {
log.warn("预加载商品失败: {}", productId, e);
}
}
}
private void warmupRegex() {
// 预编译常用正则表达式
Pattern.compile("^[a-zA-Z0-9]+$");
Pattern.compile("^\\d{11}$");
Pattern.compile("^[\\u4e00-\\u9fa5]+$");
}
private void warmupConnectionPools() {
// 提前建立数据库连接
try {
jdbcTemplate.queryForObject("SELECT 1", Integer.class);
} catch (Exception e) {
log.warn("预热数据库连接失败", e);
}
// 提前建立 Redis 连接
try {
redisTemplate.opsForValue().get("warmup");
} catch (Exception e) {
log.warn("预热 Redis 连接失败", e);
}
}
}
Kubernetes PreStop Hook:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
image: myapp:v1.0
lifecycle:
# 启动后预热
postStart:
exec:
command:
- /bin/sh
- -c
- |
# 等待应用启动
sleep 10
# 调用预热接口
curl -X POST http://localhost:8080/actuator/warmup
# 关闭前通知
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 等待 30 秒,让负载均衡器摘除此实例
sleep 30
# 五、云平台弹性伸缩
# 1、AWS Auto Scaling
EC2 Auto Scaling Group:
{
"AutoScalingGroupName": "web-app-asg",
"MinSize": 3,
"MaxSize": 50,
"DesiredCapacity": 10,
"DefaultCooldown": 300,
"HealthCheckType": "ELB",
"HealthCheckGracePeriod": 300,
"LaunchTemplate": {
"LaunchTemplateId": "lt-1234567890abcdef0",
"Version": "$Latest"
},
"TargetGroupARNs": [
"arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/web-app-tg/50dc6c495c0c9188"
],
"VPCZoneIdentifier": "subnet-12345678,subnet-87654321"
}
Target Tracking Scaling Policy:
{
"PolicyName": "target-tracking-cpu",
"PolicyType": "TargetTrackingScaling",
"TargetTrackingConfiguration": {
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
},
"TargetValue": 70.0
}
}
Step Scaling Policy:
{
"PolicyName": "step-scaling-cpu",
"PolicyType": "StepScaling",
"AdjustmentType": "PercentChangeInCapacity",
"MetricAggregationType": "Average",
"StepAdjustments": [
{
"MetricIntervalLowerBound": 0.0,
"MetricIntervalUpperBound": 10.0,
"ScalingAdjustment": 10
},
{
"MetricIntervalLowerBound": 10.0,
"MetricIntervalUpperBound": 20.0,
"ScalingAdjustment": 20
},
{
"MetricIntervalLowerBound": 20.0,
"ScalingAdjustment": 30
}
]
}
Scheduled Scaling:
{
"ScheduledActionName": "scale-up-morning",
"Schedule": "0 8 * * 1-5",
"MinSize": 20,
"MaxSize": 50,
"DesiredCapacity": 30
}
# 2、阿里云弹性伸缩
伸缩组配置:
# 伸缩组
ScalingGroupId: asg-bp1igpak5ft1flyp****
ScalingGroupName: web-app-scaling-group
MinSize: 3
MaxSize: 50
DefaultCooldown: 300
RemovalPolicies:
- OldestInstance
- OldestScalingConfiguration
HealthCheckType: ECS
LoadBalancerIds:
- lb-bp1hoxb3i7i5jk6mk****
VServerGroups:
- LoadBalancerId: lb-bp1hoxb3i7i5jk6mk****
VServerGroupAttributes:
- VServerGroupId: rsp-bp1jp1rge7f71o0j****
Port: 80
目标追踪规则:
ScalingRuleName: target-tracking-cpu
ScalingRuleType: TargetTrackingScalingRule
MetricName: CpuUtilization
TargetValue: 70
EstimatedInstanceWarmup: 300
简单规则:
ScalingRuleName: add-3-instances
ScalingRuleType: SimpleScalingRule
AdjustmentType: QuantityChangeInCapacity
AdjustmentValue: 3
Cooldown: 180
定时任务:
ScheduledTaskName: scale-up-morning
ScheduledAction: arn:acs:ess:cn-hangzhou:123456789:scalingrule/asr-bp1****
RecurrenceType: Cron
RecurrenceValue: 0 8 * * 1-5
LaunchTime: 2024-01-01T08:00:00Z
LaunchExpirationTime: 600
MinValue: 30
MaxValue: 50
DesiredCapacity: 40
# 3、腾讯云弹性伸缩
伸缩组:
{
"AutoScalingGroupName": "web-app-asg",
"LaunchConfigurationId": "asc-abc123",
"MaxSize": 50,
"MinSize": 3,
"DesiredCapacity": 10,
"DefaultCooldown": 300,
"LoadBalancerIds": ["lb-abc123"],
"VpcId": "vpc-abc123",
"SubnetIds": ["subnet-abc123", "subnet-def456"],
"TerminationPolicies": ["OLDEST_INSTANCE"]
}
# 六、伸缩策略最佳实践
# 1、指标选择
不同场景的指标选择:
| 场景 | 主要指标 | 辅助指标 | 目标值 |
|---|---|---|---|
| Web 应用 | CPU 使用率 | QPS、响应时间 | CPU 70% |
| API 服务 | QPS | CPU、并发连接数 | 1000 QPS/Pod |
| 消息处理 | 队列长度 | CPU、内存 | 10 msg/Pod |
| 视频转码 | CPU 使用率 | 任务队列长度 | CPU 80% |
| 数据库 | 连接数 | CPU、IOPS | 100 conn/实例 |
| 缓存服务 | 内存使用率 | QPS、命中率 | Memory 70% |
# 2、伸缩参数调优
扩容策略:
# 激进扩容(适合流量突增场景)
scaleUp:
stabilizationWindowSeconds: 30 # 短冷却时间
policies:
- type: Percent
value: 100 # 每次翻倍
periodSeconds: 30
selectPolicy: Max # 选择最激进策略
# 保守扩容(适合稳定场景)
scaleUp:
stabilizationWindowSeconds: 120 # 较长冷却时间
policies:
- type: Percent
value: 25 # 每次增加 25%
periodSeconds: 60
selectPolicy: Min # 选择最保守策略
缩容策略:
# 保守缩容(推荐,避免频繁缩容)
scaleDown:
stabilizationWindowSeconds: 300 # 5 分钟冷却
policies:
- type: Percent
value: 10 # 每次最多缩 10%
periodSeconds: 120
- type: Pods
value: 1 # 每次最多缩 1 个
periodSeconds: 120
selectPolicy: Min # 选择最保守策略
# 3、多指标组合
组合策略示例:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 5
maxReplicas: 100
metrics:
# 指标 1: CPU(权重最高)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# 指标 2: 内存(防止 OOM)
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# 指标 3: QPS(业务指标)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "800"
# 指标 4: 响应时间(用户体验)
- type: Pods
pods:
metric:
name: http_request_duration_p95
target:
type: AverageValue
averageValue: "200m"
# HPA 会选择所有指标计算的副本数中的最大值
计算逻辑:
当前状态:
- 10 个副本
- CPU: 140% (期望 70%) → 需要 20 个副本
- Memory: 60% (期望 80%) → 需要 8 个副本
- QPS: 10000 (期望 800/Pod) → 需要 13 个副本
- P95: 300ms (期望 200ms) → 需要 15 个副本
最终选择: max(20, 8, 13, 15) = 20 个副本
# 4、防抖动设计
问题: 指标在阈值附近波动,导致频繁扩缩容。
CPU 使用率:
71% → 扩容到 12 个副本
69% → 缩容到 10 个副本
71% → 扩容到 12 个副本
69% → 缩容到 10 个副本
...
解决方案 1: 设置缓冲区:
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65 # 目标值设置为 65%,而非 70%
# 这样:
# CPU > 70% 才扩容
# CPU < 60% 才缩容
# 60%-70% 之间不动作
解决方案 2: 延长稳定窗口:
behavior:
scaleDown:
stabilizationWindowSeconds: 600 # 10 分钟内多次计算,取最大值
解决方案 3: 限制缩容速度:
behavior:
scaleDown:
policies:
- type: Pods
value: 1 # 每次只缩 1 个
periodSeconds: 300 # 5 分钟缩一次
# 七、成本优化
# 1、竞价实例
AWS Spot Instances:
apiVersion: v1
kind: Node
metadata:
labels:
node.kubernetes.io/instance-type: spot
spec:
taints:
- key: spot
value: "true"
effect: NoSchedule
# Pod 配置容忍 Spot 实例
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
spec:
template:
spec:
tolerations:
- key: spot
operator: Equal
value: "true"
effect: NoSchedule
# Spot 实例可能被回收,设置优雅终止
terminationGracePeriodSeconds: 120
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- spot
混合使用按需和竞价实例:
# 核心服务使用按需实例
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 10
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node.kubernetes.io/instance-type
operator: NotIn
values:
- spot
---
# 批处理任务使用竞价实例
apiVersion: batch/v1
kind: Job
metadata:
name: video-transcode
spec:
template:
spec:
tolerations:
- key: spot
operator: Equal
value: "true"
effect: NoSchedule
# 2、按优先级伸缩
定义 PriorityClass:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
description: "高优先级服务(核心业务)"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: medium-priority
value: 500
globalDefault: false
description: "中优先级服务(重要业务)"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
globalDefault: true
description: "低优先级服务(非核心业务)"
应用优先级:
# 核心 API 服务 - 高优先级
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 20
template:
spec:
priorityClassName: high-priority
containers:
- name: api
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
---
# 数据分析服务 - 低优先级
apiVersion: apps/v1
kind: Deployment
metadata:
name: analytics
spec:
replicas: 5
template:
spec:
priorityClassName: low-priority
containers:
- name: analytics
resources:
requests:
cpu: 200m
memory: 256Mi
资源不足时的驱逐顺序:
资源不足 → 优先驱逐低优先级 Pod → 释放资源给高优先级 Pod
# 3、多层级伸缩
三层伸缩架构:
# L1: 应用层 HPA(最快,秒级响应)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
minReplicas: 10
maxReplicas: 100 # 受限于节点容量
metrics:
- type: Resource
resource:
name: cpu
target:
averageUtilization: 70
---
# L2: 节点池 CA(中等速度,分钟级响应)
# 当 Pod pending 时,Cluster Autoscaler 自动扩容节点
---
# L3: 定时伸缩(预测性,提前准备)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: scheduled-scaler
spec:
minReplicas: 50 # 提前扩容
maxReplicas: 200
# 通过外部控制器在流量高峰前调整 minReplicas
成本对比:
| 策略 | 月成本 | 资源利用率 | 响应速度 |
|---|---|---|---|
| 固定容量(按峰值) | ¥100万 | 30% | 无需响应 |
| 单层 HPA | ¥60万 | 60% | 秒级 |
| HPA + CA | ¥50万 | 70% | 分钟级 |
| HPA + CA + 定时 + Spot | ¥35万 | 75% | 混合 |
最优策略节省成本 65%。
# 八、监控与调优
# 1、关键监控指标
伸缩事件监控:
# HPA 伸缩事件
kube_hpa_status_current_replicas{hpa="web-app-hpa"} # 当前副本数
kube_hpa_status_desired_replicas{hpa="web-app-hpa"} # 期望副本数
kube_hpa_spec_min_replicas{hpa="web-app-hpa"} # 最小副本数
kube_hpa_spec_max_replicas{hpa="web-app-hpa"} # 最大副本数
# 伸缩频率
rate(kube_hpa_status_desired_replicas[5m])
# Pod pending 时间
kube_pod_status_phase{phase="Pending"}
性能指标:
# CPU 使用率
100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])))
# 内存使用率
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# Pod 启动时间
histogram_quantile(0.95, rate(kube_pod_start_duration_seconds_bucket[5m]))
成本指标:
# 实例数量
count(kube_node_info)
# 按实例类型统计成本
sum by (instance_type) (
kube_node_labels * on(node) group_left(instance_type)
node_instance_cost
)
# 2、Grafana 仪表盘
HPA 监控面板:
{
"dashboard": {
"title": "HPA Monitoring",
"panels": [
{
"title": "副本数变化",
"targets": [
{
"expr": "kube_hpa_status_current_replicas{hpa='web-app-hpa'}",
"legendFormat": "当前副本数"
},
{
"expr": "kube_hpa_status_desired_replicas{hpa='web-app-hpa'}",
"legendFormat": "期望副本数"
}
]
},
{
"title": "CPU 使用率",
"targets": [
{
"expr": "100 * avg(rate(container_cpu_usage_seconds_total{pod=~'web-app-.*'}[5m]))",
"legendFormat": "平均 CPU 使用率"
}
]
},
{
"title": "伸缩事件",
"targets": [
{
"expr": "changes(kube_hpa_status_desired_replicas{hpa='web-app-hpa'}[1h])",
"legendFormat": "伸缩次数"
}
]
}
]
}
}
# 3、告警规则
Prometheus 告警:
groups:
- name: autoscaling-alerts
rules:
# HPA 达到上限
- alert: HPAMaxedOut
expr: |
kube_hpa_status_current_replicas / kube_hpa_spec_max_replicas > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "HPA 接近上限"
description: "{{ $labels.hpa }} 当前副本数已达到最大值的 90%"
# HPA 达到下限
- alert: HPAMinedOut
expr: |
kube_hpa_status_current_replicas == kube_hpa_spec_min_replicas
for: 30m
labels:
severity: info
annotations:
summary: "HPA 处于下限"
description: "{{ $labels.hpa }} 长时间处于最小副本数,可能可以进一步缩容"
# 频繁伸缩
- alert: FrequentScaling
expr: |
changes(kube_hpa_status_desired_replicas[30m]) > 10
labels:
severity: warning
annotations:
summary: "HPA 频繁伸缩"
description: "{{ $labels.hpa }} 30分钟内伸缩超过10次,可能需要调整参数"
# Pod 启动慢
- alert: SlowPodStartup
expr: |
histogram_quantile(0.95, rate(kube_pod_start_duration_seconds_bucket[10m])) > 120
for: 10m
labels:
severity: warning
annotations:
summary: "Pod 启动时间过长"
description: "P95 Pod 启动时间超过 2 分钟"
# 4、调优建议
场景 1: 频繁扩缩容
原因分析:
- 指标在阈值附近波动
- 冷却时间过短
- 伸缩步长过大
解决方案:
# 调整前
metrics:
- type: Resource
resource:
name: cpu
target:
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 60
# 调整后
metrics:
- type: Resource
resource:
name: cpu
target:
averageUtilization: 65 # 降低目标值,增加缓冲
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 延长冷却时间
policies:
- type: Pods
value: 1
periodSeconds: 120 # 减缓缩容速度
场景 2: 扩容不及时
原因分析:
- 扩容策略过于保守
- Pod 启动时间长
- 节点资源不足
解决方案:
# 1. 调整扩容策略
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # 缩短冷却时间
policies:
- type: Percent
value: 100 # 激进扩容,每次翻倍
periodSeconds: 30
# 2. 优化 Pod 启动
spec:
containers:
- name: app
startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
# 3. 启用 Cluster Autoscaler
# 确保有足够的节点资源
场景 3: 成本过高
原因分析:
- 最小副本数设置过高
- 长期处于高负载
- 未使用竞价实例
解决方案:
# 1. 降低最小副本数
spec:
minReplicas: 3 # 从 10 降到 3
# 2. 使用定时伸缩
# 低峰期进一步降低副本数
# 3. 使用竞价实例
# 非核心服务使用 Spot 实例
# 4. 启用 VPA 优化资源配置
# 避免资源浪费
# 九、总结
# 弹性伸缩的核心价值
- 成本优化: 按需使用资源,降低 40%-60% 成本
- 性能保障: 自动应对流量波动,保证用户体验
- 运维自动化: 减少人工干预,提升效率
- 资源利用率: 从 30% 提升到 70%+
# 实施路径
| 阶段 | 内容 | 难度 |
|---|---|---|
| 入门 | 基于 CPU 的 HPA | 低 |
| 进阶 | 多指标 HPA + CA | 中 |
| 高级 | KEDA + 定时伸缩 + Spot 实例 | 高 |
| 专家 | 预测性伸缩 + 多层伸缩 | 很高 |
# 关键最佳实践
- ✅ 合理设置边界:
minReplicas保证可用性,maxReplicas控制成本 - ✅ 延长缩容冷却: 避免频繁缩容,推荐 5-10 分钟
- ✅ 多指标组合: CPU + 内存 + 业务指标
- ✅ 优化启动速度: 减少冷启动时间,提升扩容响应速度
- ✅ 监控先行: 建立完善的监控和告警体系
- ✅ 渐进式调优: 从保守策略开始,逐步优化
- ✅ 成本优化: 定时伸缩 + 竞价实例 + 优先级调度
# 常见误区
- ❌ 过度激进的扩容策略
- ❌ 过度保守的缩容策略
- ❌ 忽视 Pod 启动时间
- ❌ 单一指标决策
- ❌ 未设置合理的边界
- ❌ 缺乏监控和告警
弹性伸缩是云原生架构的核心能力,掌握弹性伸缩的原理和实践,是构建高可用、高性价比系统的关键。
祝你变得更强!