CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
容器安全
这一课你将能做到
用 Trivy 扫描一个 Docker 镜像,解读报告里的 CVE 严重等级和修复版本
给本站 Spring Boot 应用写一份最小化、分阶段构建的 Dockerfile,并解释每行安全含义
说出 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 泄漏 (镜像里残留的密钥)
# .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
讲清楚要点
最小化镜像是第一道防线 :攻击者能利用的只有镜像里存在的东西。Ubuntu 镜像里几百个 OS 包可能有 10 个 CVE;distroless 镜像接近零 OS 包。JRE 比 JDK 少编译工具,Alpine 比 Debian 小。
非 root 是容器安全的底线 :容器不是虚拟机,共享宿主机内核,root 用户逃逸后就是宿主机 root。USER appuser 是最便宜最有效的控制,零代码改动。
不可变基础设施 :镜像标签一旦推上生产就不修改。修复漏洞就发新版本、新 tag,滚动替换。latest 标签无法确定线上跑的到底是哪天 build 的版本,出了问题无法对应源码。
SBOM 是供应链应急响应的救急工具 :Log4Shell 爆发时,没有 SBOM 的团队花 2 周手动翻依赖,有 SBOM 的团队 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-alpineUSER root 运行应用逃逸后宿主机 root USER appuser推 :latest 到生产 无法追溯版本、回滚困难 用 git-sha + 语义版本标签 镜像里带编译工具/ssh 被攻破后可直接编译恶意程序 多阶段构建,运行镜像极简 不写 resources.limits 一个容器耗尽宿主机资源 设置 CPU/内存 limit 无 NetworkPolicy 一台容器被攻破可横向扫描全部 默认拒绝,按需放行
卡点急救
多阶段构建 COPY 产物找不到 :检查 COPY --from=builder 的路径和 builder 阶段 WORKDIR 是否对应。Spring Boot 打包产物在 target/*.jar,注意不要用 * 而用精确文件名(否则可能把 -sources.jar 也复制进去)。
加了 securityContext 后 Pod 起不来 :八成是 readOnlyRootFilesystem: true 后应用在启动期往容器里写文件(如临时编译的 JSP、日志文件)。解法:打日志到 stdout、把需要写的目录挂 emptyDir、或者用 volumes 单独挂可写卷。
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 生成。
自测三问
多阶段构建的好处是什么?最终镜像里不应该出现哪些东西?
readOnlyRootFilesystem: true 的防御逻辑是什么?哪些目录需要特别处理?
为什么 :latest 标签在生产环境是危险的?应该用什么替代?
小结一句话 容器安全三板斧:最小化镜像减少攻击面、非 root+只读+资源限制防止作恶、Trivy 扫描+SBOM 持续追踪漏洞——latest 标签绝不能进生产。
延伸