CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
DevSecOps 入门/课时/容器安全
RawBlame
课时目录12 节

DevSecOps 入门

  1. 01DevSecOps 概览
  2. 02网络安全基础
  3. 03密码学
  4. 04身份与访问
  5. 05应用安全
  6. 06威胁建模
  7. 07云安全
  8. 08容器安全
  9. 09供应链安全
  10. 10检测与 SIEM
  11. 11Incident 响应
  12. 12合规与扫描

容器安全

镜像扫描、SBOM 与 K8s。

30 分钟

容器安全

这一课你将能做到

  1. 用 Trivy 扫描一个 Docker 镜像,解读报告里的 CVE 严重等级和修复版本
  2. 给本站 Spring Boot 应用写一份最小化、分阶段构建的 Dockerfile,并解释每行安全含义
  3. 说出 K8s 中 3 个关键安全配置(非 root 用户、只读根文件系统、资源限制)及其 YAML 写法

为什么要学

容器让环境一致性变成默认选项,但也把「整个操作系统 + 依赖库 + 应用」打成一个包——包里任何一个漏洞都是攻击面。Alpine 官方镜像曾经出现过 CVE,Log4Shell 的利用链里容器逃逸是常见路径。而最小化镜像、非 root 运行、资源限制,这些基础控制能挡住大部分自动化扫描和简单逃逸。

人话:容器安全三板斧——镜像最小化(少漏洞)、运行加固(跑不动坏事情)、扫描持续化(新 CVE 出来就修)。latest 标签不是方便的代名词,是不可重现性的安全噩梦。

先建立图像

容器安全生命周期:

构建阶段 ──→ 分发阶段 ──→ 运行阶段 ──→ 销毁阶段
    │            │            │            │
    ├ SBOM      ├ 签名       ├ 非 root     ├ 数据擦除
    ├ 最小基板   ├ 私有仓库    ├ 只读根      ├ 日志归档
    ├ 无编译工具 ├ 漏洞扫描    ├ 资源限制    ├ 审计闭环
    └ 扫描报告   └ 准入策略    └ 网络隔离    └ 镜像 GC

为什么最小化很重要:

镜像大小      │ 攻击面 │ 构建速度 │ 部署速度
─────────────┼────────┼──────────┼─────────
ubuntu:22.04 │ 很大   │ 慢       │ 慢
openjdk:17   │ 大     │ 中       │ 中
distrolless  │ 极小   │ 快       │ 快
debug 工具    │ 无     │ 极简     │ 极简

跟着做

步骤 1:写最小化、分阶段构建的 Dockerfile

本站 Spring Boot 应用的安全 Dockerfile:

# 阶段 1:编译,所有工具都在这,不用进最终镜像
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /build
COPY pom.xml .
COPY src src
RUN ./mvnw package -DskipTests -q

# 阶段 2:运行,只放 JRE 和编译产物,去掉编译工具
FROM eclipse-temurin:17-jre-alpine
# 不用 root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
  CMD wget -q --spider http://localhost:8080/actuator/health || exit 1

WORKDIR /app
# 只从 builder 阶段复制产物,源代码不进运行镜像
COPY --from=builder /build/target/app.jar app.jar

# 元数据:用 SHA 或版本号标签,不要用 latest
LABEL version="1.2.3"
LABEL org.opencontainers.image.source="https://github.com/coderoadmap/backend"

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

关键安全行解释:

USER appuser            以非 root 身份运行,即使容器逃逸也拿不到 root
jre-alpine              只包含运行时,去掉编译器、gcc、bash 等工具
--from=builder          多阶段构建,源代码/依赖缓存/编译产物不留在最终镜像
LABEL version           可追溯性,出事后能知道线上跑的是哪个 commit 的产物

构建和打标签:

docker build -t coderoadmap/app:1.2.3 -t coderoadmap/app:git-$(git rev-parse --short HEAD) .
# 绝不用 :latest 推生产

步骤 2:Trivy 扫描镜像

# 安装 trivy(Windows 可用 scoop 或 release 二进制)
# 扫描本地镜像
trivy image --severity HIGH,CRITICAL coderoadmap/app:1.2.3

# 输出包括:
# - OS 包漏洞 (CVE-2023-XXXX)
# - 应用依赖漏洞 (Maven pom 坐标)
# - 错误配置 (如 Dockerfile 里的 USER root)
# - Secret 泄漏 (镜像里残留的密钥)

CI 里集成:

# .github/workflows/docker-security.yml
- name: Scan image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'coderoadmap/app:${{ github.sha }}'
    severity: 'HIGH,CRITICAL'
    exit-code: '1'    # 高危漏洞直接中断流水线

步骤 3:生成 SBOM 并归档

SBOM(软件物料清单)是镜像的「配料表」,应急响应时快速定位含漏洞的包:

trivy image --format spdx-json -o sbom.json coderoadmap/app:1.2.3
# 生产环境应随每次发版归档 SBOM,应急时搜 "含 log4j 2.14 的镜像版本"

步骤 4:K8s Pod 安全上下文(SecurityContext)

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true         # 强制非 root
        seccompProfile:
          type: RuntimeDefault     # 默认系统调用过滤
      containers:
        - name: app
          image: coderoadmap/app:1.2.3
          imagePullPolicy: Always  # 确保不会用本地旧镜像
          securityContext:
            allowPrivilegeEscalation: false   # 禁止提权
            readOnlyRootFilesystem: true      # 根文件系统只读
            capabilities:
              drop:
                - ALL                          # 丢弃所有特权
          resources:
            limits:
              memory: "512Mi"
              cpu: "500m"
          volumeMounts:
            # 需要写临时文件时,挂一个 emptyDir 到 /tmp
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}

逐行说明:

runAsNonRoot: true            启动时不是 root UID,防逃逸后提权
allowPrivilegeEscalation: false  不能用 setuid 提权(不能 sudo)
readOnlyRootFilesystem: true   即使容器被攻破也改不了应用二进制/配置
drop: ALL                      去掉 NET_ADMIN、SYS_PTRACE 等危险能力
resources.limits               防止被利用后占满宿主机资源(DoS)

步骤 5:NetworkPolicy 限制容器通信

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: app-allow-only-ingress
spec:
  podSelector:
    matchLabels:
      app: coderoadmap-backend
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: nginx-ingress
      ports:
        - protocol: TCP
          port: 8080
  egress:
    # 只允许连 MySQL 和 Redis,其余全部拒绝
    - to:
        - podSelector:
            matchLabels:
              app: mysql
      ports:
        - protocol: TCP
          port: 3306
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - protocol: TCP
          port: 6379

讲清楚要点

  1. 最小化镜像是第一道防线:攻击者能利用的只有镜像里存在的东西。Ubuntu 镜像里几百个 OS 包可能有 10 个 CVE;distroless 镜像接近零 OS 包。JRE 比 JDK 少编译工具,Alpine 比 Debian 小。

  2. 非 root 是容器安全的底线:容器不是虚拟机,共享宿主机内核,root 用户逃逸后就是宿主机 root。USER appuser 是最便宜最有效的控制,零代码改动。

  3. 不可变基础设施:镜像标签一旦推上生产就不修改。修复漏洞就发新版本、新 tag,滚动替换。latest 标签无法确定线上跑的到底是哪天 build 的版本,出了问题无法对应源码。

  4. SBOM 是供应链应急响应的救急工具:Log4Shell 爆发时,没有 SBOM 的团队花 2 周手动翻依赖,有 SBOM 的团队 5 分钟搜出受影响镜像列表。

  5. 安全上下文是 K8s 的免费补丁:不需要改应用代码,只需要在 Deployment 里加几行 YAML,就能关掉 root、只读根文件系统、限制能力。这些基线应在准入控制器(OPA/Kyverno)里强制执行。

你可能会懵的地方

  • distroless 没有 shell,怎么 debug? 应急时用临时 sidecar 容器(如 busybox)attach 到同一个 network namespace 做诊断。日常不用,生产不保留 debug 工具是原则。

  • 容器和 VM 安全有什么区别? VM 是硬件虚拟化(独立内核),逃逸极难;容器是操作系统级隔离(共享内核),隔离强度弱但启动极快。所以容器安全的核心是「假设容器可能逃逸,但限制逃逸后的损害」(非 root、只读、资源限制、网络隔离)。

  • readOnlyRootFilesystem=true 后应用写不了日志怎么办? 正确做法:日志输出 stdout(让 K8s 收集),不写到文件。临时文件挂 emptyDir 到 /tmp。配置文件用 ConfigMap/Secret 挂载为只读卷。把状态外置到卷是 12 Factor App 的要求。

  • Trivy 误报怎么处理? 配置 .trivyignore 写明 CVE 编号、原因、复查日期。CI 里只卡 HIGH/CRITICAL。低误报率是扫描器能被开发接受的前提。

  • 有 Docker 还需要 K8s 安全上下文吗? 容器运行时安全(Docker)和编排层安全(K8s)两层都需要。Docker 里用非 root,K8s 里加一层准入策略防忘记。多层防御。

错法对照

错法问题正解
用 ubuntu:latest 做基础镜像大包大面、CVE 多、不可追溯eclipse-temurin:jre-alpine
USER root 运行应用逃逸后宿主机 rootUSER appuser
推 :latest 到生产无法追溯版本、回滚困难用 git-sha + 语义版本标签
镜像里带编译工具/ssh被攻破后可直接编译恶意程序多阶段构建,运行镜像极简
不写 resources.limits一个容器耗尽宿主机资源设置 CPU/内存 limit
无 NetworkPolicy一台容器被攻破可横向扫描全部默认拒绝,按需放行

卡点急救

  1. 多阶段构建 COPY 产物找不到:检查 COPY --from=builder 的路径和 builder 阶段 WORKDIR 是否对应。Spring Boot 打包产物在 target/*.jar,注意不要用 * 而用精确文件名(否则可能把 -sources.jar 也复制进去)。

  2. 加了 securityContext 后 Pod 起不来:八成是 readOnlyRootFilesystem: true 后应用在启动期往容器里写文件(如临时编译的 JSP、日志文件)。解法:打日志到 stdout、把需要写的目录挂 emptyDir、或者用 volumes 单独挂可写卷。

  3. Trivy 扫描发现高危 CVE 但基础镜像没有新 tag:这是 Alpine 常见情况。解法:① 确认是否可达(调用链是否实际用到有漏洞的函数);② 在 Dockerfile 里 apk upgrade 临时更新;③ 升级到更新版本的基础镜像。给 CVE 加 suppression 时写明原因和复查日期,不要直接忽略。

面试常问

  • Docker 安全最佳实践? 镜像层面:最小化基础镜像(JRE-alpine/distroless)、多阶段构建、固定标签(不用 latest)、SBOM 归档。运行层面:非 root 用户、只读根文件系统、禁止特权提权、资源限制、健康检查。分发层面:私有仓库 + 镜像签名(Notary/cosign)。编排层面:K8s SecurityContext + NetworkPolicy + 准入控制器。

  • 容器逃逸常见路径有哪些?防御措施? 常见路径:以 root 运行 + 挂载宿主机 /proc 或 Docker socket;特权容器;内核漏洞;cap_add NET_ADMIN。防御:非 root 运行、不挂载宿主机敏感路径、禁止特权模式、用 seccomp/AppArmor、保持内核补丁、限制 capabilities。

  • 什么是 SBOM,为什么重要? Software Bill of Materials,软件物料清单,列出镜像/应用中的所有依赖组件及其版本。重要性:漏洞爆发时(如 Log4j)可秒级定位受影响版本;合规审计提供透明度;漏洞修复后验证依赖是否升级。输出格式常用 SPDX、CycloneDX,用 Trivy/Syft 生成。

自测三问

  1. 多阶段构建的好处是什么?最终镜像里不应该出现哪些东西?
  2. readOnlyRootFilesystem: true 的防御逻辑是什么?哪些目录需要特别处理?
  3. 为什么 :latest 标签在生产环境是危险的?应该用什么替代?

小结一句话

容器安全三板斧:最小化镜像减少攻击面、非 root+只读+资源限制防止作恶、Trivy 扫描+SBOM 持续追踪漏洞——latest 标签绝不能进生产。

延伸

  • OWASP Container Security Verification Standard
  • 下一课:供应链安全——Log4Shell 之后,你的依赖链到底有多少条命根子在别人手里。
加载本节练习…
← 上一课云安全下一课 →供应链安全