Docker-11 安全、性能与可观测性:从能跑到敢上线
1. 先定义 Docker 的安全边界
Docker 的安全边界至少包含四层:
宿主机与内核
↓
Docker daemon / containerd / runtime
↓
容器 namespace、cgroup、mount、capability、seccomp、LSM
↓
应用进程、依赖和业务数据
容器共享宿主机内核,因此内核漏洞、错误的 daemon 权限、过度暴露的设备和宿主挂载,都会影响隔离强度。安全工作不是“加一个参数”,而是减少每一层能被利用的能力,并保证升级和审计可持续。
2. 高风险配置
2.1 Docker socket
/var/run/docker.sock 通常允许调用 Docker Engine API。下面的挂载意味着容器可以请求 daemon 创建新的高权限容器:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
除非是明确审计过的受信任构建/监控组件,否则不要把 socket 暴露给业务容器。需要 Docker API 时,优先使用受限代理、独立构建节点或 rootless daemon。
2.2 --privileged、宿主根目录和设备
# 高风险示例:不要在生产业务容器使用
docker run --privileged -v /:/host image
这类配置可能让容器读取/修改宿主文件系统、访问设备或获得大量 capability。排障需要额外权限时,逐项添加 --cap-add、--device 和只读挂载,完成任务后撤销。
2.3 docker 用户组
能访问 Docker socket 的用户通常等价于宿主 root。多租户机器上不要把“加入 docker 组”当成普通开发权限;可以使用 rootless、远程受限 daemon 或专门的 CI worker。
3. 应用容器的最小权限
一个常见的加固运行命令:
docker run -d --name api \
--user 10001:10001 \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m \
--cap-drop=ALL \
--security-opt no-new-privileges:true \
--pids-limit 256 \
--memory 512m --cpus 1.0 \
--restart unless-stopped \
example/api:1.0.0
3.1 非 root 用户
在 Dockerfile 中设置 USER,并为需要写入的目录提前授予数字 UID/GID 权限:
RUN addgroup --system --gid 10001 app \
&& adduser --system --uid 10001 --ingroup app app \
&& mkdir -p /var/lib/app \
&& chown -R 10001:10001 /var/lib/app
USER 10001:10001
如果应用只需要监听高于 1024 的端口,不要为了绑定 80 而恢复 root;可以由反向代理在宿主或独立容器负责低端口入口。
3.2 Capability、seccomp、LSM
docker run --rm --cap-drop=ALL \
--cap-add=NET_BIND_SERVICE example/api:dev
docker run --rm --security-opt no-new-privileges:true example/api:dev
默认 seccomp、AppArmor/SELinux 策略应保持启用。只有在证明某个 syscall 被阻断且确实需要时,才提供最小的自定义 profile,并将 profile 纳入版本控制和测试。
3.3 只读根文件系统
--read-only 能减少运行时篡改和持久化恶意文件的路径,但必须给缓存、socket、临时目录提供明确的 tmpfs 或 volume。只读不是“应用一定无状态”,仍需设计日志和数据出口。
4. 镜像供应链安全
建议的发布链路:
依赖锁定 → 可复现构建 → 单元/集成测试
→ 漏洞扫描 + SBOM → 签名/证明
→ 私有 registry → 按 digest 部署
检查项目:
- 基础镜像来自可信维护者,固定版本并定期更新。
- 不使用
latest作为生产发布标识。 - Dockerfile 不包含密码、云凭证、SSH 私钥。
- 构建上下文由
.dockerignore限制。 - CI 生成 SBOM,扫描 OS 包和应用依赖漏洞。
- 部署前验证镜像 digest、签名和来源 commit。
扫描工具的结果需要结合可利用性、运行时权限和网络暴露评估,不能只看 CVE 数量。修复基础镜像后要重新构建,不能在运行中的容器里 apt upgrade。
4.1 漏洞扫描实操
Docker Scout(Docker 官方集成):
docker scout quickview example/api:1.0.0
docker scout cves example/api:1.0.0
docker scout recommendations example/api:1.0.0
Trivy(开源,支持镜像、文件系统、Git 仓库和 Kubernetes):
trivy image example/api:1.0.0
trivy image --severity HIGH,CRITICAL example/api:1.0.0
trivy image --format json --output scan.json example/api:1.0.0
Grype(开源,Anchore 出品):
grype example/api:1.0.0
grype example/api:1.0.0 --only-fixed
生成 SBOM(软件物料清单):
docker buildx build --sbom=true --provenance=true \
-t example/api:1.0.0 --push .
docker sbom example/api:1.0.0
syft example/api:1.0.0 -o spdx-json > sbom.spdx.json
CI 中建议的最小流程:
构建镜像
↓
trivy/scout 扫描,CRITICAL 漏洞阻断流水线
↓
生成 SBOM 并存档
↓
签名(cosign/notation)
↓
推送到受控 registry
扫描只是发现漏洞的入口。修复要回到 Dockerfile 和依赖锁文件,重新构建并测试;不要在生产容器内手动 apt upgrade 或 apk add,这些变更不可复现、不可审计。
5. 秘密管理
镜像层、环境变量、docker inspect、命令行历史和日志都可能泄露秘密。优先级通常是:
- 外部 secret manager 或编排平台 secret。
- 只读挂载的短生命周期 secret 文件。
- 受严格权限控制的运行时环境变量。
- 绝不把秘密写入 Dockerfile、Git 或镜像 layer。
Compose 开发环境可以使用:
services:
api:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
正式环境应由部署系统注入并轮换;应用要支持连接池凭据刷新,避免每次轮换都需要人工修改镜像。
6. 资源限制和性能
6.1 CPU
--cpus=2:限制 CPU 带宽,竞争时可能发生 throttling。--cpu-shares:相对权重,无竞争时不限制绝对上限。--cpuset-cpus=0,1:限制可运行的 CPU 集合。
观察 throttling:
docker stats --no-stream api
docker exec api cat /sys/fs/cgroup/cpu.stat
不要把 runtime.NumCPU() 当成容器真正可用的 CPU 数;应用线程池、GOMAXPROCS 和连接池应根据 cgroup 限额或编排平台配置。
6.2 内存
docker run --memory 512m --memory-swap 512m example/api:dev
设置 --memory 后仍要为堆外内存、线程栈、mmap、page cache 和运行时开销留余量。memory.max 超限可能导致容器内进程被 OOM kill,退出码常见为 137;要结合 memory.events 和应用指标判断。
6.3 I/O 和存储驱动
overlayfs 对大量小文件创建、删除和 copy-up 可能有开销。数据库应使用合适的 volume/块存储,并进行 fsync、随机读写和恢复测试。不要用一个“镜像大小很小”的结论推断运行时 I/O 一定很快。
7. 日志、指标、事件和追踪
7.1 日志
应用优先输出结构化 stdout/stderr:
{"level":"info","ts":"2026-09-11T10:00:00Z","msg":"request","trace_id":"..."}
Docker local/json-file 日志驱动要设置大小和保留策略;生产大流量场景可以转发到 journald、Fluent Bit、云日志或 Kafka。日志必须有采集失败和磁盘满时的退化策略。
7.2 指标
至少监控:容器 CPU 使用和 throttling、内存 working set、OOM 次数、网络吞吐、磁盘 I/O、重启次数、健康状态和应用请求延迟。docker stats 适合临时观察,长期监控应使用 Prometheus/cAdvisor、宿主机 exporter 或编排平台指标。
7.3 事件
docker events --filter type=container --since 1h
docker events --filter event=oom
事件可以帮助关联“部署 → 重启 → 健康检查失败 → OOM”的时间线,但不替代应用日志和指标;事件本身不应当作为唯一审计存储。
8. 性能分析方法
遇到慢请求或高 CPU 时,先区分四类瓶颈:
| 层 | 证据 | 常见原因 |
|---|---|---|
| 应用 CPU | pprof、火焰图、线程状态 | 算法、锁竞争、GC、忙循环 |
| cgroup CPU | cpu.stat、throttled 时间 |
配额过低、突发流量 |
| 存储 | iostat、容器 I/O、应用 fsync |
overlay copy-up、磁盘饱和 |
| 网络 | ss、抓包、重传、conntrack |
MTU、连接池、NAT/防火墙 |
基准测试要固定镜像 digest、CPU/内存、存储、并发、数据集和网络路径;不要拿 Desktop VM 的结果直接代表 Linux 生产机。
9. 安全和上线检查表
- 基础镜像和依赖有版本/ digest,扫描结果已评估。
- 应用使用非 root,删除不必要 capability,启用 seccomp/LSM。
- 根文件系统尽可能只读,写路径使用明确的 volume/tmpfs。
- 没有
--privileged、宿主根目录挂载或无必要的 Docker socket。 - 只发布必要端口,数据库和管理端口未暴露公网。
- 设置 CPU、内存、PIDs、日志大小和重启策略。
- 有健康检查、优雅退出、结构化日志和关键指标。
- 镜像、配置、volume、备份和恢复流程已演练。
- 发生 OOM、磁盘满、DNS 失败、证书过期时有 runbook。
10. 按威胁模型建立安全基线
先明确攻击者和资产:
| 场景 | 主要资产 | 首要控制 |
|---|---|---|
| 不可信应用代码 | 宿主内核、其他容器、凭据 | rootless/沙箱、非 root、seccomp/LSM、无 socket |
| 多团队共享构建机 | registry 凭据、源码、daemon | 隔离 runner、短期 token、构建上下文和网络限制 |
| 生产 Web 服务被 RCE | 数据库、云 metadata、宿主设备 | egress 限制、最小 capability、只读挂载、秘密分层 |
| 供应链被污染 | 镜像、依赖、部署集群 | digest、签名、SBOM、可审计构建和回滚 |
安全措施的强度要和资产、攻击面、可接受故障匹配。把所有容器都设成 --privileged 是“让测试通过”,不是安全设计;把所有 syscall 都禁掉也会破坏可用性。
10.1 运行时基线快照
docker info > docker-info.txt
docker version > docker-version.txt
docker ps -a --no-trunc > containers.txt
docker network ls > networks.txt
docker volume ls > volumes.txt
docker system df -v > disk-usage.txt
关键配置应进入变更审计:daemon.json、systemd drop-in、registry CA、默认 seccomp/AppArmor/SELinux policy、主机内核和 Docker 包版本。
11. 失败实验:权限、网络和资源
11.1 非 root 与只读根
docker run --rm \
--user 10001:10001 \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev \
alpine:3.20 sh -c 'id; touch /etc/should-fail; touch /tmp/ok'
用失败结果确认应用真正需要哪些写路径和权限,再为单一路径提供 volume,而不是恢复 root/关闭只读。
11.2 capability 的最小增量
docker run --rm --cap-drop=ALL alpine:3.20 \
sh -c 'ip link show' || true
docker run --rm --cap-drop=ALL --cap-add=NET_RAW \
alpine:3.20 sh -c 'grep Cap /proc/self/status'
能力名称、内核版本和工具行为要结合实际验证;--cap-add=ALL 不是排障的最小修复。
11.3 资源压力与事件时间线
docker run -d --name pressure-lab \
--memory 128m --cpus 0.25 --pids-limit 128 \
--restart on-failure:3 \
alpine:3.20 sh -c 'while :; do x="$(head -c 1M /dev/zero)"; done'
docker stats --no-stream pressure-lab
docker inspect pressure-lab --format \
'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} restarts={{.RestartCount}}'
docker events --filter container=pressure-lab --since 1m
实验后删除容器并检查日志、宿主 dmesg 和 cgroup events。不要在生产节点制造 OOM;使用隔离 VM 或受控测试 runner。
12. 可观测性不是 docker stats 的别名
12.1 指标分层
宿主机:CPU、内存、磁盘、conntrack、文件句柄、内核 OOM
runtime:容器重启、OOMKilled、health、镜像拉取、事件
cgroup:memory.current/max/events、cpu.stat throttled、pids.current/max
网络:连接数、重传、DNS 延迟、入口/出口字节、丢包
应用:请求量、错误率、延迟分位数、队列深度、业务成功率
只有容器 CPU 下降并不能证明系统恢复:它可能在等待磁盘或下游连接。告警应能关联 trace/request ID、容器版本 digest、部署时间和节点。
12.2 日志轮转和采集故障
docker info --format '{{.LoggingDriver}}'
docker inspect api --format '{{json .HostConfig.LogConfig}}'
du -sh /var/lib/docker/containers/* 2>/dev/null | sort -h | tail
日志采集器不可用时,要定义是阻塞应用、丢弃日志还是本地缓冲;本地缓冲必须有上限。日志中禁止密码、token、完整 cookie 和不必要个人信息,避免“为了排障”扩大数据泄露。
12.3 容器身份与审计
每次生产请求应能关联:
- 镜像 digest、源码 revision 和构建流水线。
- Compose/Kubernetes 配置版本、发布操作者和时间。
- 容器 ID、节点、网络入口和相关 volume。
- daemon API、registry、secret manager 和主机登录审计。
tag 是给人看的别名,审计主键应使用 digest 和发布记录。
13. 本篇小结
Docker 安全的核心是最小权限和可观测的失败:少给 capability,少暴露网络和文件,限制资源,记录完整证据,允许快速回滚。性能优化也必须建立在 cgroup、存储和网络数据之上,而不是只比较镜像体积。
| 上一篇 | 下一篇 |
|---|---|
| 10-底层原理与运行时 | 12-实战项目与生产发布 |