Docker 完全指南 / 12 - 安全加固
12 - 安全加固
理解容器安全机制:namespace、cgroup、seccomp、AppArmor,以及 Rootless 模式与安全最佳实践。
12.1 容器安全概述
容器安全是一个多层次的防御体系,涵盖从内核到应用的各个层面。
容器安全层次:
┌─────────────────────────────────────┐
│ 应用层安全: 代码审计、依赖扫描 │
├─────────────────────────────────────┤
│ 镜像安全: 最小镜像、漏洞扫描、签名 │
├─────────────────────────────────────┤
│ 运行时安全: seccomp、AppArmor/SELinux│
├─────────────────────────────────────┤
│ 容器隔离: namespace、cgroup │
├─────────────────────────────────────┤
│ 宿主机安全: 内核加固、Rootless │
├─────────────────────────────────────┤
│ 网络安全: 网络策略、TLS、防火墙 │
└─────────────────────────────────────┘
容器 vs 虚拟机安全对比
| 维度 | 容器 | 虚拟机 |
|---|---|---|
| 隔离级别 | 进程级 (namespace) | 硬件级 (hypervisor) |
| 内核共享 | ✅ 共享宿主机内核 | ❌ 独立内核 |
| 攻击面 | 较大(共享内核) | 较小 |
| 逃逸风险 | 较高 | 较低 |
| 启动安全 | 快速销毁重建 | 迁移成本高 |
12.2 Linux Namespace 安全
Namespace 隔离类型
| Namespace | 隔离内容 | 安全影响 |
|---|---|---|
| PID | 进程 ID | 看不到宿主机其他进程 |
| Network | 网络栈 | 独立的网络接口和端口 |
| Mount | 文件系统挂载 | 无法访问宿主机文件系统 |
| UTS | 主机名 | 独立的 hostname |
| IPC | 进程间通信 | 隔离信号量和消息队列 |
| User | 用户 ID | 非特权用户映射 |
User Namespace 实验
# 查看当前用户的 UID 映射
cat /proc/self/uid_map
# 使用 User Namespace 运行容器
docker run --rm --userns=host alpine id
# 在容器内查看 UID 映射
docker run --rm alpine cat /proc/self/uid_map
PID Namespace 验证
# 容器内只能看到自己的进程
docker run --rm alpine ps aux
# PID USER TIME COMMAND
# 1 root 0:00 ps aux
# 对比宿主机
ps aux | wc -l # 可能有上百个进程
12.3 Cgroup 资源限制
内存限制
# 限制内存 256MB,swap 512MB
docker run -d --name mem-limited \
--memory=256m \
--memory-swap=512m \
--memory-reservation=128m \
--oom-kill-disable=false \
nginx:alpine
# 查看内存使用
docker stats mem-limited --no-stream
# 查看 cgroup 内存限制
docker exec mem-limited cat /sys/fs/cgroup/memory.max
CPU 限制
# 限制使用 1.5 个 CPU
docker run -d --name cpu-limited --cpus=1.5 nginx:alpine
# 绑定到特定 CPU 核心
docker run -d --name cpu-pinned --cpuset-cpus="0,1" nginx:alpine
# CPU 权重(相对权重,默认 1024)
docker run -d --name high-priority --cpu-shares=2048 nginx:alpine
docker run -d --name low-priority --cpu-shares=512 nginx:alpine
# 查看 CPU 使用
docker stats cpu-limited --no-stream
I/O 限制
# 限制块设备读写速度
docker run -d --name io-limited \
--device-read-bps /dev/sda:10mb \
--device-write-bps /dev/sda:5mb \
--device-read-iops /dev/sda:1000 \
--device-write-iops /dev/sda:500 \
nginx:alpine
进程数限制
# 限制最大进程数
docker run -d --name pid-limited --pids-limit=50 nginx:alpine
# 防止 fork bomb
docker run -d --pids-limit=100 nginx:alpine
资源限制最佳实践
| 资源 | 生产建议 | 说明 |
|---|---|---|
| 内存 | 必须设置 --memory | 防止 OOM 影响宿主机 |
| CPU | 推荐设置 --cpus | 防止单容器占用全部 CPU |
| 进程数 | 推荐设置 --pids-limit | 防止 fork bomb |
| I/O | 按需设置 | 适用于 I/O 密集型应用 |
12.4 Seccomp 安全计算模式
Seccomp (Secure Computing Mode) 限制容器可以调用的系统调用(syscall)。
Docker 默认 Seccomp 配置
Docker 默认禁止约 44 个危险的系统调用:
被禁止的系统调用示例:
├── mount / umount: 防止挂载文件系统
├── reboot: 防止重启系统
├── kexec_load: 防止加载新内核
├── open_by_handle_at: 防止绕过文件系统权限
├── init_module / finit_module: 防止加载内核模块
├── bpf: 防止操作 BPF 程序
└── ...
使用自定义 Seccomp 配置
# 查看默认 Seccomp 配置
docker info | grep -i seccomp
# 使用自定义 Seccomp 配置
docker run --security-opt seccomp=my-seccomp.json nginx:alpine
# 禁用 Seccomp(不推荐)
docker run --security-opt seccomp=unconfined nginx:alpine
自定义 Seccomp Profile
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "open", "close", "stat", "fstat",
"lseek", "mmap", "mprotect", "munmap", "brk",
"ioctl", "access", "pipe", "select", "sched_yield",
"dup", "dup2", "nanosleep", "getpid", "clone",
"execve", "exit", "wait4", "kill", "fcntl",
"flock", "fsync", "ftruncate", "getdents",
"getcwd", "chdir", "mkdir", "rmdir", "unlink",
"readlink", "chmod", "chown", "getuid", "getgid",
"geteuid", "getegid", "getppid", "getpgrp",
"setsid", "setuid", "setgid", "getgroups",
"setgroups", "sigaltstack", "rt_sigaction",
"rt_sigprocmask", "socket", "connect", "accept",
"sendto", "recvfrom", "bind", "listen", "getsockname",
"getpeername", "socketpair", "epoll_create",
"epoll_ctl", "epoll_wait", "clock_gettime",
"exit_group", "epoll_create1", "pipe2",
"pread64", "pwrite64", "readv", "writev",
"signalfd4", "eventfd2", "epoll_create1",
"dup3", "prlimit64", "getrandom"],
"action": "SCMP_ACT_ALLOW"
}
]
}
12.5 AppArmor 与 SELinux
AppArmor
# 查看 Docker 默认 AppArmor 配置
sudo cat /etc/apparmor.d/docker-default
# 使用自定义 AppArmor 配置
docker run --security-opt apparmor=my-profile nginx:alpine
# 禁用 AppArmor(不推荐)
docker run --security-opt apparmor=unconfined nginx:alpine
# 查看容器的 AppArmor 状态
docker inspect --format '{{.AppArmorProfile}}' my-container
SELinux
# 启用 SELinux 标签
docker run --security-opt label=type:my_container_t nginx:alpine
# 禁用 SELinux 标签
docker run --security-opt label=disable nginx:alpine
# 查看 SELinux 状态
getenforce
12.6 Rootless 模式
优势
| 特性 | Root 模式 | Rootless 模式 |
|---|---|---|
| daemon 运行身份 | root | 普通用户 |
| 容器默认用户 | root | root (在 namespace 中) |
| 宿主机影响 | daemon 漏洞可获取 root | daemon 漏洞仅获取用户权限 |
| 端口 < 1024 | ✅ | ❌(需额外配置) |
| cgroup v2 完整支持 | ✅ | 部分 |
配置 Rootless
# 安装 Rootless Docker
dockerd-rootless-setuptool.sh install
# 配置环境变量
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# 使用 systemd 管理
systemctl --user enable docker
systemctl --user start docker
# 开启 lingering
sudo loginctl enable-linger $(whoami)
Rootless 限制与解决方案
# 问题: 无法使用 < 1024 端口
# 解决: 设置内核参数
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# 问题: 需要额外的 uid/gid 映射
# 解决: 编辑 /etc/subuid 和 /etc/subgid
username:100000:65536
# 问题: overlay2 需要 fuse-overlayfs
# 解决: 安装 fuse-overlayfs
sudo apt-get install fuse-overlayfs
12.7 容器安全最佳实践
镜像安全
# ✅ 使用最小基础镜像
FROM alpine:3.19
# ✅ 使用非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# ✅ 使用只读文件系统
# (运行时: docker run --read-only)
# ✅ 不安装不必要的包
RUN apk add --no-cache --virtual .build-deps build-base && \
apk add --no-cache python3 && \
apk del .build-deps
# ✅ 固定版本
FROM python:3.11.7-slim-bookworm
运行时安全
# ✅ 只读根文件系统
docker run --read-only --tmpfs /tmp nginx:alpine
# ✅ 禁用特权模式
# ❌ docker run --privileged (极不安全)
# ✅ 仅添加所需的能力
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx:alpine
# ✅ 禁止获取新权限
docker run --security-opt=no-new-privileges nginx:alpine
# ✅ 使用自定义 Seccomp
docker run --security-opt seccomp=custom-seccomp.json nginx:alpine
# ✅ 删除 Linux capabilities
docker run --cap-drop=ALL my-app:latest
# ✅ 设置内存和 CPU 限制
docker run -m 512m --cpus=1 my-app:latest
# ✅ 设置 PID 限制
docker run --pids-limit=100 my-app:latest
Linux Capabilities
| Capability | 说明 | 是否需要 |
|---|---|---|
NET_BIND_SERVICE | 绑定 < 1024 端口 | 按需 |
CHOWN | 修改文件所有者 | 按需 |
SETUID/SETGID | 切换用户/组 | 按需 |
NET_RAW | 使用原始套接字 | 通常不需要 |
SYS_ADMIN | 大量系统管理操作 | 极不推荐 |
ALL | 所有能力 | 永远不要使用 |
# 最小权限运行
docker run \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--security-opt=no-new-privileges \
--pids-limit=100 \
-m 256m \
nginx:alpine
12.8 Docker Content Trust (DCT)
# 启用内容信任(仅拉取签名镜像)
export DOCKER_CONTENT_TRUST=1
# 拉取签名镜像
docker pull my-user/my-app:v1.0
# 推送签名镜像
docker push my-user/my-app:v1.0
# 首次会提示创建签名密钥
# 查看签名信息
docker trust inspect --pretty my-user/my-app
# 禁用内容信任
export DOCKER_CONTENT_TRUST=0
12.9 安全扫描
# Docker Scout
docker scout cves nginx:alpine
docker scout quickview nginx:alpine
# Trivy
trivy image nginx:alpine
trivy image --severity HIGH,CRITICAL nginx:alpine
# 扫描 Dockerfile
trivy config Dockerfile
# 扫描文件系统
trivy fs --security-checks vuln,config .
要点回顾
| 要点 | 核心内容 |
|---|---|
| 隔离机制 | namespace (隔离) + cgroup (限制) + seccomp (系统调用过滤) |
| 最小权限 | --cap-drop=ALL + --cap-add 仅添加所需能力 |
| Rootless | daemon 以非 root 用户运行,降低攻击面 |
| 只读文件系统 | --read-only + --tmpfs /tmp 防止文件篡改 |
| 镜像扫描 | Docker Scout / Trivy 扫描漏洞 |
注意事项
永远不要使用
--privileged: 这会赋予容器几乎等同于宿主机 root 的权限,是最大的安全隐患。
及时更新基础镜像: 定期重建镜像以获取安全补丁。设置 CI/CD 定时任务自动扫描。
限制容器资源: 生产环境必须设置内存、CPU、PID 限制,防止单容器影响整个宿主机。
下一步
→ 13 - 日志管理:学习 Docker 日志驱动与集中日志方案。