概述

容量规划是 SRE 的核心职责之一。Google SRE Book 将容量规划视为"前瞻性工作",强调基于数据预测而非凭经验猜测。一个没有容量规划的团队,要么在高峰期被流量打垮,要么在低谷期浪费大量资源成本。

从指标采集、数据建模、Kubernetes 弹性扩容配置、陷阱规避四个层面,详细梳理容量规划的工程实践。

关于容量规划的系统方法论,可参考 Google SRE Book - Capacity Planning 中关于容量规划与级联故障的讨论。

一、容量规划的核心思想:基于数据而非猜测

容量规划的三个层次

  1. 当前容量评估:系统现在能扛多少?水位是多少?
  2. 容量趋势预测:按当前增长趋势,什么时候需要扩容?
  3. 弹性伸缩策略:面对突发流量,如何自动应对?

容量规划的前提:可观测性

没有度量就没有管理。容量规划的基础是完善的监控体系,需要持续采集以下指标:

指标类别具体指标采集工具
CPU使用率、负载(1m/5m/15m)node_exporter / cAdvisor
内存使用量、可用量、OOM 次数node_exporter / cAdvisor
网络入站/出站带宽、连接数、丢包率node_exporter
磁盘 I/OIOPS、读写延迟、队列深度node_exporter
应用层QPS、延迟分布、错误率Prometheus / 自定义指标
中间件连接池使用率、队列长度、缓存命中率Exporter / 自定义指标

容量水位定义

不是所有指标都同等重要。需要定义关键资源的容量水位:

# 容量水位定义示例
capacity_thresholds:
  cpu:
    warning: 60%    # 60% 开始关注
    critical: 80%   # 80% 需要扩容
    limit: 90%      # 90% 紧急扩容
  memory:
    warning: 70%
    critical: 85%
    limit: 95%
  disk_io:
    warning: 60%
    critical: 80%
    limit: 90%
  connection:
    warning: 60%    # 连接池使用率
    critical: 80%
    limit: 90%

关键原则:水位线不是拍脑袋定的,而是基于压测数据和历史故障分析得出的。如果你的应用在 CPU 85% 时开始出现延迟劣化,那么 warning 就应该设在 70% 以下。

二、容量指标采集

Prometheus 指标采集配置

以下是生产环境中常用的 Prometheus 采集配置,覆盖节点级和应用级指标:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: "production"

scrape_configs:
  # 节点级指标
  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

  # 容器级指标
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  # Kubernetes 服务发现
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)

  # 应用自定义指标(用于 HPA)
  - job_name: 'app-metrics'
    static_configs:
      - targets: ['app-metrics-service:9090']

关键容量查询语句

# CPU 使用率(节点级)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 内存使用率
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100

# 磁盘 I/O 使用率
rate(node_disk_io_time_seconds_total[5m]) * 100

# Pod CPU 使用率(相对于 limit)
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod) /
  sum(kube_pod_container_resource_limits{resource="cpu"}) by (pod) * 100

# 连接数监控
node_netstat_Tcp_CurrEstab

# HTTP 请求 QPS(应用级)
sum(rate(http_requests_total[5m])) by (service)

三、基于历史数据的容量模型

趋势分析

容量预测的第一步是识别趋势。以 QPS 增长为例,通过线性回归可以预测未来资源需求:

#!/usr/bin/env python3
"""
容量趋势分析:基于历史监控数据预测未来容量需求
数据来源:Prometheus Query API
"""
import requests
import numpy as np
from datetime import datetime, timedelta
from sklearn.linear_model import LinearRegression

PROMETHEUS_URL = "http://prometheus:9090/api/v1/query_range"

def fetch_qps_history(days=30):
    """从 Prometheus 获取最近 N 天的每日峰值 QPS"""
    end = datetime.now()
    start = end - timedelta(days=days)
    query = 'sum(rate(http_requests_total[1h]))'

    resp = requests.get(PROMETHEUS_URL, params={
        'query': query,
        'start': start.timestamp(),
        'end': end.timestamp(),
        'step': '3600s'  # 每小时一个数据点
    })
    data = resp.json()['data']['result'][0]['values']
    return [float(v[1]) for v in data]

def predict_capacity(qps_history, days_ahead=30):
    """线性回归预测未来容量需求"""
    x = np.arange(len(qps_history)).reshape(-1, 1)
    y = np.array(qps_history)

    model = LinearRegression().fit(x, y)

    future_x = np.arange(len(qps_history), len(qps_history) + days_ahead).reshape(-1, 1)
    predictions = model.predict(future_x)

    # 计算增长率
    growth_rate = (predictions[-1] - qps_history[-1]) / qps_history[-1] * 100

    return predictions, growth_rate

def check_capacity(predictions, current_capacity, threshold=0.7):
    """检查何时达到容量阈值"""
    for day, pred in enumerate(predictions, start=1):
        if pred > current_capacity * threshold:
            return day, pred
    return None, None

if __name__ == "__main__":
    qps_history = fetch_qps_history(30)
    predictions, growth_rate = predict_capacity(qps_history, 30)

    current_capacity = 8000  # 当前总承载能力
    threshold_day, threshold_qps = check_capacity(predictions, current_capacity, 0.7)

    print(f"当前峰值 QPS: {qps_history[-1]:.0f}")
    print(f"预计 30 天后 QPS: {predictions[-1]:.0f}")
    print(f"增长率: {growth_rate:.1f}%")
    if threshold_day:
        print(f"⚠️ 第 {threshold_day} 天将达到 70% 水位 (QPS: {threshold_qps:.0f}),建议提前扩容")
    else:
        print("✅ 未来 30 天内无需扩容")

季节性波动

很多业务存在季节性波动——电商在双 11、618 峰值飙升;社交平台在晚间高峰流量翻倍。容量规划必须考虑这些周期性模式:

"""
季节性容量分析:识别周期性流量模式
"""
import numpy as np

def detect_seasonality(data, period=24):
    """
    检测数据的周期性模式
    data: 每小时一个数据点的 QPS 列表
    period: 周期长度(24 = 一天)
    """
    if len(data) < period * 7:  # 至少需要 7 个周期
        return None

    # 计算每个时间位的平均值
    pattern = []
    for i in range(period):
        values = [data[j] for j in range(i, len(data), period)]
        pattern.append(np.mean(values))

    avg = np.mean(data)
    peak_ratio = max(pattern) / avg  # 峰值/均值比

    return {
        'pattern': pattern,
        'peak_ratio': peak_ratio,
        'peak_hour': pattern.index(max(pattern))
    }

# 示例使用
data = fetch_qps_history(30 * 24)  # 30 天每小时
result = detect_seasonality(data, period=24)
if result:
    print(f"峰值时段: {result['peak_hour']}:00")
    print(f"峰值/均值比: {result['peak_ratio']:.2f}x")
    # 如果峰值/均值比 > 2,说明流量波动大,弹性扩容收益高
    if result['peak_ratio'] > 2:
        print("💡 流量波动显著,建议采用弹性扩容而非固定扩容")

四、Kubernetes 自动扩缩容

HPA(Horizontal Pod Autoscaler)

HPA 是 Kubernetes 最常用的自动扩缩容机制,根据 CPU/内存或自定义指标自动调整 Pod 副本数。

基于 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: 20          # 最大副本数(防止扩容失控)
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70  # 目标 CPU 利用率 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80  # 目标内存利用率 80%
  behavior:
    # 扩容行为:快速扩容
    scaleUp:
      stabilizationWindowSeconds: 0  # 无需等待,立即扩容
      policies:
        - type: Percent
          value: 100                  # 每次最多扩容 100%
          periodSeconds: 15
        - type: Pods
          value: 4                     # 或最多扩 4 个 Pod
          periodSeconds: 15
      selectPolicy: Max                # 取两个策略的最大值
    # 缩容行为:缓慢缩容
    scaleDown:
      stabilizationWindowSeconds: 300  # 稳定窗口 5 分钟,防止抖动
      policies:
        - type: Percent
          value: 10                    # 每次最多缩容 10%
          periodSeconds: 60

基于自定义指标的 HPA

对于延迟敏感的服务,基于 QPS 或延迟等自定义指标扩容更合理:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-server-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  minReplicas: 4
  maxReplicas: 30
  metrics:
    # 基于 QPS 扩容
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"  # 每个 Pod 目标 1000 QPS
    # 基于延迟扩容(P99)
    - type: Pods
      pods:
        metric:
          name: http_request_duration_p99
        target:
          type: AverageValue
          averageValue: "200"  # P99 延迟目标 200ms
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 50
          periodSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 20
          periodSeconds: 120

使用自定义指标需要部署 Prometheus Adapter,将 Prometheus 指标暴露为 Kubernetes 自定义指标 API:

# Prometheus Adapter 规则配置
# rules.yaml
rules:
  - seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
    resources:
      overrides:
        namespace: {resource: "namespace"}
        pod: {resource: "pod"}
    name:
      matches: "^(.*)_total"
      as: "${1}_per_second"
    metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'

VPA(Vertical Pod Autoscaler)

VPA 自动调整 Pod 的 CPU/内存 requests 和 limits,适用于无法水平扩展的有状态服务:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: database-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: StatefulSet
    name: postgresql
  updatePolicy:
    updateMode: "Auto"  # Auto: 自动调整 | Off: 仅推荐
  resourcePolicy:
    containerPolicies:
      - containerName: postgresql
        minAllowed:
          cpu: 500m
          memory: 2Gi
        maxAllowed:
          cpu: 4000m
          memory: 16Gi
        controlledResources: ["cpu", "memory"]

Cluster Autoscaler

当 Pod 因资源不足而 Pending 时,Cluster Autoscaler 自动向集群添加节点:

# Cluster Autoscaler 配置(以 AWS 为例)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cluster-autoscaler
  namespace: kube-system
spec:
  template:
    spec:
      containers:
        - image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.27.0
          name: cluster-autoscaler
          command:
            - ./cluster-autoscaler
            - --cluster-name=production-cluster
            - --max-node-provision-time=15m     # 最大节点供给时间
            - --balance-similar-node-groups       # 平衡相似节点组
            - --scale-down-enabled=true
            - --scale-down-delay-after-add=10m   # 扩容后 10 分钟内不缩容
            - --scale-down-unneeded-time=15m    # 节点空闲 15 分钟后才缩容
            - --scale-down-utilization-threshold=0.5  # 节点利用率低于 50% 才缩容

五、弹性扩容的陷阱与好的实践

陷阱一:冷启动延迟

问题:HPA 检测到高负载 → 创建新 Pod → 拉取镜像 → 启动应用 → 健康检查通过 → 加入负载均衡。这个链路在极端场景下可能需要 2-5 分钟,高峰期流量早已打满现有实例。

好的实践

# 1. 预热:通过 CronHPA 在可预测的高峰前提前扩容
apiVersion: autoscaling.alibaba.com/v1
kind: CronHorizontalPodAutoscaler
metadata:
  name: ecommerce-pre-scaling
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  jobs:
    - name: "morning-peak"
      schedule: "0 9 * * 1-5"    # 工作日 9:00
      targetReplicas: 15
    - name: "evening-peak"
      schedule: "0 19 * * 1-5"   # 工作日 19:00
      targetReplicas: 20
    - name: "scale-down"
      schedule: "0 23 * * 1-5"   # 工作日 23:00 缩回常态
      targetReplicas: 5
# 2. 预留缓冲副本:minReplicas 设为峰值需求的 60%-70%
spec:
  minReplicas: 6  # 常态保持 6 个副本,而非最低限度的 2 个
  maxReplicas: 30

陷阱二:扩容风暴(Cascading Scaling)

问题:流量突增时,多个服务同时触发 HPA 扩容,导致集群资源瞬间紧张,节点不足,Pod Pending,雪崩效应。

好的实践

  1. 设置扩容优先级:核心服务优先调度
  2. 分级扩容策略:不同服务设置不同的扩容速率
  3. Overprovisioning:使用低优先级的占位 Pod 预留资源
# 占位 Pod:用低优先级 Pod 预留资源,高峰期被自动驱逐
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: overprovisioning
value: -1               # 最低优先级
globalDefault: false
description: "用于预留资源的占位 Pod"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: overprovisioning
  namespace: production
spec:
  replicas: 3
  template:
    spec:
      priorityClassName: overprovisioning
      containers:
        - name: reserve
          image: registry.k8s.io/pause:3.9
          resources:
            requests:
              cpu: 2
              memory: 4Gi

陷阱三:缩容保护

问题:流量高峰刚过,HPA 立即缩容,但此时残余流量仍在处理中,缩容导致正在处理的请求被中断。

好的实践

# 1. 缩容稳定窗口:设置足够长的 stabilizationWindow
behavior:
  scaleDown:
    stabilizationWindowSeconds: 300  # 5 分钟稳定窗口
    policies:
      - type: Pods
        value: 1                      # 每次只缩 1 个 Pod
        periodSeconds: 120            # 每 2 分钟最多缩 1 次

# 2. 优雅终止:给应用足够时间完成在途请求
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: app
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "15"]  # 从 LB 摘除后等待 15s 再退出
# 3. PDB(Pod Disruption Budget)防止过多 Pod 同时被驱逐
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
  namespace: production
spec:
  minAvailable: 50%   # 至少保持 50% 副本可用
  selector:
    matchLabels:
      app: web-app

总结

容量规划不是一次性工作,而是持续性的工程实践:

层面关键实践
度量完善的指标采集体系,定义清晰的水位线
预测基于历史数据的趋势分析 + 季节性波动识别
自动化HPA/VPA/Cluster Autoscaler 多层联动
防护冷启动预热、扩容风暴防护、缩容保护

核心原则只有一条:基于数据做决策,用自动化应对变化。当你能在故障发生前就预判到容量瓶颈并自动扩容,才算真正做到了"用工程方法管理可靠性"。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Google SRE Book - Capacity Planning — Google SRE 团队,参考了Google SRE Book - Capacity Planning相关内容