Trivy 实战:容器镜像漏洞扫描与 CI 集成

Trivy 是 Aqua Security 开源的容器镜像漏洞扫描工具,能在 CI 里一键检出操作系统包与语言依赖里的 CVE。本文从安装、扫描到 GitHub Actions 集成,手把手把供应链安全问题拦在合入之前,而不是等上线后被扫出一堆 CRITICAL。

一、为什么要在 CI 里做镜像扫描

容器镜像不是“干净的运行环境”,它里面打包了基础系统的 glibc、openssl,以及你的 Python/Node 依赖。任何一层有已知 CVE,都会随镜像一起发布到生产。典型的教训是 Log4Shell(CVE-2021-44228):一个传递依赖就能让整个集群可被远程执行代码。

把扫描放到 CI 而不是事后人工巡检,核心收益有三:一是可复现,每次构建都按同一套规则检查;二是可阻断,高危漏洞直接让流水线失败,合并不了;三是可审计,SBOM(软件物料清单)能回答“这个镜像到底装了什么”。

扫描的时机也很关键:越早越好(shift-left)。在镜像构建完成后立刻扫,比等到部署前再扫能省下数倍的返工成本——你肯定不希望在凌晨被一条“紧急修复 CVE”的告警叫醒。把扫描作为流水线里一个普通步骤,和单元测试、构建并列,安全就不再是上线前的“临时抱佛脚”。

二、安装 Trivy

Trivy 是单一静态二进制,没有运行时依赖。常见平台一行即可装好:

# macOS
brew install trivy

# Ubuntu / Debian
sudo apt-get install -y wget apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb stable main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivy

# 不想装?直接用容器版,效果完全一致
docker run --rm aquasec/trivy:latest --version

装完用 trivy --version 确认能跑。它的漏洞库(trivy-db)首次运行会自动下载并缓存到 ~/.cache/trivy,后续扫描默认走本地缓存,离线也能扫。

三、扫描一个镜像:读懂结果

# 扫描官方 nginx 镜像
trivy image nginx:1.25

# 只看高危以上,避免被海量 LOW 噪音淹没
trivy image --severity HIGH,CRITICAL nginx:1.25

# 指定镜像来源(本地镜像、tar 包、私有仓库都支持)
trivy image --input myapp.tar
trivy image registry.internal.example.com/myapp:1.2.0

输出按漏洞等级分四档:CRITICAL(严重)、HIGH(高)、MEDIUM(中)、LOW(低)。每条都会给出 CVE 编号、受影响包、已修复版本(Fixed Version)和建议。重点关注 CRITICAL/HIGH,且 Fixed Version 非空的项——那是可以直接升级修掉的。

看漏洞不能只看等级。同样是 HIGH,CVSS 评分 7.0 和 9.8 的紧迫度天差地别;更关键的是攻击向量(Attack Vector):标着“本地可达 / 需要认证”的漏洞,实际被利用的概率远低于“网络可达 / 无需认证”的漏洞。Trivy 结果里会标注这些维度,排修复优先级时要把“能否被外部打到”放在等级之前,避免团队被海量 LOW 噪音拖垮。

四、Trivy 能扫什么:扫描类型一览

扫描目标命令示例主要用途
容器镜像trivy image <img>检出 OS 包与语言依赖 CVE
文件系统trivy fs ./project扫源码目录里的依赖与密钥
代码仓库trivy repo <git-url>远程仓库直接拉取扫描
Kubernetestrivy k8s –report summary扫集群工作负载与配置
配置合规trivy config ./deploy检查 IaC(Dockerfile/K8s)误配置
密钥泄露trivy secret ./project发现硬编码的 token/密码
SBOM 生成trivy image –format cyclonedx输出软件物料清单

日常 CI 用得最多的是 image 和 fs;secret 和 config 则能补上“代码里写了密码”“Dockerfile 用 root 跑”这类非 CVE 类风险。

五、忽略策略与误报处理

不是每个漏洞都能立刻修:有些依赖暂时没有补丁,有些在你的运行场景里根本不可达。Trivy 用 .trivyignore 声明豁免,但务必写清理由,避免变成“一键屏蔽所有”。

# .trivyignore
# CVE-2023-1234 在 Alpine 3.19 的 libwebp 中不可达(仅静态库被链接,无解码路径)
CVE-2023-1234

# 该组件为构建期工具,不进最终镜像,风险可控
CVE-2024-5678

除了忽略文件,还可以用 --ignore-unfixed 跳过“官方还没给修复版本”的漏洞,让 CI 只报真正能修的项,减少噪音:

trivy image --severity HIGH,CRITICAL --ignore-unfixed myapp:latest

六、集成到 GitHub Actions:高危即阻断

这是最关键的落地方式。下面这个 workflow 在构建出镜像后立刻扫描,只要有 HIGH/CRITICAL 漏洞,流水线就非零退出,PR 无法合入。

name: scan
on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          severity: HIGH,CRITICAL
          exit-code: "1"
          ignore-unfixed: true

如果团队暂时不想“硬阻断”,可先把 exit-code 设为 0,让结果以警告形式出现在 PR 评论里,观察一段时间再收紧。关于流水线本身的搭建,可以参考我们的 GitHub Actions CI/CD 实战。

这个 workflow 有几个值得说明的点:pull_request 触发保证每次合并评审都过一遍扫描;exit-code: '1' 是“阻断”开关,设为 0 则只报不拦;ignore-unfixed: true 跳过官方尚未提供补丁的漏洞,避免无效告警刷屏。Trivy Action 会自动处理漏洞库下载与缓存,基本开箱即用,无需自己再写一层封装。

七、与 Docker 构建联动:多阶段 + 扫描

推荐在 CI 里用多阶段构建产出最小运行时镜像,再对最终镜像扫描。这样既能减少攻击面,也能避免把构建期的 dev 依赖扫进风险清单。

# 构建阶段
FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# 运行时阶段:只拿产物,不带构建工具
FROM nginx:1.25-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# 注意:尽量用非 root 用户,避免 config 扫描报“以 root 运行”
USER 1001

构建镜像的姿势,建议沉淀成团队统一的 Docker Compose 一键编排 模板,保证本地和 CI 行为一致。

八、生成 SBOM:合规的入场券

越来越多客户和监管要求在交付时附上 SBOM。Trivy 一条命令就能产出 CycloneDX 或 SPDX 格式清单,可直接对接后续的合规与持续监控平台。

# 生成 CycloneDX 格式 SBOM
trivy image --format cyclonedx --output sbom.cdx.json myapp:latest

# 生成 SPDX 格式
trivy image --format spdx-json --output sbom.spdx.json myapp:latest

九、性能与私有仓库

扫描慢多半是漏洞库没缓存或拉镜像走公网。把缓存目录挂到 CI 缓存里能显著加速;私有仓库则通过环境变量注入凭证:

# 复用漏洞库缓存,避免每次重建下载
export TRIVY_CACHE_DIR=$HOME/.cache/trivy

# 私有仓库认证(推荐用 CI Secret,别写死)
export TRIVY_USERNAME=$REG_USER
export TRIVY_PASSWORD=$REG_PASS
trivy image registry.internal.example.com/myapp:latest

十、常见坑与排错

现象根因对策
首次扫描极慢正在下载 trivy-db预热缓存,CI 中挂载 ~/.cache/trivy
DB 下载报网络错访问 GitHub 受限设 TRIVY_DB_REPOSITORY 走镜像源
扫描结果“全绿”镜像标签被覆盖/拉错用 immutable 的 digest(@sha256:)锁定
大量 LOW 噪音未过滤严重度加 –severity HIGH,CRITICAL –ignore-unfixed
私有仓库 401凭证未注入配置 TRIVY_USERNAME/PASSWORD 或 docker login

十一、落地 Checklist

  • 本地装好 Trivy,先用 trivy image 扫一遍现有镜像,建立基线。
  • 在 GitHub Actions 里加扫描步骤,HIGH/CRITICAL 直接 exit-code: 1 阻断。
  • 用 .trivyignore 收敛误报,每条必须带理由。
  • 开启 secret 与 config 扫描,补齐非 CVE 风险。
  • 每次发布附上 SBOM,喂给合规与监控链路。
  • 缓存 ~/.cache/trivy,把扫描耗时压到分钟级。

十二、小结

Trivy 的最大价值不是“扫出多少漏洞”,而是把安全卡点前移到 CI,让问题在合入前就被拦下。它单一二进制、规则开箱即用、和 GitHub Actions 几乎零成本对接,是中小团队最划算的供应链安全第一步。配合 VS Code 远程开发 在本地提前验证、再用 chezmoi 管理 dotfiles 统一团队配置,整条研发链路都能更稳。

上一篇 前端虚拟列表实战:海量数据的流畅渲染
下一篇 Fluentd 实战:统一日志采集与转发管道搭建
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]