容器安全与运行时原理
·
第一层:容器安全与运行时原理
一、Seccomp/AppArmor安全机制
1.1 Seccomp(安全计算模式)
核心原理
┌─────────────────────────────────────────────────────────────────┐
│ Seccomp 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户进程 │
│ │ │
│ │ 系统调用 (syscall) │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Seccomp BPF Filter │ ◄── Seccomp Profile (JSON) │
│ │ (内核eBPF程序) │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────┼──────┐ │
│ ▼ ▼ ▼ │
│ ALLOW ERRNO KILL │
│ (允许) (返回错误)(终止进程) │
│ │
└─────────────────────────────────────────────────────────────────┘
Seccomp Profile结构
{
"defaultAction": "ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "exit", "sigreturn"],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["chmod", "chown"],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1
},
{
"names": ["mount", "umount2"],
"action": "SCMP_ACT_KILL"
}
]
}
Kubernetes中使用Seccomp
apiVersion: v1
kind: Pod
metadata:
name: seccomp-demo
annotations:
seccomp.security.alpha.kubernetes.io/pod: "runtime/default"
# 自定义Profile:
# seccomp.security.alpha.kubernetes.io/pod: "localhost/profiles/custom.json"
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # 或 Localhost
localhostProfile: profiles/custom.json # type=Localhost时指定
containers:
- name: app
image: nginx
securityContext:
seccompProfile:
type: RuntimeDefault
常见系统调用分类
| 类别 | 系统调用 | 典型操作 |
|---|---|---|
| 文件I/O | read, write, open, close | 文件读写 |
| 进程管理 | fork, clone, execve, exit | 进程创建/退出 |
| 网络 | socket, bind, listen, accept | 网络通信 |
| 内存 | mmap, mprotect, brk | 内存管理 |
| 信号 | kill, sigaction, sigprocmask | 信号处理 |
| 危险操作 | mount, pivot_root, chroot | 文件系统操作 |
Seccomp BPF程序加载流程详解
┌─────────────────────────────────────────────────────────────────┐
│ Seccomp BPF 程序加载流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 用户态: 解析JSON Profile │
│ ┌──────────────────────┐ │
│ │ seccomp.json │ │
│ │ (defaultAction, │ │
│ │ syscalls rules) │ │
│ └──────────┬───────────┘ │
│ │ libseccomp解析 │
│ ▼ │
│ 2. 用户态: 生成BPF程序 │
│ ┌──────────────────────┐ │
│ │ BPF指令序列 │ │
│ │ - 加载arch │ │
│ │ - 比较syscall号 │ │
│ │ - 跳转至匹配规则 │ │
│ │ - 返回Action │ │
│ └──────────┬───────────┘ │
│ │ prctl(PR_SET_SECCOMP, │
│ │ SECCOMP_MODE_FILTER, &prog) │
│ ▼ │
│ 3. 内核态: seccomp过滤器安装 │
│ ┌──────────────────────┐ │
│ │ 内核验证BPF程序 │ │
│ │ - 检查指令数限制 │ │
│ │ (MAX_BPF_SIZE) │ │
│ │ - 验证无循环 │ │
│ │ - 验证无危险操作 │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ 4. 运行时: 系统调用拦截 │
│ ┌──────────────────────┐ │
│ │ 进程发起syscall │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ BPF过滤器执行 │ ◄── 在syscall入口点 │
│ │ (SECCOMP_RET_xxx) │ 上下文切换之前 │
│ └──────────────────────┘ │
│ │
│ 关键系统调用: │
│ - prctl(PR_SET_SECCOMP): 安装过滤器 │
│ - seccomp(SECCOMP_SET_MODE_FILTER): 新版安装接口 │
│ - seccomp(SECCOMP_GET_NOTIF_SIZES): 获取通知大小 │
│ │
│ BPF程序执行上下文: │
│ - seccomp_data结构体包含: │
│ · int nr (系统调用号) │
│ · __u32 arch (架构, AUDIT_ARCH_X86_64等) │
│ · __u64 instruction_pointer (指令指针) │
│ · __u64 args[6] (系统调用参数) │
│ │
└─────────────────────────────────────────────────────────────────┘
Seccomp BPF程序示例(编译后的指令序列)
// BPF程序伪代码 - 拦截mount系统调用
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), // 加载syscall号
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mount, 0, 1), // 比较是否为mount
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), // 匹配则杀死进程
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), // 不匹配则允许
// 带架构检查的完整示例
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)), // 加载架构
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0), // 检查x86_64
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), // 架构不匹配则终止
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), // 加载syscall号
// ... 后续规则匹配
系统调用追踪方法
方法一: strace追踪
# 追踪容器所有系统调用
strace -f -e trace=all -p <container_pid> -o /tmp/strace.log
# 仅追踪特定类别
strace -f -e trace=file,network,process -p <container_pid>
# 统计系统调用频率(用于生成Profile)
strace -c -f -p <container_pid>
# 输出示例:
# % time seconds usecs/call calls errors syscall
# ------ ----------- ----------- --------- --------- ----------------
# 25.34 0.002534 2 1200 3 read
# 18.72 0.001872 3 680 write
# 12.45 0.001245 1 1100 1 openat
# 5.67 0.000567 4 142 mmap
# 0.01 0.000001 1 1 mount ← 可能需要拦截
# 追踪特定进程的系统调用并生成列表
strace -f -e trace=all -p <pid> 2>&1 | \
awk '/^[0-9]+ /{print $2}' | sort -u > /tmp/syscalls.txt
方法二: oci-seccomp-bpf-hook(推荐)
# 安装oci-seccomp-bpf-hook
make && sudo make install
# 配置Podman/Docker使用hook
# /etc/oci/hooks.d/oci-seccomp-bpf-hook.json
{
"hook": "/usr/libexec/oci/hooks.d/oci-seccomp-bpf-hook",
"stages": ["prestart"],
"conditions": [
{
"type": "annotation",
"key": "io.containers.trace-syscall",
"value": ".*"
}
]
}
# 运行容器并自动追踪系统调用
podman run --annotation io.containers.trace-syscall=of:/tmp/profile.json \
alpine:latest sh -c "apk add --no-cache curl && curl https://example.com"
# 生成的Profile可直接用于后续容器启动
podman run --security-opt seccomp=/tmp/profile.json alpine:latest curl https://example.com
方法三: sysdig高级追踪
# 实时监控容器系统调用
sysdig -c syscalllog container.id=<container_id>
# 过滤特定系统调用
sysdig -c syscalllog "container.id=<id> and evt.type in (mount, umount2, chroot)"
# 生成系统调用热力图
sysdig -c spectrogram "container.id=<id>"
自定义Seccomp Profile生成工具链
┌─────────────────────────────────────────────────────────────────┐
│ Seccomp Profile 生成工具链 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ strace/sysdig │───►│ 原始syscall列表 │ │
│ │ (运行时追踪) │ │ │ │
│ └─────────────────┘ └────────┬─────────┘ │
│ │ │
│ ┌─────────────────┐ │ │
│ │ oci-seccomp- │───►│ 自动生成JSON │ │
│ │ bpf-hook │ │ Profile │ │
│ └─────────────────┘ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Profile加工与优化 │ │
│ │ ┌────────────────┐ ┌────────────────┐ │ │
│ │ │ seccomp-profile│ │ security-profile│ │ │
│ │ │ -operator │ │ -operator (SPO) │ │ │
│ │ │ (自动管理) │ │ (K8s集成) │ │ │
│ │ └────────────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 工具对比: │
│ ┌──────────────────┬──────────────┬──────────────────┐ │
│ │ 工具 │ 输出格式 │ 特点 │ │
│ ├──────────────────┼──────────────┼──────────────────┤ │
│ │ strace -c │ 文本统计 │ 最简单,通用 │ │
│ │ oci-seccomp-bpf │ JSON Profile │ 运行时BPF,精确 │ │
│ │ seccomp-json │ JSON Profile │ 手动编辑,灵活 │ │
│ │ SPO(security- │ CRD+Profile │ K8s原生管理 │ │
│ │ profile-op) │ │ │ │
│ └──────────────────┴──────────────┴──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
使用security-profiles-operator在Kubernetes中管理Seccomp Profile
# 安装SPO
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/security-profiles-operator/main/deploy/operator.yaml
# 定义Seccomp Profile
apiVersion: security-profiles-operator.x-k8s.io/v1beta1
kind: SeccompProfile
metadata:
name: nginx-profile
namespace: security-profiles
spec:
defaultAction: SCMP_ACT_ERRNO
targetWorkload:
kind: Deployment
selector:
matchLabels:
app: nginx
syscalls:
- action: SCMP_ACT_ALLOW
names:
- read
- write
- openat
- close
- fstat
- newfstatat
- lseek
- mmap
- mprotect
- munmap
- brk
- rt_sigaction
- rt_sigprocmask
- ioctl
- access
- faccessat
- faccessat2
- pipe
- poll
- select
- sched_yield
- mremap
- nanosleep
- clock_nanosleep
- alarm
- getpid
- sendfile
- socket
- connect
- accept
- sendto
- recvfrom
- bind
- listen
- setsockopt
- getsockopt
- clone
- fork
- vfork
- execve
- exit
- wait4
- kill
- uname
- semget
- semop
- semctl
- shmdt
- dup
- dup2
- dup3
- fcntl
- flock
- fsync
- fdatasync
- truncate
- ftruncate
- getdents
- getdents64
- getcwd
- chdir
- rename
- renameat
- renameat2
- mkdir
- mkdirat
- rmdir
- creat
- link
- unlink
- unlinkat
- readlink
- readlinkat
- chmod
- fchmod
- fchmodat
- chown
- fchown
- lchown
- fchownat
- umask
- gettimeofday
- getrlimit
- getrusage
- sysinfo
- times
- getuid
- getgid
- setuid
- setgid
- geteuid
- getegid
- getgroups
- setgroups
- getresuid
- getresgid
- sigaltstack
- arch_prctl
- set_tid_address
- set_robust_list
- futex
- sched_getaffinity
- exit_group
- epoll_create
- epoll_create1
- epoll_ctl
- epoll_wait
- tgkill
- clock_gettime
- clock_getres
- action: SCMP_ACT_ERRNO
names:
- mount
- umount2
- pivot_root
- chroot
- ptrace
- kexec_load
- kexec_file_load
- reboot
- init_module
- finit_module
- delete_module
- iopl
- ioperm
- swapon
- swapoff
- sysctl
AppArmor与SELinux对比
┌─────────────────────────────────────────────────────────────────┐
│ AppArmor vs SELinux 深度对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 维度 AppArmor SELinux │
│ ───────────── ────────── ──────── │
│ 访问控制模型 路径名(文件路径) 标签(inode扩展属性) │
│ 策略语言 简洁(路径规则) 复杂(type enforcement) │
│ 默认发行版 Ubuntu/SUSE RHEL/Fedora/CentOS │
│ 学习模式 是(complain模式) 是(permissive模式) │
│ 策略生成 aa-genprof/aa-logprof audit2allow │
│ MLS/MCS支持 否 是(多级/多类安全) │
│ 文件系统依赖 无(路径名) 需要扩展属性支持 │
│ 容器支持 Docker/Podman原生 需sVirt适配 │
│ 内核模块 apparmor selinux(挂钩到LSM) │
│ 策略热加载 支持 支持 │
│ 网络控制 粗粒度(协议族) 细粒度(端口+类型) │
│ │
│ 选择建议: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景 推荐方案 │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ Ubuntu/Debian环境 AppArmor │ │
│ │ RHEL/CentOS环境 SELinux │ │
│ │ 容器安全(简单) AppArmor │ │
│ │ 多级安全(政府/军事) SELinux (MLS) │ │
│ │ 快速原型/开发 AppArmor (complain模式) │ │
│ │ 生产强制访问控制 SELinux (enforcing模式) │ │
│ │ NFS/无扩展属性FS AppArmor (路径名不受限) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
SELinux容器策略示例(sVirt)
# 查看容器SELinux标签
ps -eZ | grep container_t
# 容器类型:
# container_t - 普通容器(受限)
# svirt_lxc_net_t - 虚拟化容器
# container_runtime_t - 容器运行时本身
# SELinux文件标签与容器
# container_file_t - 容器可读写的文件
# container_share_t - 容器只读共享文件
# container_ro_file_t - 容器只读文件
# 为卷设置正确的SELinux标签
docker run -v /host/path:/container:path:z nginx # 自动重新标记(私有)
docker run -v /host/path:/container:path:Z nginx # 自动重新标记(共享)
# 查看SELinux拒绝日志
ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent
sealert -a /var/log/audit/audit.log
生产环境Seccomp Profile模板(按应用类型分类)
Web服务器(Nginx)Profile
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
"syscalls": [
{
"names": [
"read", "write", "close", "fstat", "newfstatat", "lseek",
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "ioctl", "access", "faccessat",
"pipe", "poll", "sched_yield", "mremap", "nanosleep",
"clock_nanosleep", "getpid", "sendfile", "socket",
"connect", "accept", "accept4", "sendto", "recvfrom",
"bind", "listen", "setsockopt", "getsockopt", "clone",
"execve", "exit", "exit_group", "wait4", "uname",
"fcntl", "flock", "fsync", "fdatasync", "dup", "dup2",
"dup3", "getdents", "getdents64", "getcwd", "chdir",
"rename", "mkdir", "mkdirat", "rmdir", "creat", "link",
"unlink", "unlinkat", "readlink", "readlinkat", "chmod",
"fchmod", "fchmodat", "chown", "fchown", "lchown",
"fchownat", "umask", "gettimeofday", "getrlimit",
"getrusage", "sysinfo", "times", "getuid", "getgid",
"setuid", "setgid", "geteuid", "getegid", "getgroups",
"setgroups", "getresuid", "getresgid", "sigaltstack",
"arch_prctl", "set_tid_address", "set_robust_list",
"futex", "sched_getaffinity", "epoll_create",
"epoll_create1", "epoll_ctl", "epoll_wait", "epoll_pwait",
"tgkill", "clock_gettime", "clock_getres", "openat",
"prctl", "prlimit64", "getrandom", "membarrier",
"madvise", "recvmsg", "sendmsg", "shutdown",
"getsockname", "getpeername", "socketpair",
"sigaltstack", "pipe2", "statfs", "fstatfs",
"statx", "pread64", "pwrite64", "splice", "tee",
"copy_file_range", "getcpu", "sysinfo"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["personality"],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"op": "SCMP_CMP_EQ",
"value": 0
}
]
}
]
}
数据库(PostgreSQL)Profile关键差异
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"read", "write", "close", "fstat", "newfstatat", "lseek",
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "ioctl", "access", "faccessat",
"pipe", "poll", "sched_yield", "mremap", "nanosleep",
"clock_nanosleep", "getpid", "sendfile", "socket",
"connect", "accept", "accept4", "sendto", "recvfrom",
"bind", "listen", "setsockopt", "getsockopt", "clone",
"fork", "execve", "exit", "exit_group", "wait4",
"uname", "fcntl", "flock", "fsync", "fdatasync",
"dup", "dup2", "dup3", "getdents", "getdents64",
"getcwd", "chdir", "rename", "mkdir", "mkdirat",
"rmdir", "unlink", "unlinkat", "readlink", "chmod",
"fchmod", "fchmodat", "chown", "fchown", "fchownat",
"umask", "gettimeofday", "getrlimit", "getrusage",
"sysinfo", "getuid", "getgid", "setuid", "setgid",
"geteuid", "getegid", "getgroups", "setgroups",
"getresuid", "getresgid", "sigaltstack", "arch_prctl",
"set_tid_address", "set_robust_list", "futex",
"sched_getaffinity", "epoll_create1", "epoll_ctl",
"epoll_wait", "epoll_pwait", "tgkill", "clock_gettime",
"openat", "prctl", "prlimit64", "getrandom",
"membarrier", "madvise", "recvmsg", "sendmsg",
"shutdown", "getsockname", "getpeername",
"semget", "semop", "semctl", "shmget", "shmat",
"shmdt", "shmctl", "msgget", "msgsnd", "msgrcv",
"msgctl", "ftruncate", "truncate", "statfs", "fstatfs",
"statx", "pread64", "pwrite64", "copy_file_range",
"getcpu", "process_vm_readv", "process_vm_writev",
"seccomp", "pipe2", "preadv", "pwritev"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Go应用通用Profile
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"read", "write", "close", "fstat", "newfstatat", "lseek",
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "ioctl", "access", "faccessat",
"pipe", "pipe2", "poll", "sched_yield", "mremap",
"nanosleep", "clock_nanosleep", "getpid", "sendfile",
"socket", "connect", "accept", "accept4", "sendto",
"recvfrom", "bind", "listen", "setsockopt", "getsockopt",
"clone", "fork", "execve", "exit", "exit_group", "wait4",
"kill", "uname", "fcntl", "flock", "fsync", "fdatasync",
"dup", "dup2", "dup3", "getdents", "getdents64",
"getcwd", "chdir", "rename", "mkdir", "mkdirat",
"rmdir", "unlink", "unlinkat", "readlink", "readlinkat",
"chmod", "fchmod", "fchmodat", "chown", "fchown",
"fchownat", "umask", "gettimeofday", "getrlimit",
"getrusage", "sysinfo", "times", "getuid", "getgid",
"setuid", "setgid", "geteuid", "getegid", "getgroups",
"setgroups", "getresuid", "getresgid", "sigaltstack",
"arch_prctl", "set_tid_address", "set_robust_list",
"futex", "sched_getaffinity", "epoll_create1",
"epoll_ctl", "epoll_wait", "epoll_pwait", "tgkill",
"clock_gettime", "clock_getres", "openat", "prctl",
"prlimit64", "getrandom", "membarrier", "madvise",
"recvmsg", "sendmsg", "shutdown", "getsockname",
"getpeername", "socketpair", "statfs", "fstatfs",
"statx", "pread64", "pwrite64", "splice", "tee",
"copy_file_range", "getcpu", "sysinfo", "seccomp",
"fcntl", "fadvise64", "sync_file_range", "fallocate",
"timer_create", "timer_settime", "timer_gettime",
"timer_delete", "clock_adjtime", "signalfd4",
"eventfd2", "inotify_init1", "inotify_add_watch"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["personality"],
"action": "SCMP_ACT_ALLOW",
"args": [{"index": 0, "op": "SCMP_CMP_EQ", "value": 0}]
}
]
}
Profile调试与故障排查
# 1. 查看容器当前Seccomp状态
cat /proc/<pid>/status | grep Seccomp
# Seccomp: 2 (0=禁用, 1=strict, 2=filter)
# 2. 查看当前BPF过滤器
cat /proc/<pid>/syscall
# 显示最后一次系统调用信息
# 3. 使用auditd追踪被Seccomp拒绝的操作
# /etc/audit/rules.d/audit.rules
-a always,exit -F arch=b64 -S mount -S umount2 -S chroot -F key=seccomp-deny
# 4. 将Profile从enforcing切换为permissive(日志模式)
# 修改defaultAction为SCMP_ACT_LOG, 记录但不拒绝
{
"defaultAction": "SCMP_ACT_LOG",
"syscalls": [...]
}
# 5. 使用journalctl查看seccomp拒绝日志
journalctl -t kernel | grep seccomp
# 或
dmesg | grep -i seccomp
# 6. 常见故障排查流程
# 容器启动失败 → 检查dmesg/journalctl → 识别被拒绝的syscall
# → 将该syscall加入允许列表 → 重新测试
1.2 AppArmor(应用程序装甲)
核心原理
┌─────────────────────────────────────────────────────────────────┐
│ AppArmor 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 进程启动 │
│ │ │
│ ▼ │
│ 内核加载AppArmor Profile │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ AppArmor强制模式 │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │文件访问│ │网络访问│ │ │
│ │ │控制 │ │控制 │ │ │
│ │ └────────┘ └────────┘ │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │能力控制│ │资源限制│ │ │
│ │ └────────┘ └────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
AppArmor Profile示例
#include <tunables/global>
profile nginx-profile flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
#include <abstractions/nameservice>
# 文件访问规则
/usr/sbin/nginx mr,
/etc/nginx/** r,
/var/log/nginx/** rw,
/var/cache/nginx/** rw,
/usr/share/nginx/** r,
# 网络规则
network inet tcp,
network inet6 tcp,
# 能力规则
capability setgid,
capability setuid,
capability net_bind_service,
# 拒绝规则
deny /etc/shadow r,
deny /root/** rw,
}
Kubernetes中使用AppArmor
apiVersion: v1
kind: Pod
metadata:
name: apparmor-demo
annotations:
container.apparmor.security.beta.kubernetes.io/app: "localhost/nginx-profile"
spec:
containers:
- name: app
image: nginx
1.3 Seccomp vs AppArmor对比
| 特性 | Seccomp | AppArmor |
|---|---|---|
| 控制粒度 | 系统调用级别 | 文件/网络/能力级别 |
| 配置复杂度 | 高(需了解syscall) | 中(路径规则) |
| 学习曲线 | 陡峭 | 中等 |
| 适用场景 | 限制系统调用 | 限制文件/网络访问 |
| 默认启用 | 容器运行时默认 | 需手动配置 |
| BPF支持 | 是(seccomp-bpf) | 否 |
二、Rootless容器
2.1 Rootless容器原理
传统容器 vs Rootless容器
┌─────────────────────────────────────────────────────────────────────┐
│ 传统容器(Rootful) │
├─────────────────────────────────────────────────────────────────────┤
│ 用户空间 │ 容器进程(root) │ 宿主机root映射 │
│ │ UID 0 │ UID 0 │
│ │ 全部能力 │ 全部能力 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Rootless容器 │
├─────────────────────────────────────────────────────────────────────┤
│ 用户空间 │ 容器进程(root) │ 宿主机普通用户映射 │
│ │ UID 0(容器内) │ UID 100000(宿主机) │
│ │ 子集能力 │ 受限能力 │
└─────────────────────────────────────────────────────────────────────┘
User Namespace映射机制
┌─────────────────────────────────────────────────────────────────┐
│ User Namespace UID映射 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 容器内UID 宿主机UID │
│ ────────── ────────── │
│ 0 (root) ───► 100000 │
│ 1 (daemon) ───► 100001 │
│ 2 (bin) ───► 100002 │
│ ... ... │
│ 65534 ───► 165534 │
│ │
│ /etc/subuid: username:100000:65536 │
│ /etc/subgid: username:100000:65536 │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 Rootless容器运行时配置
Podman Rootless模式
# 配置subuid/subgid
echo "username:100000:65536" >> /etc/subuid
echo "username:100000:65536" >> /etc/subgid
# 运行rootless容器
podman run --rm -it alpine:latest whoami
# 输出: root (但实际是普通用户)
# 检查容器内进程在宿主机的UID
podman run --rm alpine ps aux
Docker Rootless模式
# 安装rootless模式
dockerd-rootless-setuptool.sh install
# 配置环境变量
export DOCKER_HOST=unix:///run/user/$UID/docker.sock
# 运行容器
docker run --rm -it alpine:latest whoami
Kubernetes Rootless模式
# 使用kind创建rootless集群
kind create cluster --image kindest/node:v1.28.0 \
--config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- containerPath: /var/lib/kubelet
hostPath: /tmp/kubelet
EOF
2.3 Rootless容器限制与解决方案
| 限制 | 原因 | 解决方案 |
|---|---|---|
| 端口<1024 | 无net_bind_service能力 | sysctl设置net.ipv4.ip_unprivileged_port_start |
| 网络限制 | 无NET_ADMIN能力 | Slirp4netns/Pasta |
| Cgroup限制 | 无cgroup管理权限 | cgroup v2 + systemd delegation |
| NFS挂载 | 内核限制 | FUSE解决方案 |
| 性能开销 | 用户空间网络 | Pasta替代Slirp4netns |
User Namespace内核实现原理
┌─────────────────────────────────────────────────────────────────┐
│ User Namespace 内核实现 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 数据结构关系: │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ task_struct (进程描述符) │ │
│ │ ├── nsproxy │ │
│ │ │ ├── uts_ns │ │
│ │ │ ├── ipc_ns │ │
│ │ │ ├── mnt_ns │ │
│ │ │ ├── pid_ns_for_children │ │
│ │ │ ├── net_ns │ │
│ │ │ └── cgroup_ns │ │
│ │ │ │ │
│ │ ├── real_cred (真实凭据) │ │
│ │ │ ├── uid, gid, suid, sgid │ │
│ │ │ └── user_namespace (指向所属user_ns) │ │
│ │ │ │ │
│ │ └── nsproxy->user_ns (或单独的user_ns) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ UID映射内核结构: │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ uid_gid_map (在/proc/<pid>/uid_map) │ │
│ │ ├── first: 容器内起始UID │ │
│ │ ├── lower_first: 宿主机起始UID(需要权限) │ │
│ │ └── count: 映射范围大小 │ │
│ │ │ │
│ │ 示例: 0 100000 65536 │ │
│ │ 含义: 容器UID 0-65535 → 宿主机UID 100000-165535 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 权限检查流程(关键函数): │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. ns_capable(user_ns, CAP_SYS_ADMIN) │ │
│ │ - 检查是否在目标user_ns中具有CAP_SYS_ADMIN │ │
│ │ │ │
│ │ 2. mapped_kuid(user_ns, uid_in_container) │ │
│ │ - 将容器UID映射到内核kuid_t │ │
│ │ - from_kuid(user_ns->parent, kuid) │ │
│ │ │ │
│ │ 3. cap_raise_nscapable(user_ns, cap) │ │
│ │ - 在嵌套namespace中提升能力 │ │
│ │ - 最多嵌套32层(USER_NS_LIMIT) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 关键系统调用: │
│ - unshare(CLONE_NEWUSER): 创建新user namespace │
│ - clone(CLONE_NEWUSER): 创建进程时同时创建user ns │
│ - setns(fd, CLONE_NEWUSER): 加入已有user ns │
│ │
│ /proc接口: │
│ - /proc/<pid>/uid_map: UID映射配置 │
│ - /proc/<pid>/gid_map: GID映射配置 │
│ - /proc/<pid>/projid_map: Project ID映射(极少使用) │
│ │
└─────────────────────────────────────────────────────────────────┘
User Namespace创建与配置流程
# 手动创建user namespace并配置映射
# 步骤1: 创建新user namespace
unshare --user --map-root-user bash
# 或手动:
unshare -U bash
# 步骤2: 配置UID映射(需要父namespace权限)
# 在父namespace中:
echo "0 100000 65536" > /proc/<child_pid>/uid_map
echo "0 100000 65536" > /proc/<child_pid>/gid_map
# 步骤3: 禁用setgroups(必须先于gid_map)
echo "deny" > /proc/<child_pid>/setgroups
# 验证映射
cat /proc/self/uid_map
# 输出: 0 100000 65536
# 查看当前进程在各级namespace中的UID
# /proc/self/status中的字段:
# Uid: 0(root) 0(root) 0(root) 0(root) # 容器内视角
# 在宿主机视角(另一个终端):
ps -o pid,uid,user,cmd -p <pid>
# 显示UID为100000
内核源码关键路径
// kernel/user_namespace.c - 关键函数
// 创建user namespace
static struct ucounts *inc_user_namespaces(struct user_namespace *ns)
{
return inc_ucount(ns, current_euid(), UCOUNT_USER_NAMESPACES);
}
// UID映射写入
static ssize_t map_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos,
int cap_setid,
struct uid_gid_map *map,
struct uid_gid_map *parent_map)
{
// 权限检查:
// 1. 写入者必须在父namespace中
// 2. 需要CAP_SETUID/CAP_SETGID(在父namespace中)
// 3. 或需要CAP_SYS_ADMIN(在父namespace中)
// 映射规则验证:
// - 不能映射到父namespace之外的UID
// - 不能覆盖已映射的范围
}
// 权限检查: 进程A能否向进程B发送信号
static int kill_userns_check(int sig, struct pid *pid,
struct user_namespace *targ_ns)
{
// 检查信号发送者是否有权限:
// 1. 相同user namespace: 正常权限检查
// 2. 父namespace: 如果子ns的UID映射回父ns且有权限
// 3. 否则拒绝
}
Rootless容器网络方案深度对比
┌─────────────────────────────────────────────────────────────────┐
│ Rootless 网络方案对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方案 原理 性能 功能 推荐度 │
│ ──────── ────── ──── ──── ────── │
│ slirp4netns 用户态TCP/IP栈 低 基础 ★★☆☆☆ │
│ pasta libpnetns+tap 中 完整 ★★★★☆ │
│ Bypass4netns 非绕过模式 高 完整 ★★★★★ │
│ CNI-plugins (需要setuid) 高 完整 ★★★★☆ │
│ │
│ 性能对比(bandwidth): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 方案 TCP下载 TCP上传 UDP │ │
│ │ slirp4netns ~500MB/s ~400MB/s ~300MB/s │ │
│ │ pasta ~800MB/s ~700MB/s ~600MB/s │ │
│ │ bypass4netns ~1000MB/s ~900MB/s ~800MB/s │ │
│ │ rootful ~1000MB/s ~1000MB/s ~900MB/s │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 延迟对比(ping localhost): │
│ slirp4netns: 0.3-0.5ms │
│ pasta: 0.1-0.2ms │
│ bypass4netns: 0.05-0.1ms │
│ rootful: 0.01-0.05ms │
│ │
└─────────────────────────────────────────────────────────────────┘
slirp4netns详解
┌─────────────────────────────────────────────────────────────────┐
│ slirp4netns 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 容器进程 │
│ │ │
│ │ eth0 (tap设备) │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ slirp4netns 进程 │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ 用户态TCP/IP栈 (libslirp) │ │ │
│ │ │ - ARP处理 │ │ │
│ │ │ - IP路由 │ │ │
│ │ │ - TCP状态机 │ │ │
│ │ │ - NAT (端口映射) │ │ │
│ │ │ - DHCP服务器 │ │ │
│ │ │ - DNS代理 │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ tap接口 │ │
│ │ ▼ │ │
│ │ 宿主机网络栈 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 默认网络配置: │
│ - 容器IP: 10.0.2.100 │
│ - 网关: 10.0.2.2 │
│ - DNS: 10.0.2.3 │
│ - 子网: 10.0.2.0/24 │
│ │
│ 启动命令: │
│ slirp4netns --configure --mtu=65520 <pid> tap0 │
│ │
│ 端口映射: │
│ slirp4netns --port-handler=builtin:0.0.0.0:8080-80 \ │
│ --port-handler=builtin:0.0.0.0:8443-443 <pid> tap0 │
│ │
└─────────────────────────────────────────────────────────────────┘
pasta详解(推荐方案)
┌─────────────────────────────────────────────────────────────────┐
│ pasta ( passt + netns ) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 容器进程 │
│ │ │
│ │ eth0 (tap设备) │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ pasta 进程 │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ passt (用户态TCP/UDP) │ │ │
│ │ │ - 零拷贝(splice/sendfile) │ │ │
│ │ │ - 高性能epoll事件循环 │ │ │
│ │ │ - 完整TCP状态机 │ │ │
│ │ │ - DSCP/ECN支持 │ │ │
│ │ │ - TCP_MD5SIG支持 │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ tap设备管理 │ │ │
│ │ │ - 自动配置IPv4/IPv6 │ │ │
│ │ │ - MTU发现 │ │ │
│ │ │ - 多队列支持 │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 特点: │
│ 1. 基于 passt (Passive Automatic Sockets and Splicing Tool) │
│ 2. 支持自动检测宿主机网络配置 │
│ 3. 支持TCP/UDP端口转发 │
│ 4. 支持IPv4和IPv6双栈 │
│ 5. 自动处理ICMP(v4/v6) │
│ │
│ 使用方法: │
│ # Podman自动使用pasta(如果安装) │
│ podman run --network=pasta alpine │
│ │
│ # 手动配置端口转发 │
│ podman run --network=pasta:-T,8080:80,-U,53:53 alpine │
│ │
│ # 与slirp4netns性能对比 │
│ pasta优势: │
│ - 零拷贝减少内存复制 │
│ - 更高效的epoll事件处理 │
│ - 原生支持IPv6 │
│ │
└─────────────────────────────────────────────────────────────────┘
bypass4netns(高性能方案)
# bypass4netns通过绕过用户态网络栈实现接近原生性能
# 需要:
# 1. sysctl -w net.ipv4.ip_unprivileged_port_start=0
# 2. 特殊的网络配置
# 安装
go install github.com/rootless-containers/bypass4netns@latest
# 使用bypass4netns
bypass4netnsd &
podman run --network=bypass4netns alpine
# 原理:
# bypass4netns检测到本地连接时,直接通过socket传递,
# 跳过用户态网络栈,实现接近原生性能
Rootless模式下的存储驱动选择
┌─────────────────────────────────────────────────────────────────┐
│ Rootless 存储驱动对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 驱动 需要权限 性能 功能完整性 推荐度 │
│ ──────── ──────── ──── ──────── ────── │
│ overlayfs 无 高 完整 ★★★★★ │
│ fuse-overlayfs 无 中 完整 ★★★★☆ │
│ vfs 无 低 基础 ★★☆☆☆ │
│ devmapper 需要root 高 完整 N/A │
│ btrfs 无* 高 完整 ★★★★☆ │
│ zfs 无* 高 完整 ★★★★☆ │
│ │
│ *btrfs/zfs需要宿主机内核支持相应文件系统 │
│ │
│ 推荐选择流程: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. 检查内核支持: │ │
│ │ cat /proc/filesystems | grep overlay │ │
│ │ → 有: 使用 overlayfs │ │
│ │ → 无: 继续检查 │ │
│ │ │ │
│ │ 2. 检查fuse-overlayfs: │ │
│ │ which fuse-overlayfs │ │
│ │ → 有: 使用 fuse-overlayfs │ │
│ │ → 无: 使用 vfs (性能最差) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
fuse-overlayfs详解
┌─────────────────────────────────────────────────────────────────┐
│ fuse-overlayfs 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 容器进程 │
│ │ │
│ │ 文件系统操作 (read/write/open) │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ FUSE (Filesystem in Userspace) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ fuse-overlayfs 进程 │ │ │
│ │ │ │ │ │
│ │ │ overlay操作(用户态实现): │ │ │
│ │ │ - lowerdir: 只读镜像层 │ │ │
│ │ │ - upperdir: 容器可写层 │ │ │
│ │ │ - workdir: 元数据目录 │ │ │
│ │ │ - merged: 挂载点 │ │ │
│ │ │ │ │ │
│ │ │ FUSE操作: │ │ │
│ │ │ - getattr: 获取文件属性 │ │ │
│ │ │ - lookup: 查找文件 │ │ │
│ │ │ - read/write: 读写操作 │ │ │
│ │ │ - copy-up: 写时复制 │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ /dev/fuse │ │
│ │ ▼ │ │
│ │ 宿主机文件系统 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 性能开销(相比原生overlayfs): │
│ - 文件读取: +10-20% 延迟 │
│ - 文件写入: +15-30% 延迟 │
│ - 元数据操作: +20-40% 延迟 │
│ │
│ 安装与配置: │
│ # 安装fuse-overlayfs │
│ apt install fuse-overlayfs │
│ # 或 │
│ dnf install fuse-overlayfs │
│ │
│ # Podman配置 │
│ cat > ~/.config/containers/storage.conf <<EOF │
│ [storage] │
│ driver = "overlay" │
│ runroot = "/run/user/$(id -u)/containers" │
│ graphroot = "$HOME/.local/share/containers/storage" │
│ [storage.options.overlay] │
│ mount_program = "/usr/bin/fuse-overlayfs" │
│ mountopt = "nodev,fsync=0" │
│ EOF │
│ │
└─────────────────────────────────────────────────────────────────┘
vfs存储驱动(备选方案)
# vfs驱动: 直接使用目录树,无overlay
# 缺点:
# - 无写时复制,每次创建容器都完整复制镜像
# - 占用大量磁盘空间
# - 性能差
# 使用场景:
# - 内核不支持overlayfs
# - 无法安装fuse-overlayfs
# - 测试环境
# 配置
podman --storage-driver=vfs run alpine
Kubernetes Rootless模式生产部署方案
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Rootless 部署架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方案一: Usernetes (用户态Kubernetes) │
│ ───────────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 用户空间 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ kube- │ │ kube- │ │ kube- │ │ │
│ │ │ apiserver│ │ scheduler│ │ let │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ etcd │ │ coredns │ │ CNI │ │ │
│ │ │ │ │ │ │(pasta) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 方案二: Rootless Kind (测试环境推荐) │
│ ───────────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ kind create cluster --rootless │ │
│ │ │ │
│ │ 支持功能: │ │
│ │ - 多节点集群 │ │
│ │ - 端口映射 │ │
│ │ - 卷挂载 │ │
│ │ - LoadBalancer (需要cloud-provider) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 方案三: K3s Rootless (轻量生产推荐) │
│ ───────────────────────────────────────────── │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ curl -sfL https://get.k3s.io | sh -s - --rootless │ │
│ │ │ │
│ │ 限制: │ │
│ │ - 无NodePort Service │ │
│ │ - 无LoadBalancer(需额外组件) │ │
│ │ - Ingress需要特殊配置 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
K3s Rootless生产部署配置
# 1. 系统准备
# 启用cgroup v2 delegation
sudo systemctl edit --full user@1000.service
# 添加:
[Service]
Delegate=yes
# 或在/etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
# 2. 配置sysctl参数
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0
# 持久化
echo "net.ipv4.ip_unprivileged_port_start=0" | \
sudo tee /etc/sysctl.d/rootless.conf
# 3. 安装K3s rootless
curl -sfL https://get.k3s.io | sh -s - --rootless \
--write-kubeconfig-mode=644 \
--kube-apiserver-arg="--enable-admission-plugins=NodeRestriction"
# 4. 配置网络
# 使用pasta作为CNI
export K3S_ROOTLESS_API_PORT=6444
cat > /etc/rancher/k3s/config.yaml <<EOF
write-kubeconfig-mode: "0644"
cni: "pasta"
disable:
- traefik
EOF
# 5. 验证
KUBECONFIG=~/.kube/config kubectl get nodes
Pod Security Standards for Rootless
# rootless-pod-security.yaml
apiVersion: v1
kind: Namespace
metadata:
name: rootless-apps
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
---
apiVersion: v1
kind: Pod
metadata:
name: rootless-app
namespace: rootless-apps
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:alpine
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
Rootless vs 运行时特权模式安全对比
┌─────────────────────────────────────────────────────────────────┐
│ Rootless vs 特权容器 安全深度对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 攻击面维度 Rootless 特权容器 风险差异 │
│ ──────────── ──────── ──────── ──────── │
│ 宿主机UID 普通用户 root 高危 │
│ 能力集 最小子集 全部(CAP_ALL) 高危 │
│ 系统调用 受限(seccomp) 不受限 中危 │
│ 设备访问 仅基本设备 所有设备 高危 │
│ namespace逃逸 影响有限 可控宿主机 高危 │
│ 文件系统 用户目录 整个根文件系统 高危 │
│ 网络攻击 受限NAT 原始网络栈 高危 │
│ 内核漏洞利用 权限不足 可直接利用 高危 │
│ │
│ 攻击场景分析: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 场景: CVE-2019-5736 (runc容器逃逸) │ │
│ │ │ │
│ │ Rootless容器: │ │
│ │ - 攻击者UID为100000+ │ │
│ │ - 无法覆盖宿主机runc二进制 │ │
│ │ - 逃逸后仍为普通用户权限 │ │
│ │ - 影响范围: 仅用户目录 │ │
│ │ │ │
│ │ 特权容器: │ │
│ │ - 攻击者UID为0 │ │
│ │ - 可以覆盖runc二进制 │ │
│ │ - 获得宿主机root权限 │ │
│ │ - 影响范围: 整个宿主机 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 安全评分(CIS Docker Benchmark): │
│ Rootless: 95/100 (推荐) │
│ 特权容器: 15/100 (强烈不推荐) │
│ │
└─────────────────────────────────────────────────────────────────┘
安全配置对比
# Rootless安全Pod配置
apiVersion: v1
kind: Pod
metadata:
name: rootless-secure
spec:
securityContext:
runAsNonRoot: true # 必须
runAsUser: 1000 # 非root用户
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
containers:
- name: app
image: app:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# 仅添加必需能力
add:
- NET_BIND_SERVICE # 仅绑定端口需要
---
# 特权容器配置(仅在绝对必要时使用)
apiVersion: v1
kind: Pod
metadata:
name: privileged-only-when-necessary
annotations:
security.kubernetes.io/audit: "privileged-container-used"
spec:
securityContext:
runAsNonRoot: false
containers:
- name: app
image: app:latest
securityContext:
privileged: true # 危险!
# 特权容器隐含以下权限:
# - 所有能力(CAP_ALL)
# - 访问所有设备
# - 禁用seccomp
# - 禁用AppArmor
# - 可修改内核参数
# - 可挂载任意文件系统
三、OCI规范深度解析
3.1 OCI三大规范
┌─────────────────────────────────────────────────────────────────┐
│ OCI规范体系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Image Spec │ │ Runtime Spec│ │Distribution │ │
│ │ 镜像规范 │ │ 运行时规范 │ │ 分发规范 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 镜像格式 │ │ 容器生命周期│ │ 镜像推送/拉取│ │
│ │ 层/清单/索引│ │ 配置/根文件 │ │ 仓库API │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.2 OCI镜像规范(Image Spec)
镜像结构
┌─────────────────────────────────────────────────────────────────┐
│ OCI镜像结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Image Index (多架构清单) │
│ ├── mediaType: application/vnd.oci.image.index.v1+json │
│ ├── manifests[] │
│ │ ├── platform: linux/amd64 → Manifest A │
│ │ ├── platform: linux/arm64 → Manifest B │
│ │ └── platform: linux/s390x → Manifest C │
│ │ │
│ Image Manifest (单架构清单) │
│ ├── mediaType: application/vnd.oci.image.manifest.v1+json │
│ ├── config → Config Blob │
│ ├── layers[] → Layer Blobs │
│ │ ├── layer 0 (基础层) │
│ │ ├── layer 1 (依赖层) │
│ │ └── layer 2 (应用层) │
│ │
│ Image Config (配置) │
│ ├── architecture: amd64 │
│ ├── os: linux │
│ ├── rootfs.diff_ids[] │
│ ├── config.Cmd[] │
│ └── config.Env[] │
│ │
└─────────────────────────────────────────────────────────────────┘
镜像层存储结构
/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/
├── a1b2c3... # Layer 0 (基础层tar+gzip)
├── d4e5f6... # Layer 1 (依赖层tar+gzip)
├── g7h8i9... # Layer 2 (应用层tar+gzip)
└── j0k1l2... # Config JSON
3.3 OCI运行时规范(Runtime Spec)
容器生命周期状态机
┌──────────┐ create ┌──────────┐ start ┌──────────┐
│ Creating │────────────►│ Created │────────────►│ Running │
└──────────┘ └──────────┘ └──────────┘
│ │
│ delete │ stop
▼ ▼
┌──────────┐ ┌──────────┐
│ (deleted)│ │ Stopped │
└──────────┘ └──────────┘
│
│ delete
▼
┌──────────┐
│ (deleted)│
└──────────┘
运行时配置结构(OCI Spec JSON)
{
"ociVersion": "1.0.2",
"process": {
"terminal": true,
"user": {"uid": 0, "gid": 0},
"args": ["/bin/sh"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
"cwd": "/",
"capabilities": {
"bounding": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"],
"effective": ["CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE"]
}
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "container",
"mounts": [
{"destination": "/proc", "type": "proc", "source": "proc"},
{"destination": "/dev", "type": "tmpfs", "source": "tmpfs"},
{"destination": "/sys", "type": "sysfs", "source": "sysfs"}
],
"linux": {
"namespaces": [
{"type": "pid"},
{"type": "network"},
{"type": "ipc"},
{"type": "uts"},
{"type": "mount"}
],
"seccomp": {
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [{"names": ["read","write"], "action": "SCMP_ACT_ALLOW"}]
}
}
}
3.4 OCI分发规范(Distribution Spec)
仓库API核心端点
┌─────────────────────────────────────────────────────────────────┐
│ OCI Distribution API │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PUSH流程: │
│ 1. POST /v2/<name>/blobs/uploads/ → 获取upload URL │
│ 2. PUT /v2/<name>/blobs/uploads/<uuid> → 上传层 │
│ 3. PUT /v2/<name>/manifests/<ref> → 上传清单 │
│ │
│ PULL流程: │
│ 1. GET /v2/<name>/manifests/<ref> → 获取清单 │
│ 2. GET /v2/<name>/blobs/<digest> → 下载层 │
│ │
│ 其他: │
│ - GET /v2/_catalog → 仓库列表 │
│ - GET /v2/<name>/tags/list → 标签列表 │
│ - DELETE /v2/<name>/manifests/<digest> → 删除清单 │
│ │
└─────────────────────────────────────────────────────────────────┘
四、镜像分发原理
4.1 镜像拉取流程
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Kubelet │ │containerd│ │ Registry│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ CRI PullImage │ │
│───────────────────►│ │
│ │ │
│ │ GET /manifests/tag │
│ │───────────────────►│
│ │ │
│ │ Manifest JSON │
│ │◄───────────────────│
│ │ │
│ │ GET /blobs/digest │
│ │ (逐层下载) │
│ │───────────────────►│
│ │ │
│ │ Layer Data │
│ │◄───────────────────│
│ │ │
│ │ 解压+组装rootfs │
│ │ │
│ PullImage Response │ │
│◄───────────────────│ │
│ │ │
4.2 镜像优化策略
多阶段构建
# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .
# 运行阶段
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]
镜像层缓存优化
# 差: 每次代码变更都重新安装依赖
COPY . /app
RUN npm install
# 好: 依赖层可缓存
COPY package.json package-lock.json /app/
RUN npm install
COPY . /app
镜像大小对比
| 基础镜像 | 大小 | 安全性 | 适用场景 |
|---|---|---|---|
| ubuntu:22.04 | ~77MB | 中 | 通用开发 |
| debian:slim | ~74MB | 中 | 通用生产 |
| alpine:3.18 | ~3MB | 中( musl) | 精简场景 |
| distroless | ~2MB | 高 | 安全生产 |
| scratch | 0MB | 最高 | 静态编译 |
4.3 镜像安全扫描
# Trivy扫描
trivy image nginx:latest
# Grype扫描
grype nginx:latest
# Syft生成SBOM
syft nginx:latest -o spdx-json > sbom.json
# Cosign签名验证
cosign verify --key cosign.pub nginx:latest
五、containerd内部架构
5.1 containerd架构总览
┌─────────────────────────────────────────────────────────────────┐
│ containerd架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ gRPC API │ │
│ │ (containerd daemon对外暴露的API) │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────┼────────────────────────────────┐ │
│ │ containerd核心服务 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Metadata │ │ Content │ │ Diff │ │ Images │ │ │
│ │ │ Store │ │ Store │ │ Service │ │ Service │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Containers│ │ Tasks │ │ Events │ │ Leases │ │ │
│ │ │ Service │ │ Service │ │ Service │ │ Service │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────┼────────────────────────────────┐ │
│ │ Backend │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ Snapshotter │ │ Runtime │ │ │
│ │ │ (overlayfs/ │ │ (runc/kata/ │ │ │
│ │ │ devmapper/ │ │ wasm) │ │ │
│ │ │ native) │ │ │ │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2 核心子系统详解
Content Store(内容存储)
负责存储镜像的所有内容(blob)
/var/lib/containerd/io.containerd.content.v1.content/
├── blobs/
│ └── sha256/
│ ├── a1b2c3... # 镜像层
│ └── d4e5f6... # 配置JSON
└── ingest/ # 正在上传的内容
Snapshotter(快照管理器)
┌─────────────────────────────────────────────────────────────────┐
│ Snapshotter工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 镜像层(只读) 容器层(读写) │
│ │
│ Layer 3 ◄──────┐ │
│ ▲ │ │
│ Layer 2 ◄──┐ │ ┌──────────────────┐ │
│ ▲ │ │ │ Upperdir (可写) │ ◄── 容器修改 │
│ Layer 1 ◄──┤ └────┤ │ │
│ │ │ Lowerdir (只读) │ ◄── 镜像层叠加 │
│ Base Layer─┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ overlayfs mount │
│ │
│ Snapshot类型: │
│ - Committed: 只读快照(镜像层) │
│ - Active: 可写快照(容器层) │
│ - View: 只读视图(用于diff) │
│ │
└─────────────────────────────────────────────────────────────────┘
Snapshotter类型对比
| Snapshotter | 文件系统 | 特点 | 适用场景 |
|---|---|---|---|
| overlayfs | overlayfs | 默认、高性能 | 通用 |
| devmapper | devicemapper | 块设备 | 生产稳定 |
| native | bind-mount | 简单 | 测试 |
| zfs | zfs | 快照/校验 | ZFS存储 |
| stargz | overlayfs+远程 | 懒加载 | 远程镜像 |
| nydus | RAFS | 按需加载 | 大镜像加速 |
5.3 CRI插件架构
┌─────────────────────────────────────────────────────────────────┐
│ CRI Plugin架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Kubelet │
│ │ │
│ │ CRI gRPC │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ CRI Service │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ImageService│ │RuntimeSvc │ │ Event │ │ │
│ │ │ PullImage │ │CreateContainer│ │ Monitor │ │ │
│ │ │ ListImages │ │StartContainer│ │ │ │ │
│ │ │ RemoveImage│ │ExecSync │ │ │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ containerd│ │ runc │ │
│ │ Services │ │ (shim) │ │
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.4 容器创建完整流程
Kubelet → CRI Plugin → containerd → shim → runc
详细步骤:
1. Kubelet调用CRI RuntimeService.CreateContainer
2. CRI插件调用containerd.CreateContainer
3. containerd创建container元数据
4. Kubelet调用CRI RuntimeService.StartContainer
5. CRI插件调用containerd.Tasks.Create
6. containerd启动containerd-shim-runc-v2
7. shim调用runc create
8. runc创建namespace、rootfs
9. shim调用runc start
10. 容器进程启动
11. shim监控容器进程
12. 事件回传至containerd→Kubelet
5.5 containerd-shim详解
┌─────────────────────────────────────────────────────────────────┐
│ containerd-shim作用 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │containerd │ │ shim │ │ runc │ │
│ │ (daemon) │ │ (per-container)│ │ │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ gRPC/TTY │ fork/exec │ │
│ │◄──────────────►│────────────────►│ │
│ │ │ │ │
│ │
│ Shim职责: │
│ 1. 解耦containerd与runc(容器退出不依赖daemon) │
│ 2. 管理容器stdin/stdout/stderr │
│ 3. 报告容器退出状态 │
│ 4. 支持容器热重启 │
│ 5. 守护容器进程( reap子进程) │
│ │
│ v2改进: │
│ - 单shim管理多个容器(容器组) │
│ - 减少进程数 │
│ - 更好的资源效率 │
│ │
└─────────────────────────────────────────────────────────────────┘
六、容器运行时对比
6.1 运行时层级关系
高层运行时 (CRI实现)
├── containerd ←── Docker/Moby内部使用
├── CRI-O ←── OpenShift/Kubernetes专用
└── Docker Engine(含containerd)
低层运行时 (OCI实现)
├── runc ←── 默认OCI运行时
├── crun ←── C语言实现,更快
├── kata-runtime←── VM隔离
├── gVisor(runsc)←── 用户态内核
└── wasmtime ←── Wasm运行时
6.2 安全容器运行时对比
| 运行时 | 隔离方式 | 性能开销 | 安全等级 | 适用场景 |
|---|---|---|---|---|
| runc | namespace+cgroup | 最低 | 中 | 通用 |
| kata | 硬件虚拟化 | 高(20-30%) | 高 | 多租户/不可信负载 |
| gVisor | 用户态内核 | 中(5-15%) | 高 | 沙箱/不可信代码 |
| Wasm | 沙箱隔离 | 低 | 高 | 轻量/插件 |
参考资料:
更多推荐
所有评论(0)