4、容器化部署安全:Dockerfile安全最佳实践、镜像漏洞扫描、容器运行时安全策略
容器化部署,说白了就是把你的应用连同它的运行环境一起打包。听起来很爽,对吧?但我在生产环境里见过太多因为容器安全没做好而翻车的事故了。这一章,我们就来聊聊怎么把容器这头“猛兽”关进安全的笼子里。
4.1 Dockerfile 安全最佳实践
Dockerfile 是容器安全的起点。我见过很多团队,应用写得不错,但 Dockerfile 写得一塌糊涂。嗯,这里要注意,Dockerfile 里的每一行指令,都可能成为攻击者的突破口。
4.1.1 选择合适的基础镜像
别图省事直接用 ubuntu:latest 或者 node:latest。这些镜像体积大,而且包含大量你根本用不到的软件包。攻击面越大,风险越高。
- 首选 Alpine 镜像:体积小(5MB 左右),攻击面小。但要注意,Alpine 用的是 musl libc,有些应用可能会有兼容性问题。
- 使用官方镜像:比如
python:3.11-slim或node:20-alpine。官方镜像经过安全审核,更新也及时。 - 固定版本标签:别用
latest,用python:3.11.5-slim。这样能保证构建的可复现性。
4.1.2 最小权限原则
容器默认以 root 用户运行,这很危险。一旦容器被攻破,攻击者直接拿到宿主机的 root 权限。我建议你在 Dockerfile 里显式创建非 root 用户。
# 不好的做法
FROM node:20-alpine
COPY . /app
RUN npm install
CMD ["node", "app.js"]
# 好的做法
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm install
USER appuser
CMD ["node", "app.js"]
4.1.3 减少层数,合并 RUN 指令
每一层都会增加镜像体积,也可能引入不必要的文件。我习惯把相关的操作合并到一个 RUN 指令里,并在最后清理缓存。
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*
4.1.4 避免敏感信息泄露
千万别把密码、密钥、Token 直接写在 Dockerfile 里。我曾经在客户代码里看到过直接把数据库密码写在 ENV 指令里的情况,那真是触目惊心。
- 使用
--secret构建参数(BuildKit 特性)。 - 运行时通过环境变量或挂载卷传入敏感信息。
- 使用
.dockerignore文件排除本地敏感文件。
.env 文件直接 COPY 进镜像,然后推到了公共仓库。结果不到一小时,数据库就被拖库了。记住:镜像一旦被推送,里面的信息就再也收不回来了。
4.2 镜像漏洞扫描
镜像构建好了,不代表就安全了。基础镜像和依赖包里可能藏着已知漏洞。你想想看,如果基础镜像里有个 CVE-2024-XXXX,你的应用再安全也没用。
4.2.1 常用扫描工具对比
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Trivy | 轻量、速度快、支持多种语言 | CI/CD 流水线集成 |
| Clair | 静态分析、支持 API 调用 | 私有镜像仓库集成 |
| Anchore Grype | 与 Syft 配合使用,SBOM 生成 | 需要软件物料清单的场景 |
| Docker Scout | Docker 官方工具,集成度高 | Docker Desktop 用户 |
4.2.2 在 CI/CD 中集成扫描
我个人习惯把镜像扫描作为 CI/CD 流水线的一个强制步骤。扫描不通过,镜像就不允许推送到生产环境。
# 在 GitLab CI 中使用 Trivy 的示例
scan-image:
stage: test
script:
- trivy image --severity HIGH,CRITICAL my-app:${CI_COMMIT_SHA}
only:
- main
4.2.3 漏洞修复策略
- 更新基础镜像:定期拉取最新版本的基础镜像。
- 重建依赖:更新
package.json或requirements.txt中的依赖版本。 - 使用多阶段构建:最终镜像只包含运行所需的最小文件。
4.3 容器运行时安全策略
镜像安全了,容器跑起来之后呢?运行时安全同样不能忽视。说白了,就是限制容器在宿主机上能做什么。
4.3.1 限制容器能力(Capabilities)
Docker 默认会给容器分配一些能力,比如 NET_RAW、SYS_CHROOT 等。但大多数应用根本不需要这些。我建议你只保留必要的能力。
# docker-compose.yml 示例
services:
app:
image: my-app:latest
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
4.3.2 只读根文件系统
如果应用不需要写文件系统,就把根文件系统设为只读。这样即使容器被攻破,攻击者也无法修改系统文件。
# 在 Docker Compose 中设置
services:
app:
image: my-app:latest
read_only: true
tmpfs:
- /tmp
4.3.3 使用 seccomp 和 AppArmor
这两个是 Linux 内核的安全模块,可以限制容器能调用的系统调用。默认的 seccomp 策略已经过滤了 300 多个危险系统调用,但你可以根据应用需求进一步收紧。
personality 系统调用来检测 CPU 架构。所以,在收紧策略之前,一定要充分测试。
4.3.4 运行时安全策略清单
| 策略 | 说明 | 推荐级别 |
|---|---|---|
| 禁止特权模式 | 不允许 --privileged 运行容器 |
强制 |
| 限制内存和 CPU | 防止 DoS 攻击 | 推荐 |
| 使用非 root 用户 | 降低提权风险 | 强制 |
| 限制网络访问 | 使用自定义网络,避免 --network=host |
推荐 |
| 启用日志审计 | 记录容器行为,便于事后追溯 | 推荐 |