当前位置: 首页 > news >正文

容器镜像漏洞扫描拖垮 CI:我把构建时间从 20 分钟压回 3 分钟的排查实录

容器镜像漏洞扫描拖垮 CI:我把构建时间从 20 分钟压回 3 分钟的排查实录

上周三下午,我正准备把一个新功能合并到主分支,CI 突然跑了一个多小时还没结束。平时 3 分钟的构建,硬生生拖到了 20 分钟。翻了一圈日志,罪魁祸首是两周前接入的容器镜像漏洞扫描。

说实话,安全团队的初衷是好的。但把扫描直接塞进构建流程,没做任何优化,这跟在高速公路入口设检查站检查每辆车的底盘没啥区别——堵是必然的。

问题是怎么发现的

事情要从安全合规说起。公司安全团队要求所有上线镜像必须通过漏洞扫描,Severity High 及以上必须清零。我花了一个下午把 Trivy 接进了 GitLab CI,配置看起来挺简单:

scan_image:stage:testimage:aquasec/trivy:latestscript:-trivy image--exit-code 1--severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

第一遍跑通了,我还挺得意。但接下来的几天,每次推送代码都要等 20 分钟。更离谱的是,开发人员开始抱怨本地分支的 CI 也慢了,因为 merge request 触发的 pipeline 同样要跑完整扫描。

我拉了一下构建耗时数据,扫描阶段占了整个 pipeline 的 78%。Trivy 每次都要重新下载漏洞数据库,然后对镜像逐层扫描。我们的镜像基于一个 Ubuntu 基础镜像,一层叠一层,Trivy 把每一层都拆开来分析。再加上没有缓存,每次都是从零开始。

划重点:漏洞扫描不是不能慢,但慢在构建流程里就是致命伤。

排查时间线

我按时间线记录了整个排查过程,方便后续复盘。

T+0 分钟:确认瓶颈

先看了 GitLab CI 的 stage 耗时。build 阶段 2 分 30 秒,scan 阶段 17 分钟。问题明确:扫描是瓶颈。

# 快速查看各 stage 耗时gl-ci-trace|grep-E"(build|scan|test)"|awk'{print $1, $NF}'

T+5 分钟:分析 Trivy 扫描日志

Trivy 的日志里反复出现Downloading DB...Analyzing layer...。我注意到漏洞数据库下载占了 4 分钟,镜像分析占了 13 分钟。

# 本地复现,加上时间戳timetrivy image--severityHIGH,CRITICAL my-app:latest

本地跑了一遍,结果一样:数据库下载慢,镜像分析慢。

T+15 分钟:定位两个根因

第一个根因:Trivy 默认从 GitHub 下载漏洞数据库,网络不稳定时经常超时重试。第二个根因:我们镜像层数太多,而且有很多重复文件,Trivy 的逐层分析策略在这种情况下效率极低。

T+25 分钟:制定三层修复方案

我画了一个简单的决策树:要么让扫描变快,要么把扫描从构建流程里挪出去,或者两者都做。最后决定三层一起上:缓存漏洞数据库、并行扫描、分层扫描策略。

解决方案:三层修复

第一层:缓存漏洞数据库

Trivy 的漏洞数据库可以缓存到本地或者 registry。我在 CI 里加了一个缓存 job,每天只更新一次数据库,其他 job 直接使用缓存。

.trivy_cache:cache:key:trivy-db-${CI_COMMIT_REF_SLUG}paths:-.cache/trivybefore_script:-mkdir-p .cache/trivy-export TRIVY_CACHE_DIR=.cache/trivy-|if [ ! -f .cache/trivy/db/trivy.db ]; then echo "Cache miss, downloading DB..." trivy image --download-db-only else echo "DB cache hit, skipping download" fi

这一步把数据库下载时间从 4 分钟压到了 10 秒。

第二层:并行扫描 + 分层策略

原来我们扫描的是最终构建完成的镜像。但实际上,很多漏洞来自基础镜像,业务代码层很少有高危漏洞。我改成了先扫描基础镜像,再扫描业务层,两者并行。

scan_base_image:stage:testimage:aquasec/trivy:latestscript:-trivy image--severity HIGH,CRITICAL--skip-dirs /app ubuntu:22.04allow_failure:truescan_app_layer:stage:testimage:aquasec/trivy:latestscript:-trivy filesystem--severity HIGH,CRITICAL /apponly:-merge_requests

filesystem模式扫描的是构建上下文,而不是完整镜像。对于业务代码变更,这通常就够了。只有基础镜像升级时,才触发完整镜像扫描。

第三层:优化镜像本身

扫描慢还有一个原因:镜像太大了。我花了一个小时把镜像从 Ubuntu 换成了 distroless,又砍掉了一些不必要的构建依赖。

# 优化前:Ubuntu 基础镜像,层数多 FROM ubuntu:22.04 RUN apt-get update && apt-get install -y python3 python3-pip COPY requirements.txt . RUN pip install -r requirements.txt COPY . /app # 优化后:多阶段构建 + distroless FROM python:3.11-slim as builder COPY requirements.txt . RUN pip install --user -r requirements.txt FROM gcr.io/distroless/python3 COPY --from=builder /root/.local /home/nonroot/.local COPY --chown=nonroot:nonroot . /app WORKDIR /app ENV PATH=/home/nonroot/.local/bin:$PATH

镜像从 1.2GB 压到了 180MB。镜像小了,Trivy 扫描自然也少了。

踩坑记录

坑 1:Trivy cache 目录权限

GitLab runner 的 cache 目录默认权限是 root,但 Trivy 容器里跑的是非 root 用户。结果缓存写进去了,Trivy 读不出来。解决方式是统一用root用户跑 Trivy,或者调整 cache 目录权限。

scan_image:image:name:aquasec/trivy:latestentrypoint:[""]script:-trivy image--cache-dir /tmp/trivy-cache...

坑 2:–skip-dirs 不生效

我一开始用--skip-dirs /app想跳过业务代码目录,结果发现 Trivy 的image模式里--skip-dirs只对filesystem模式有效。换成filesystem扫描构建上下文,才真正跳过不需要的目录。

坑 3:并行扫描的依赖顺序

scan_base_imagescan_app_layer放在同一个 stage,但scan_app_layer其实依赖 build 阶段的产物。我一开始把它们放错了 stage,导致scan_app_layer跑的时候构建产物还没生成。调整 stage 顺序后解决。

坑 4:distroless 的调试困难

distrioless 镜像里没有 shell,调试起来很费劲。我最后保留了一个debug版本的 Dockerfile,本地调试用debug标签,线上用distroless标签。

# 本地调试dockerbuild-tmy-app:debug-fDockerfile.debug.# 线上部署dockerbuild-tmy-app:latest-fDockerfile.

最终效果

三层修复下来,CI 构建时间从 20 分钟压回了 3 分钟,甚至更短。

优化项优化前优化后节省
漏洞数据库下载4 分钟10 秒~3.8 分钟
镜像扫描13 分钟45 秒~12 分钟
镜像构建3 分钟2 分钟1 分钟
总计20 分钟3 分钟17 分钟

更重要的是,开发人员不再抱怨 CI 慢了。安全合规和开发效率这两件事,终于可以和平共处了。

写在最后

这件事给我最大的教训是:安全工具接入不能简单粗暴。直接把 Trivy 塞进构建流程,不考虑缓存、不考虑分层、不考虑镜像体积,结果就是开发效率和安全合规两边都不讨好。

如果你是安全团队,建议把扫描放在独立的 stage,并且给开发团队留足缓存策略。如果你是开发团队,接入扫描工具的时候,先本地跑一遍time trivy image,看看瓶颈到底在哪。

说到底,DevSecOps 不是把安全工具堆进 CI 就完事了。安全左移没问题,但左移不等于把负担全扔给开发。找到一个让两边都能接受的方案,才是真正的工程实践。

如果你也在 CI 里踩过扫描的坑,欢迎评论区交流。踩坑的路上,你并不孤单。

http://www.cnnetsun.cn/news/3215538.html

相关文章:

  • Scikit-learn 1.4 集成学习实战:Bagging vs Boosting vs Stacking 在 3 个数据集上的性能评测
  • 5分钟掌握Luyten:Java反编译工具的最佳图形化解决方案
  • vsftpd 虚拟用户 vs 本地用户:3种登录模式权限与安全深度对比
  • 抖音批量下载完全指南:三步掌握免费无水印视频保存方法
  • 软件测评实验室认可,作业指导书文件清单分享
  • R语言生态建模】biomod2物种分布模型完整实战:数据获取→预处理→建模→评估→优化→预测
  • 优质的中空直驱电机厂家深度盘点与选型指南
  • ClaudeCode安装和使用
  • ffmpeg里使用的解码器的介绍和了解
  • WuWa-Mod终极指南:解锁《鸣潮》游戏潜能的完整实战方案
  • SVGnest完全指南:零成本实现材料利用率翻倍的智能切割工具
  • okbiye 告别通宵做 PPT!AI 一键生成适配全场景演示文稿
  • RAG准确率翻倍,我做了这些优化。。。
  • LangChain开发核心技巧,动态切换AI大模型参数
  • Vivado升级后,老工程时序崩
  • 直播+对讲+会议+点播,企业级融媒体平台EasyDSS打通应急指挥“最后一公里”
  • 商用烤盘定制厂家哪家专业
  • 粉笔980适合大二大三备考公务员吗?在校生怎么提前打公考基础
  • 2026小提琴避坑实测!3大新手雷区+6款高性价比机型解析推荐
  • 手把手教程:用“人人微投票”零门槛搭建高并发、防刷票的评选活动
  • BORA:面向接触丰富场景的灵巧操作在线残差自适应方法
  • 短时序未来视频驱动的机器人多步操作方法
  • Three.js 草地着色器教程
  • Frida-Trace实战:从动态追踪到精准Hook的Android逆向进阶指南
  • 2026大模型API聚合平台横向评测:企业与开发者如何选择稳定的AI网关方案
  • Claude正版使用指南:从环境配置到代码实战完整方案
  • 智推时代完成数千万元天使轮融资,GEO市场2030年规模或突破500亿!
  • 赋能增安型7/8多芯防爆连接器适用范围
  • 暗黑破坏神2现代化补丁D2DX:让经典游戏在现代电脑上焕然新生
  • 国内首场Houdini 22技术大会落地深圳