找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3726

积分

0

好友

496

主题
发表于 1 小时前 | 查看: 4| 回复: 0

很多团队把 Kubernetes 当成“部署平台”,真正进了生产才会发现,它更像一个分布式系统操作系统。Pod 重启、节点漂移、滚动发布、自动扩缩容、服务发现、资源隔离、故障转移,这些能力如果只是“会配 YAML”,系统最多算是跑起来;只有把控制面原理、应用架构、容量治理、发布策略、观测体系和工程约束串成闭环,Kubernetes 才会从“容器平台”变成“自愈底座”。

本文不讲 Hello World,也不只讲几个 Deployment 配置片段,而是围绕一个真实感很强的生产故障案例,系统回答四个问题:

  • Kubernetes 的自愈到底是怎么工作的,边界又在哪里
  • 微服务在高并发场景下,怎样才能做到不因单点阻塞演化成全链路雪崩
  • 生产级 K8s 应用应该怎么设计探针、扩缩容、发布、隔离和观测
  • 一套“能跑”的系统,怎样演进成“扛峰值、可扩展、可回滚、可治理”的工程体系

为了让讨论足够具体,全文统一使用一个电商供应链场景作为主线:

商品服务、库存调度服务、订单履约服务、物流服务和风控服务运行在 Kubernetes 集群中。日常订单处理量在百万级,大促峰值入口流量会放大 8 到 12 倍,库存锁定接口是全链路里最敏感的核心路径。


一、从一次真实的雪崩说起

凌晨两点,值班电话响起。大盘上 CPU 不高、节点负载正常、Pod 也没大面积重启,但商品上下架失败、订单创建超时、履约回调积压,业务已经基本不可用。

复盘这次事故,现场现象很典型:

  • inventory-scheduler 平均 RT 从 40ms 飙升到 8s 以上
  • 数据库连接池耗尽,请求线程大量阻塞
  • 上游 order-service 因同步调用超时被拖死,Tomcat 工作线程打满
  • Kafka 消费 lag 持续累积,异步补偿链路也逐步失效
  • HPA 没有及时扩容,因为 CPU 使用率并不高
  • Liveness Probe 误杀了一批本就繁忙的实例,放大了抖动

这类故障最值得警惕的一点在于:

集群没坏,机器没满,Pod 也没全挂,但系统已经不可用。

根因通常不在单一组件,而在“基础设施自愈”和“业务架构韧性”之间断层了。把事故拆开,基本会落到下面几类问题。

1.1 第一层问题:把同步调用链做得太长

很多业务一开始图省事,接口设计是这样的:

  1. 订单服务同步调用库存锁定
  2. 库存服务同步访问数据库
  3. 库存服务同步写缓存
  4. 库存服务同步发消息
  5. 全部成功才返回

这条链路在低流量时很顺,在峰值时就会变成串联放大器。只要其中一个环节响应变慢,所有上游线程都会排队,最终形成线程池耗尽、连接池耗尽、队列积压和超时重试风暴。

1.2 第二层问题:把 K8s 自愈误解成系统自愈

Kubernetes 可以做这些事:

  • Pod 进程退出后自动拉起
  • 节点宕机后把副本调度到别处
  • 根据指标自动增减实例
  • 在发布时渐进替换旧版本

但 Kubernetes 做不了这些事:

  • 理解你的数据库连接池已经打满
  • 理解你的线程池已经阻塞
  • 理解某个外部依赖出现高延迟,需要限流或熔断
  • 理解你的业务必须降级为“只读库存”而不是继续强推写请求

也就是说,K8s 的自愈是基础设施层自愈,不是业务层自愈。如果应用本身没有隔离、熔断、超时、削峰、补偿和降级设计,Pod 再多、节点再健康,也只是把错误复制得更快。

1.3 第三层问题:监控只看资源,不看系统状态

很多团队的观测体系只有:

  • CPU
  • 内存
  • 磁盘
  • Pod 重启次数

这远远不够。真正能提前暴露风险的往往是:

  • 数据库连接池使用率
  • 线程池活跃线程数和队列长度
  • 外部依赖超时率
  • Kafka lag
  • 接口 P95/P99 延迟
  • 熔断器打开次数
  • 限流丢弃请求数
  • 每个下游依赖的错误分布

所以这篇文章的目标不是“把 K8s 配起来”,而是把一个生产系统从“容易崩”升级到“可观测、可隔离、可恢复、可治理”。


二、先把底层原理讲透:Kubernetes 为什么能自愈

理解 Kubernetes,最重要的不是记对象,而是理解它的工作模型:

声明式期望状态 + 控制循环 + 最终一致性收敛

2.1 声明式的本质:你描述终态,不描述步骤

例如你提交一个 Deployment:

spec:
  replicas: 4
  template:
    spec:
      containers:
        - name: inventory-scheduler
          image: registry.example.com/inventory-scheduler:v2.4.0

你并没有告诉 Kubernetes:

  • 先创建哪台机器
  • 先拉哪个镜像
  • 先启动哪一个 Pod
  • 挂了以后怎么补

你只是声明:“我希望永远有 4 个符合模板的 Pod 存在。”

接下来由控制器持续观察实际状态和期望状态之间的差异,然后不断收敛。这就是 Kubernetes 整套系统的核心哲学。

2.2 控制面的工作链路

从工程视角看,一次“提交 YAML 到运行成功”的链路大致如下:

kubectl/apply
    |
    v
API Server
    |
    v
etcd (存储期望状态)
    |
    v
Controller Manager --------> 各类 Controller 持续对账
    |
    v
Scheduler 为待调度 Pod 选择 Node
    |
    v
Kubelet 在目标节点创建容器
    |
    v
Container Runtime 拉镜像并启动进程

这里有三个关键角色。

2.3 API Server:控制面的统一入口

API Server 的职责是:

  • 接收所有声明式请求
  • 做认证、鉴权、准入校验
  • 把对象持久化到 etcd
  • 对外提供 watch 机制,让控制器感知对象变更

它不是“配置中心”那么简单,而是整个控制面的事务入口。

2.4 Controller:持续对账的自动化执行者

Controller 的逻辑几乎都可以抽象成一句话:

如果实际状态和期望状态不一致,就采取动作让它们重新一致。

例如:

  • Deployment Controller 发现副本少了,就补 Pod
  • Node Controller 发现节点失联,就驱逐副本
  • EndpointSlice Controller 发现 Pod Ready 了,就把它加入服务后端
  • HPA Controller 发现指标超过阈值,就调整副本数

这就是 Kubernetes 自愈的本质。它不是“发现异常就报警”,而是“发现偏差就尝试纠偏”。

2.5 Scheduler:不是“找空闲机器”那么简单

调度过程通常分成两步:

  1. Filter:先筛掉不满足约束的节点
  2. Score:再对可选节点打分,选最优节点

Filter 关注的是“能不能放”:

  • CPU/内存/GPU 是否足够
  • taint/toleration 是否匹配
  • nodeSelector、nodeAffinity 是否满足
  • volume 是否能挂载
  • topology spread 是否满足

Score 关注的是“放哪更合适”:

  • 资源是否均衡
  • 是否和已有副本过于集中
  • 是否更接近依赖
  • 是否满足亲和性或反亲和性偏好

如果你把调度理解成“随机选个节点”,很多生产事故都解释不通。

2.6 Kubelet 与探针:自愈链路的最后一跳

Kubelet 是节点上的执行代理,负责:

  • 感知 Node 和 Pod 状态
  • 调用容器运行时启动/停止容器
  • 执行 liveness/readiness/startup probe
  • 上报状态给 API Server

很多团队的误区是把探针当成“有就行”的模板配置。实际上探针直接决定了:

  • 一个实例何时对外接流量
  • 一个繁忙实例是否会被误判成故障
  • 应用冷启动时是否会被过早拉流量
  • 发布和扩容时是否会引入瞬时错误

探针配置错了,自愈就会变成“自杀”。


三、Kubernetes 的边界:为什么它不能替你解决架构问题

这一节很关键,因为很多架构误判都来自能力边界不清。

3.1 K8s 解决的是基础设施编排,不是业务复杂度

Kubernetes 擅长的是:

  • 统一调度与生命周期管理
  • 资源隔离与多租户承载
  • 服务发现与网络抽象
  • 发布、回滚和自动扩缩容
  • 声明式运维与平台标准化

Kubernetes 不擅长的是:

  • 业务事务补偿
  • 跨服务一致性
  • 接口幂等设计
  • 数据模型设计
  • 调用链治理
  • 慢 SQL 优化
  • 线程池/连接池治理

所以正确的工程方法应该是:

让 Kubernetes 负责“把系统稳定地运行起来”,让应用架构负责“即使依赖波动也不至于雪崩”。

3.2 一张图看清“平台韧性”和“业务韧性”

┌───────────────────────────────┐
│      业务韧性(应用负责)      │
│ 超时、重试、幂等、熔断、降级   │
│ 削峰填谷、补偿、异步解耦       │
└───────────────────────────────┘
              ↑
              │
┌───────────────────────────────┐
│     平台韧性(K8s/平台负责)   │
│ 调度、自愈、扩缩容、发布、隔离 │
│ 观测、资源治理、集群高可用     │
└───────────────────────────────┘

只有上下两层一起成立,系统才真的稳。


四、生产级总体架构:从“服务部署”升级为“系统工程”

下面给出一套适合高并发供应链场景的推荐架构。重点不是具体产品必须一模一样,而是每一层的职责要清晰。

                    ┌──────────────────────┐
                    │  CDN / WAF / SLB     │
                    └──────────┬───────────┘
                               │
                               v
                    ┌──────────────────────┐
                    │ API Gateway / Ingress│
                    │ APISIX / Nginx       │
                    └──────────┬───────────┘
                               │
                 ┌─────────────┴─────────────┐
                 │                           │
                 v                           v
      ┌────────────────────┐      ┌────────────────────┐
      │  核心同步服务集群   │      │   异步消费服务集群  │
      │ order/inventory    │      │ stock-event-worker │
      └─────────┬──────────┘      └─────────┬──────────┘
                │                           │
                v                           v
      ┌────────────────────┐      ┌────────────────────┐
      │   Service Mesh      │      │ Kafka / RocketMQ   │
      │ timeout/retry/cb    │      │ 削峰、解耦、重放    │
      └─────────┬──────────┘      └────────────────────┘
                │
      ┌─────────┼───────────────────────────────────┐
      │         │                                   │
      v         v                                   v
┌──────────┐ ┌──────────┐                    ┌──────────┐
│ Redis    │ │ MySQL    │                    │ ES/OLAP  │
│ cache    │ │ Tx data  │                    │ analytics│
└──────────┘ └──────────┘                    └──────────┘
                │
                v
      ┌──────────────────────────────────────┐
      │ Kubernetes Cluster                   │
      │ HPA / PDB / Affinity / NetPolicy     │
      │ Argo CD / Prometheus / Loki / Tempo  │
      └──────────────────────────────────────┘

4.1 这套架构的核心原则

  • 同步链路只保留最必要的强一致操作
  • 非关键后处理能力尽量异步化
  • 核心服务必须有熔断、限流和隔离
  • 发布策略、扩缩容策略和容量模型要配套设计
  • 业务指标、依赖指标、资源指标三类观测同时建立

4.2 为什么库存锁定要拆成“同步最小闭环 + 异步扩展链路”

库存锁定接口最常见的错误是“顺手做太多事”:

  • 锁库存
  • 记录操作日志
  • 写审计
  • 发消息
  • 刷缓存
  • 通知风控
  • 同步第三方仓储

正确思路应该是:

  1. 同步链路只做最小必要动作
  2. 后续扩展行为通过可靠事件异步触发

这样才能在峰值时把 RT 和故障半径控制住。


五、技术选型建议:关注的是职责匹配,不是流行度

领域 推荐方案 适用原因
集群发行版 原生 Kubernetes 1.29+ 或云厂商托管版 稳定、生态完整、升级路径清晰
Ingress / Gateway APISIX、Nginx Ingress、Gateway API 入口治理成熟,支持限流、鉴权、灰度
服务治理 Istio 或基于 Gateway/Sidecar 的轻量治理 适合做超时、重试、熔断和金丝雀
消息队列 Kafka + Operator 高吞吐、可重放、适合事件驱动解耦
缓存 Redis Cluster 热点保护、读写削峰
数据库 MySQL 主从/分片或云数据库 主交易事实存储仍建议落在 RDBMS
观测 Prometheus + Grafana + Loki + Tempo/Jaeger 指标、日志、链路统一
GitOps Argo CD / Flux 收敛配置漂移,支持回滚
节点弹性 Karpenter / Cluster Autoscaler 峰值扩容和成本治理
CNI Cilium eBPF 能力强,NetworkPolicy 和观测更细

如果团队尚未具备 Service Mesh 经验,也可以先在应用层实现 Resilience4j,再逐步把一部分治理能力下沉到网格层,不必一开始就“大一统”。


六、生产级 YAML:不是能部署就算完

这一节给出一套接近生产实践的库存调度服务配置。重点不是字段多少,而是每一项配置都服务于某个工程目标。

6.1 Deployment:把可用性、启动行为和调度约束写清楚

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inventory-scheduler
  namespace: supply-chain
  labels:
    app: inventory-scheduler
    tier: core
spec:
  replicas: 4
  revisionHistoryLimit: 10
  minReadySeconds: 20
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: inventory-scheduler
  template:
    metadata:
      labels:
        app: inventory-scheduler
        version: v2-4-0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/actuator/prometheus"
    spec:
      serviceAccountName: inventory-scheduler
      terminationGracePeriodSeconds: 60
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: inventory-scheduler
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                topologyKey: kubernetes.io/hostname
                labelSelector:
                  matchLabels:
                    app: inventory-scheduler
      containers:
        - name: inventory-scheduler
          image: registry.example.com/supply-chain/inventory-scheduler:v2.4.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: "1000m"
              memory: "2Gi"
            limits:
              cpu: "2"
              memory: "4Gi"
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "prod"
            - name: JAVA_TOOL_OPTIONS
              value: >-
                -XX:+UseContainerSupport
                -XX:InitialRAMPercentage=40.0
                -XX:MaxRAMPercentage=70.0
                -XX:+ExitOnOutOfMemoryError
                -XX:+HeapDumpOnOutOfMemoryError
            - name: DB_URL
              valueFrom:
                secretKeyRef:
                  name: inventory-db-secret
                  key: url
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: inventory-db-secret
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: inventory-db-secret
                  key: password
            - name: REDIS_ADDR
              value: "redis-cluster.supply-chain.svc.cluster.local:6379"
            - name: KAFKA_BOOTSTRAP_SERVERS
              value: "kafka-bootstrap.kafka.svc.cluster.local:9092"
          startupProbe:
            httpGet:
              path: /actuator/health/startup
              port: 8080
            periodSeconds: 10
            failureThreshold: 18
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          lifecycle:
            preStop:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - "sleep 15"

6.2 Service、PDB 和 HPA:保证流量接入和副本稳定

---
apiVersion: v1
kind: Service
metadata:
  name: inventory-scheduler
  namespace: supply-chain
spec:
  selector:
    app: inventory-scheduler
  ports:
    - name: http
      port: 80
      targetPort: 8080

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: inventory-scheduler-pdb
  namespace: supply-chain
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: inventory-scheduler

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: inventory-scheduler
  namespace: supply-chain
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: inventory-scheduler
  minReplicas: 4
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 65
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
        - type: Pods
          value: 4
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 120

这里有几个真正影响生产效果的点:

  • startupProbe 把冷启动阶段和存活检测分离,避免慢启动应用被误杀
  • minReadySeconds 防止刚就绪就立刻参与滚动替换,降低抖动
  • preStop + terminationGracePeriodSeconds 给摘流和在途请求留时间
  • PDB 避免节点维护、升级、弹性收缩时把关键副本一次性赶下线
  • HPA behavior 显式约束扩缩容节奏,避免抖动

6.3 VPA 的正确姿势:先观察,再自动

很多团队一上来就把 VPA 设为 Auto,结果高峰期反复重启。更稳妥的方式是先用 Off 模式收集建议值。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: inventory-scheduler
  namespace: supply-chain
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: inventory-scheduler
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
      - containerName: inventory-scheduler
        minAllowed:
          cpu: "500m"
          memory: "1Gi"
        maxAllowed:
          cpu: "4"
          memory: "8Gi"

经验上:

  • HPA 管副本数
  • VPA 先做推荐
  • 如果要自动调资源,尽量和 HPA 用不同维度指标,避免控制器打架

6.4 NetworkPolicy:把“默认全通”改成“最小可用”

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: inventory-scheduler-policy
  namespace: supply-chain
spec:
  podSelector:
    matchLabels:
      app: inventory-scheduler
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: order-service
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: supply-chain
          podSelector:
            matchLabels:
              app: redis-cluster
      ports:
        - protocol: TCP
          port: 6379
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kafka
      ports:
        - protocol: TCP
          port: 9092

在大多数真实集群里,安全问题不是来自“有没有高深攻击”,而是来自“服务之间默认全通”。


七、应用层必须跟上:生产级代码不是把接口写通

Kubernetes 只解决“进程如何运行”,并不保证你的服务在高并发下稳定。下面给出一个更接近生产的库存锁定设计。

7.1 先看错误示范:同步大而全接口

public LockResult lockStock(LockRequest request) {
    inventoryRepository.lock(request);
    cacheService.refresh(request.getSkuId());
    kafkaTemplate.send("stock-events", request);
    auditService.record(request);
    return LockResult.success();
}

这段代码的问题不是“风格不好”,而是把数据库、缓存、消息和审计全部绑在一条同步链路里,任何一个依赖抖动都会扩大故障面。

7.2 改造目标:同步做强一致闭环,异步做扩展行为

更稳妥的分层应该是:

  • 同步链路:参数校验、幂等校验、库存扣减、事务落库、Outbox 事件落表
  • 异步链路:发 MQ、刷新缓存、审计、风控、下游通知

7.3 生产级 Spring Boot 示例

下面是一段简化但具备生产思路的代码。它展示了幂等、防重、事务边界、超时控制和事件解耦。

package com.example.inventory.application;

import com.example.inventory.domain.InventoryLockCommand;
import com.example.inventory.domain.InventoryLockResult;
import com.example.inventory.domain.InventoryService;
import com.example.inventory.domain.OutboxEvent;
import com.example.inventory.infrastructure.IdempotencyRepository;
import com.example.inventory.infrastructure.OutboxRepository;
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import jakarta.validation.Valid;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class InventoryApplicationService {

    private final InventoryService inventoryService;
    private final IdempotencyRepository idempotencyRepository;
    private final OutboxRepository outboxRepository;

    @Transactional
    @CircuitBreaker(name = "inventoryLock")
    @Bulkhead(name = "inventoryLock")
    public InventoryLockResult lock(@Valid InventoryLockCommand command) {
        String idemKey = command.orderNo() + ":" + command.skuId();
        InventoryLockResult cached = idempotencyRepository.findResult(idemKey);
        if (cached != null) {
            return cached;
        }

        InventoryLockResult result = inventoryService.lock(command);
        idempotencyRepository.saveResult(idemKey, result);

        OutboxEvent event = new OutboxEvent(
                UUID.randomUUID().toString(),
                "inventory.locked",
                command.orderNo(),
                result.toJson(),
                Instant.now()
        );
        outboxRepository.save(event);
        return result;
    }

    @TimeLimiter(name = "inventoryQuery")
    public CompletableFuture<Integer> queryAvailableStock(String skuId) {
        return CompletableFuture.supplyAsync(() -> inventoryService.availableStock(skuId));
    }
}

这段代码背后的工程思想有四个。
第一,幂等不是可选项。订单系统和消息系统都会重试,没有幂等,库存一定出错。
第二,Outbox 模式把“本地事务成功”和“事件可靠发送”拆成两个阶段,避免数据库提交成功但消息没发出去。
第三,CircuitBreakerBulkheadTimeLimiter 不是装饰品,它们决定了下游变慢时是不是会把整个线程池拖死。
第四,同步链路要短,能异步的一律异步。

7.4 Outbox 消费者:可靠发送而不是“发一下试试”

package com.example.inventory.worker;

import com.example.inventory.infrastructure.OutboxEvent;
import com.example.inventory.infrastructure.OutboxRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class OutboxPublisher {

    private final OutboxRepository outboxRepository;
    private final KafkaTemplate<String, String> kafkaTemplate;

    @Scheduled(fixedDelayString = "${inventory.outbox.publish-interval-ms:500}")
    public void publish() {
        List<OutboxEvent> events = outboxRepository.findTop100Unpublished();
        for (OutboxEvent event : events) {
            try {
                kafkaTemplate.send("inventory-events", event.aggregateId(), event.payload()).get();
                outboxRepository.markPublished(event.id());
            } catch (Exception ex) {
                log.warn("publish outbox failed, eventId={}", event.id(), ex);
                outboxRepository.increaseRetry(event.id(), ex.getMessage());
            }
        }
    }
}

在生产环境里,这里通常还会继续补上:

  • 分批拉取
  • 重试退避
  • 死信队列
  • 最大重试次数
  • 告警阈值
  • 发布延迟监控

7.5 线程池和连接池:高并发系统最容易被忽视的水位线

很多高并发问题根本不是 CPU 不够,而是池子打满了。
一个更可靠的配置思路如下:

server:
  tomcat:
    threads:
      max: 400
      min-spare: 40
    accept-count: 1000

spring:
  datasource:
    hikari:
      maximum-pool-size: 80
      minimum-idle: 20
      connection-timeout: 800
      validation-timeout: 500
      leak-detection-threshold: 5000

resilience4j:
  bulkhead:
    instances:
      inventoryLock:
        max-concurrent-calls: 120
        max-wait-duration: 10ms
  thread-pool-bulkhead:
    instances:
      inventoryQuery:
        core-thread-pool-size: 16
        max-thread-pool-size: 32
        queue-capacity: 200

这里的重点不是数值本身,而是要有一套容量关系模型。例如:

  • Web 线程池不能远大于数据库连接池,否则大量线程只会空等连接

  • Bulkhead 的并发阈值要低于下游可承受能力

  • 队列长度要可控,宁可快速失败,也不要无限排队

    • *

八、服务治理:高并发下不做隔离,扩容只会把问题放大

8.1 为什么“超时、重试、熔断、限流”必须成套出现

只配重试,不配超时,结果是请求越积越多。
只配超时,不配熔断,结果是每次都慢性拖死。
只配熔断,不配限流,结果是入口流量仍会压垮剩余健康实例。
只配限流,不配降级,结果是业务只能直接失败。

正确顺序通常是:

  1. 先设超时,确保请求不会无限等待
  2. 再设隔离和并发上限,限制故障传播
  3. 再设熔断,快速切断不健康依赖
  4. 最后配重试,而且只对明确可重试场景生效

8.2 Istio DestinationRule 示例

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: inventory-scheduler
  namespace: supply-chain
spec:
  host: inventory-scheduler.supply-chain.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 200
        connectTimeout: 300ms
      http:
        http1MaxPendingRequests: 100
        maxRequestsPerConnection: 50
        maxRetries: 2
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

8.3 VirtualService 灰度发布示例

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: inventory-scheduler
  namespace: supply-chain
spec:
  hosts:
    - inventory-scheduler
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: inventory-scheduler
            subset: v2
    - route:
        - destination:
            host: inventory-scheduler
            subset: v1
          weight: 90
        - destination:
            host: inventory-scheduler
            subset: v2
          weight: 10

对于核心服务,最稳妥的上线顺序通常是:

  1. 先按 header 或内部流量做定向验证
  2. 再从 1% 到 5% 到 10% 小流量放量
  3. 观察错误率、RT、连接池和 JVM 指标
  4. 满足阈值后继续放量,否则立即回退

九、扩缩容不能只看 CPU:高并发系统更要关注业务指标

很多实际故障里,CPU 并不高,但系统已经很慢了。原因是瓶颈可能在:

  • 数据库连接池
  • Redis RT
  • 外部依赖
  • JVM GC
  • 下游线程池
  • 网关排队

所以对核心链路,更推荐让 HPA 同时参考业务指标。

9.1 基于自定义指标的 HPA 思路

例如库存调度服务可以参考:

  • 单实例 QPS
  • P95 RT
  • 每秒锁库存请求数
  • Kafka lag

示意配置如下:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: inventory-scheduler-by-qps
  namespace: supply-chain
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: inventory-scheduler
  minReplicas: 4
  maxReplicas: 30
  metrics:
    - type: Pods
      pods:
        metric:
          name: http_server_requests_per_second
        target:
          type: AverageValue
          averageValue: "120"

9.2 容量模型要先于 HPA

不要把 HPA 当成万能救火器。它只是自动执行策略,不替你做容量建模。
一个基础容量模型至少要回答:

  • 单实例稳定 QPS 上限是多少
  • 单实例在 P95 目标下的最大并发是多少
  • 数据库和缓存能支撑多少并发扇出
  • 峰值会持续多久
  • 扩容需要多少时间完成
  • 是否需要预热实例

举例来说,如果一个实例在稳定状态下可承受 100 QPS,峰值预计 1200 QPS,那么副本数不是简单设成 12,而要考虑:

  • 发布中的冗余
  • 节点故障时的冗余
  • HPA 触发滞后
  • 冷启动期间不可用副本

工程上通常会再加 20% 到 40% 的安全余量。


十、可观测性升级:从“监控机器”到“监控系统”

一套成熟的云原生观测体系通常分三层。

10.1 第一层:资源指标

  • Node CPU、内存、磁盘、网络
  • Pod 重启次数
  • 容器 OOMKilled
  • 节点不可调度数

10.2 第二层:应用指标

  • 请求量、成功率、错误率
  • P50/P95/P99 延迟
  • 线程池活跃数、队列长度
  • 连接池占用率
  • JVM GC、堆外内存、类加载数
  • MQ lag

10.3 第三层:业务指标

  • 库存锁定成功率
  • 订单创建成功率
  • 库存回补延迟
  • 幂等冲突率
  • 补偿任务积压量

真正能指导故障定位的,是把三层数据串起来。

10.4 Prometheus 告警规则示例

groups:
  - name: inventory-alerts
    rules:
      - alert: InventorySchedulerHighLatency
        expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{uri="/api/inventory/lock"}[5m])) by (le)) > 0.3
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "inventory-scheduler P95 latency > 300ms"

      - alert: InventoryDbPoolNearlyExhausted
        expr: hikari_connections_active / hikari_connections_max > 0.85
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "database pool usage exceeds 85%"

      - alert: InventoryKafkaLagHigh
        expr: kafka_consumergroup_lag{consumergroup="inventory-worker"} > 10000
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "inventory worker lag is too high"

好的告警应该满足三件事:

  • 能说明影响范围

  • 能提示最可能的瓶颈

  • 能触发明确动作,而不是只会制造焦虑

    • *

十一、发布策略升级:从滚动更新到可验证灰度

很多团队以为 kubectl apply 就算发布,这在低风险系统里也许够,在核心链路里远远不够。

11.1 四种常见发布方式

方式 优点 风险
Recreate 简单 有中断,不适合核心服务
RollingUpdate 默认方案,成本低 新版本问题可能逐步扩散
Blue/Green 回滚快,切换清晰 资源成本较高
Canary 风险最可控 需要流量治理和观测配合

11.2 核心服务推荐策略

对库存、支付、订单这类服务,更推荐:

  1. 蓝绿作为大版本切换兜底
  2. 金丝雀作为小流量验证主手段
  3. 发布前先跑合成流量和只读探测
  4. 发布期间观测错误率、延迟、连接池和业务指标
  5. 回滚标准要提前写死,不能靠临场判断

11.3 一个简单但有效的回滚标准

例如可以明确:

  • 5 分钟内 P95 延迟升高超过 30%
  • 错误率高于基线 2 倍
  • 数据库连接池持续超过 90%
  • 订单创建成功率低于 99.5%

满足任一条件立即暂停放量并回滚。


十二、配置管理与 GitOps:别让集群越跑越分叉

当环境从 1 个集群变成 5 个、10 个以后,真正难的不是部署,而是防止配置漂移。

12.1 为什么手工改集群一定会失控

常见场景:

  • 线上临时 kubectl edit
  • 某个集群单独改了资源配额
  • 某个命名空间多了一份“临时修复版” ConfigMap
  • 测试环境和生产环境的探针、HPA、PDB 逐渐不一致

久而久之,你会发现每个集群都“差不多”,但没有两个集群真的一样。

12.2 推荐做法:Git 是唯一真相源

一个简化目录结构示意如下:

platform-gitops/
├── base/
│   ├── inventory-scheduler/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── hpa.yaml
│   │   └── pdb.yaml
├── overlays/
│   ├── test/
│   ├── staging/
│   └── prod/
└── applications/
    └── argocd/

建议原则:

  • 基础配置抽到 base

  • 环境差异放 overlay

  • 不允许绕过 Git 直接改生产

  • 所有变更都要能追溯到 PR

    • *

十三、常见生产坑与治理建议

13.1 OOMKilled:不是“加内存”这么简单

常见原因包括:

  • JVM 堆设置和容器 limit 不匹配
  • 堆外内存、DirectBuffer、线程栈没预留
  • 批处理对象瞬时堆积
  • 业务缓存没有上限

治理建议:

  • 显式配置 MaxRAMPercentage
  • 对大对象和批量任务做限流
  • 把 Heap Dump 和 OOM 告警打通
  • 用 p95/p99 而不是平均值做资源评估

13.2 Probe 误杀

常见错误:

  • 启动很慢却没有 startupProbe
  • livenessProbe 依赖数据库或外部接口
  • 把暂时繁忙当成“进程死亡”

治理建议:

  • 存活探针只判断进程是否卡死
  • 就绪探针判断是否可以接流量
  • 启动探针单独覆盖冷启动窗口

13.3 HPA 抖动

表现通常是副本来回扩缩,业务波动反而更大。治理建议:

  • 扩容窗口短一点,缩容窗口长一点
  • 不要只看 CPU
  • 为核心服务保留基线副本
  • 冷启动慢的应用要做预热

13.4 数据库才是真瓶颈,但扩的是应用 Pod

这类问题特别常见。应用 Pod 翻倍了,数据库连接池和 IO 没变,结果只是更快把数据库打满。治理建议:

  • 先做链路容量短板识别
  • 对数据库操作做缓存、批量、索引优化
  • 对热点库存做分片或令牌化削峰

13.5 配置中心和 Secret 管理混乱

建议把:

  • 非敏感业务配置放 ConfigMap

  • 口令、证书、密钥放 Secret 或外部密钥系统

  • 加密材料统一轮转

  • 应用不要把明文密码打到日志里

    • *

十四、AI/批处理工作负载:为什么默认调度器有时不够

虽然本文主线是微服务,但现在很多企业会在同一个 K8s 平台承载在线服务、离线批处理和 AI 作业,所以这部分必须补一笔。

默认调度器擅长长期运行的无状态服务,但在分布式训练、批量作业场景下,会面临两个问题:

  • Pod 之间存在强协同,需要成组启动
  • GPU、NUMA、拓扑亲和会显著影响性能

这时就需要像 Volcano 这样的批调度能力。

apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: llm-training
spec:
  minAvailable: 8
  schedulerName: volcano
  queue: ai-training
  tasks:
    - name: master
      replicas: 1
      template:
        spec:
          containers:
            - name: trainer
              image: registry.example.com/ai/torch-trainer:2.1
              resources:
                limits:
                  nvidia.com/gpu: 4
    - name: worker
      replicas: 7
      template:
        spec:
          containers:
            - name: trainer
              image: registry.example.com/ai/torch-trainer:2.1
              resources:
                limits:
                  nvidia.com/gpu: 4

minAvailable 的意义就在于:资源不够就别半吊子启动,避免一部分 Pod 启动成功、另一部分永远排队,最终谁都跑不起来。


十五、从单体到云原生的落地路线图

如果你的系统还在从传统架构向 K8s 迁移,不建议一步到位做成“全家桶”。更现实的路径通常是分阶段推进。

阶段 1:容器化标准化

  • 收敛 Dockerfile
  • 统一日志输出和健康检查
  • 建立镜像构建与漏洞扫描流程

阶段 2:无状态服务先上 K8s

  • Deployment、Service、Ingress 跑通
  • ConfigMap、Secret、Probe、HPA 基础能力到位
  • 接入 Prometheus 和日志系统

阶段 3:核心链路治理

  • 识别关键同步链路
  • 引入超时、熔断、限流、隔离
  • 事件驱动解耦
  • 补齐幂等和补偿

阶段 4:发布与运维体系化

  • 金丝雀发布
  • GitOps
  • 容量模型
  • 混沌演练
  • 自动化回滚

阶段 5:多集群与多负载统一治理

  • 多环境一致性

  • 多 AZ 容灾

  • 批处理和 AI 统一接入

  • 成本治理和资源分级

    • *

十六、一次完整的故障处置闭环应该长什么样

为了把前面的能力串起来,最后给出一个更接近实战的处置闭环。

假设大促期间库存锁定接口抖动,推荐动作顺序如下:

  1. 先确认业务影响面:订单成功率、库存锁定成功率、入口错误率
  2. 再看瓶颈位置:数据库连接池、线程池、Redis RT、Kafka lag
  3. 如果下游明显变慢,立即启用入口限流和依赖熔断
  4. 暂停非关键异步消费者,给核心链路让资源
  5. 检查 HPA 是否扩容到位,必要时手工扩容
  6. 如果新版本刚发布,按预设阈值快速回滚
  7. 故障稳定后回放积压消息并核对补偿结果
  8. 复盘时更新探针、阈值、容量和演练剧本

真正成熟的系统,不是“不出故障”,而是:

  • 故障来时能快速止血

  • 止血后能快速恢复

  • 恢复后能把经验沉淀成平台能力

    • *

十七、文章小结:从崩溃到自愈,靠的不是某一个 YAML

Kubernetes 真正的价值,从来不只是“把容器跑起来”。它的核心能力是把分布式系统的运行、调度、发布、恢复和治理抽象成统一的控制平面,让团队可以用声明式方式管理复杂系统。

但要把这种能力转化成生产稳定性,必须补上另外半张图:

  • 架构上,要缩短同步链路,强化异步解耦
  • 工程上,要配置好探针、PDB、HPA、反亲和和网络隔离
  • 应用上,要补齐超时、熔断、幂等、限流、补偿和线程池治理
  • 运维上,要建立 GitOps、灰度发布、容量模型和观测体系
  • 组织上,要通过故障演练和复盘,把经验固化为平台标准

一句话总结:

Pod 会重启,节点会漂移,流量会波动,版本会回滚,但一个系统能否真正从崩溃走向自愈,最终取决于你是否把“平台韧性”和“业务韧性”一起建设起来。

如果只把 Kubernetes 当成部署工具,它最多帮你把故障恢复得快一点;如果把它和架构治理、发布治理、容量治理、观测治理一起做,它才会成为企业分布式系统真正的工程底座。




上一篇:SKILL.md 实战:从零构建 Markdown 转 PDF 简历生成器
下一篇:分布式 MySQL 慢日志采集分析平台搭建
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-10 04:27 , Processed in 0.643922 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表