CI/CD 动态 Agent 降低 40% 计算成本

在 Jenkins 控制台 -> 节点管理 -> Pod Template 中,或者通过声明式脚本(Jenkinsfile)来配置 Kubernetes plugin:

  • Pod 生命周期管理:将 retentionStrategy(保留策略)设置为 PodCleanUp.success() 或配置 idleMinutes: 1。这意味着当流水线结束(成功/失败),K8s 会立即回收该 Pod 占用的核心资源(CPU/Memory),从而将非工作时间的“静态资源闲置率”降为 0。

  • 解决冷启动(ImagePullBackOff)延迟:动态创建 Pod 最怕每次都重新拉取几百兆的 Docker 镜像。

    • 做法:在 K8s 节点上配置 ImagePullPolicy: IfNotPresent。
    • 进阶:使用 DaemonSet 在所有 Node 节点上提前预热(缓存)好常用的基础构建镜像(如包含 Maven/Node/Go 环境的自定义镜像)。
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: image-prepuller
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: image-prepuller
  template:
    metadata:
      labels:
        name: image-prepuller
    spec:
      # 关键配置 1:永远尝试拉取,从而确保触发下载行为
      imagePullSecrets:
        - name: my-registry-secret
      containers:
      - name: prepuller
        image: ://aliyuncs.com # 你要预热的大镜像
        imagePullPolicy: Always # 强制拉取最新镜像
        # 关键配置 2:不要让它真正死循环运行,拉完镜像后让它挂起或者保持极低消耗
        command: ["sh", "-c", "echo 'Image pulled successfully'; while true; do sleep 3600; done"]
        resources:
          requests:
            cpu: "10m"     # 占用极小的资源,不影响业务
            memory: "10Mi"
  1. 全员下发:当你部署这个 DaemonSet 时,Kubernetes 控制平面会立即在集群里的每一个 Worker 节点上调度并创建一个对应的 Pod。
  2. 下载并缓存:节点上的 kubelet 发现有新 Pod 来了,就会根据 imagePullPolicy: Always 或者是本地没有该镜像,开始通过容器运行时(如 Containerd 或 Docker)向私有仓库触发下载(docker pull)。
  3. 本地落盘:镜像下载完成后,会保存在该节点的本地磁盘缓存中。
  4. 秒级启动(预热成功):后续当你的 CI/CD 流水线动态创建真正的 Jenkins Agent Pod,且刚好被调度到这个节点时,由于配置了 imagePullPolicy: IfNotPresent,K8s 发现本地已经有这个镜像了,就会直接跳过下载阶段,实现 0 秒冷启动。

监控重构,告警从 5 分钟降至 30 秒,误报率降 60%

秒级响应的底层配置 (Prometheus & Alertmanager)

  • 调整抓取频率(控制在合理范围):不能盲目把全量指标都改成 10s 抓取。针对核心系统指标(如 K8s 节点的 CPU、内存、网络 IO、业务组件的 Http Request),在 prometheus.yml 中设置独立的 scrape_interval: 10s 和 evaluation_interval: 10s。
  • Alertmanager 告警等待管道优化:
    # alertmanager.yaml 核心片段
      route:
        group_wait: 5s       # 收到第一条告警后,只等待5秒看有没有同组告警,然后立即发送
        group_interval: 1m   # 同一组告警如果触发了新的,1分钟内合并发送
        repeat_interval: 3h  # 已经发送过的告警,3小时内不再重复轰炸

降低误报率 60% 的表达式重构(防抖与降噪)

  • 旧的粗暴写法(易误报):100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100) > 90 —— 只要 CPU 一瞬间冲高超过 90% 就会立刻报警。

  • 重构后的精细化写法(降噪):

    # 使用 5分钟的滑动窗口求平均,且必须在 prometheus 中持续(for)满足 1 分钟才触发
      expr: avg_over_time(instance:node_cpu_utilization:ratio[5m]) > 0.90
      for: 1m
  • 抑制规则(Inhibition Rules):配置“当宿主机挂掉(NodeDown)时,自动抑制(挂起)该宿主机上所有 Pod 挂掉(PodMissing)的告警”,防止单点故障引发成百上千条衍生告警轰炸(告警风暴)。


慢查询优化使数据库负载下降 40%

深度排查与慢 SQL 定位

  • 开启慢日志:设置 long_query_time = 0.5(超过 0.5 秒即为慢查询),通过 pt-query-digest /var/log/mysql/mysql-slow.log 导出分析报告。
  • 发现 Top 1 的高频慢查询是一个用户订单查询接口:
    SELECT * FROM orders WHERE user_id = 10023 ORDER BY create_time DESC LIMIT 20;

具体实施三步走

  • 步骤一:执行计划分析(EXPLAIN):使用 EXPLAIN 发现该 SQL 的 type 字段为 ALL(全表扫描),或者 rows 扫描了上百万行,且 Extra 提示 Using filesort(使用了磁盘文件排序,极其消耗 CPU)。
  • 步骤二:建立联合索引(关键动作):
    -- 创建 user_id 和 create_time 的联合索引
    ALTER TABLE orders ADD INDEX idx_userid_createtime (user_id, create_time DESC);
    利用索引的有序性,直接消除了 `Using filesort`,将查询效率从秒级提升到毫秒级(`type` 变为 `ref`)。
  • 步骤三:改造为覆盖索引(避免回表):将业务层面的 SELECT * 严格修改为具体需要的字段(如 SELECT id, order_no, price FROM...),让查询直接在索引树上完成返回,不需要再去回表扫描聚簇索引,大幅降低了磁盘 I/O。
  • 步骤四:数据过期策略与 Redis 兜底:配合业务层,将超过一年的历史订单冷数据归档到冷存储中;同时针对高频访问的前几页订单数据,在应用层引入 Redis 缓存,设置 5 分钟过期时间,降低直接击穿到数据库的概率。
  • 结果:数据库主机的 CPU 利用率从经常性的 85% 飙红,稳定回落到 45% 左右,整体负载下降约 40%。

重构流水线使全链路构建交付速率提升 3 倍

  1. 痛点分析(重构前为什么慢?)
    在没有重构前,一条典型的微服务流水线需要跑 20~30 分钟,主要慢在 3 个地方:

    • 全串行执行:代码拉取->静态检查->单元测试->编译打包->镜像构建->部署,全部都在一条线上单线程排队。
    • 没有依赖缓存:每次 Maven 构建或 npm install 都是“纯净环境”,需要从公网或私服重新下载几百兆的依赖包(拉取三方件耗时极长)。
    • 单体大镜像:Dockerfile 写得不规范,没有利用好分层缓存,每次代码改动一行,整个镜像都要重新全量构建、分发、推送(几个G的镜像传输极慢)。
  2. 重构核心技术落地(怎么做到快 3 倍的?)
    通过将 Jenkinsfile 升级为声明式并发流水线,引入三大核心改造,将构建交付时间从 25 分钟压缩到了 6-7 分钟左右(提升了 3 倍多):

    改造一:并发阶段隔离(Parallel Stages)

    • 技术实现:将原本串行执行的“代码静态扫描(SonarQube)”与“单元测试(Unit Test)”利用 Jenkins 的 parallel 语法进行并发并行化处理。同时,如果涉及到多模块微服务,同时并行编译多个 jar 包。
    • 效果:原本这两个阶段各需要 4 分钟,并发后总耗时直接缩短到 4 分钟(省去 4 分钟)。

    改造二:挂载持久化依赖缓存(Cache Layer)

    • 技术实现:在 K8s 动态创建 Agent Pod 时,通过配置文件通过持久化卷(如挂载集群内的 NFS PVC,或者 K8s 宿主机的 hostPath)将 Maven 的 .m2/repository 目录或 Node 的 node_modules 持久化挂载到 Agent 容器内部。

    • Jenkinsfile字段:

        spec:
          containers:
          - name: maven
            image: maven:3.8.6-openjdk-11
            volumeMounts:
            - mountPath: /root/.m2
              name: maven-cache
          volumes:
          - name: maven-cache
            persistentVolumeClaim:
              claimName: jenkins-maven-cache-pvc
    • 效果:除第一次构建外,后续构建全部走本地缓存。Maven 编译阶段从原来的 8 分钟直降到 1.5 分钟。

    改造三:优化 Dockerfile 实现“秒级层缓存”

    • 技术实现:重写 Dockerfile,利用 Docker Layer Cache(层缓存机制)。将不经常变动的“基础环境、时区设置、依赖包”写在上面,将经常变动的“业务 Jar 包/前端代码”写在最下面。

    • 优化前:

      COPY . /app
      RUN mvn clean package # 每次代码变动,这一步都会重新下载依赖并重新编译,导致缓存失效
    • 优化后(分层多阶段构建):

        # 阶段一:仅复制 pom.xml 下载依赖并缓存
        FROM maven:3.8.6 AS build
        WORKDIR /app
        COPY pom.xml .
        RUN mvn dependency:go-offline # 这一层只要 pom.xml 没变,就永远走 Docker 缓存
        
        # 阶段二:复制源码并编译
        COPY src ./src
        RUN mvn package -DskipTests
        
        # 阶段三:运行环境
        FROM openjdk:11-jre-slim
        COPY --from=build /app/target/app.jar /app.jar
        ENTRYPOINT ["java", "-jar", "/app.jar"]
    • 效果:镜像构建和 docker push 从原来的 5 分钟,缩短到只有几秒钟(仅推送变动的 jar 包层)。


自动备份策略实现 RTO < 30分钟,RPO < 5分钟

Tip

  • RPO(Recovery Point Objective,数据恢复点目标):灾难发生后,最多能容忍丢失多少分钟的数据。简历中 <5 分钟,代表死机后最多丢 5 分钟的数据。
  • RTO(Recovery Time Objective,恢复时间目标):灾难发生后,系统从瘫痪到恢复正常需要花多少时间。简历中 <30 分钟,代表半小时内必须把数据库拉起来提供服务。

如何满足 RPO < 5分钟(不停机、高频增量):

  • 不能使用 mysqldump 这种逻辑备份工具,因为备份大表会锁表,且极其消耗 CPU。
  • 做法:生产环境开启 MySQL Binlog(ROW模式)。使用物理备份工具 Percona XtraBackup,每天凌晨 2:00 做一次“全量物理备份”;在此基础上,利用 Python 脚本配合定时任务(或分布式调度系统),每 5 分钟增量传输一次实时生成的 Binlog 日志到备用存储节点/对象存储(OSS/MinIO)。
  • 降本锁库优化:增量备份时只拷贝变化的数据块,对数据库 CPU 影响极小(通常 < 5%),因此每 5 分钟跑一次完全可行。

如何满足 RTO < 30分钟(自动化巡检与恢复演练):

  • 如果发生灾难才去临时写命令恢复,解压、导数据、追日志的时间绝对超过几小时。
  • 做法:编写 Python 脚本实现自动化恢复演练。脚本每天自动在“隔离的测试 K8s 环境”中创建临时的数据库容器,自动下载当天的全量备份文件进行解压恢复,紧接着自动顺序重放(Apply)当天每 5 分钟一段的 Binlog 增量日志。
  • 脚本在后台每天自动跑完这套流程,并记录耗时。在模拟演练中,整个流程全部自动化一键完成,耗时稳定在 15~20 分钟内。有了这个自动恢复脚本的底气,才敢在简历上写 RTO <30 分钟。