强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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普通用户
容器默认用户rootroot (在 namespace 中)
宿主机影响daemon 漏洞可获取 rootdaemon 漏洞仅获取用户权限
端口 < 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 仅添加所需能力
Rootlessdaemon 以非 root 用户运行,降低攻击面
只读文件系统--read-only + --tmpfs /tmp 防止文件篡改
镜像扫描Docker Scout / Trivy 扫描漏洞

注意事项

永远不要使用 --privileged: 这会赋予容器几乎等同于宿主机 root 的权限,是最大的安全隐患。

及时更新基础镜像: 定期重建镜像以获取安全补丁。设置 CI/CD 定时任务自动扫描。

限制容器资源: 生产环境必须设置内存、CPU、PID 限制,防止单容器影响整个宿主机。


下一步

13 - 日志管理:学习 Docker 日志驱动与集中日志方案。