目录

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 upgradeapk add,这些变更不可复现、不可审计。

5. 秘密管理

镜像层、环境变量、docker inspect、命令行历史和日志都可能泄露秘密。优先级通常是:

  1. 外部 secret manager 或编排平台 secret。
  2. 只读挂载的短生命周期 secret 文件。
  3. 受严格权限控制的运行时环境变量。
  4. 绝不把秘密写入 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-实战项目与生产发布