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"- 全员下发:当你部署这个 DaemonSet 时,Kubernetes 控制平面会立即在集群里的每一个 Worker 节点上调度并创建一个对应的 Pod。
- 下载并缓存:节点上的
kubelet发现有新 Pod 来了,就会根据imagePullPolicy: Always或者是本地没有该镜像,开始通过容器运行时(如 Containerd 或 Docker)向私有仓库触发下载(docker pull)。 - 本地落盘:镜像下载完成后,会保存在该节点的本地磁盘缓存中。
- 秒级启动(预热成功):后续当你的 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)。 - 步骤二:建立联合索引(关键动作):
利用索引的有序性,直接消除了 `Using filesort`,将查询效率从秒级提升到毫秒级(`type` 变为 `ref`)。-- 创建 user_id 和 create_time 的联合索引 ALTER TABLE orders ADD INDEX idx_userid_createtime (user_id, create_time DESC); - 步骤三:改造为覆盖索引(避免回表):将业务层面的
SELECT *严格修改为具体需要的字段(如SELECT id, order_no, price FROM...),让查询直接在索引树上完成返回,不需要再去回表扫描聚簇索引,大幅降低了磁盘 I/O。 - 步骤四:数据过期策略与 Redis 兜底:配合业务层,将超过一年的历史订单冷数据归档到冷存储中;同时针对高频访问的前几页订单数据,在应用层引入 Redis 缓存,设置 5 分钟过期时间,降低直接击穿到数据库的概率。
- 结果:数据库主机的 CPU 利用率从经常性的 85% 飙红,稳定回落到 45% 左右,整体负载下降约 40%。
重构流水线使全链路构建交付速率提升 3 倍
-
痛点分析(重构前为什么慢?)
在没有重构前,一条典型的微服务流水线需要跑 20~30 分钟,主要慢在 3 个地方:- 全串行执行:代码拉取->静态检查->单元测试->编译打包->镜像构建->部署,全部都在一条线上单线程排队。
- 没有依赖缓存:每次 Maven 构建或 npm install 都是“纯净环境”,需要从公网或私服重新下载几百兆的依赖包(拉取三方件耗时极长)。
- 单体大镜像:Dockerfile 写得不规范,没有利用好分层缓存,每次代码改动一行,整个镜像都要重新全量构建、分发、推送(几个G的镜像传输极慢)。
-
重构核心技术落地(怎么做到快 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 包层)。
- 技术实现:将原本串行执行的“代码静态扫描(SonarQube)”与“单元测试(Unit Test)”利用 Jenkins 的
自动备份策略实现 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 分钟。