前言
在上一篇文章中,我们解析了 OCI 规范中容器的顶层架构 Spec 和进程环境 Process。然而,真正让 Linux 容器具备隔离魔法的,是封装在 Spec 中的 Linux 字段。
这个 Linux 结构体可以说是整个 config.go 中最核心、最具技术深度的部分,它将 Linux 内核提供的底层特性(Namespaces, Cgroups, Seccomp, Sysctls 等)完美地映射为了结构化的 JSON 数据。本文将重点拆解这一部分,看看容器是如何与 Linux 内核特性对接的。
核心结构:Linux
在 vendor/github.com/opencontainers/runtime-spec/specs-go/config.go 中,Linux 结构体的定义如下(为了便于理解,对其进行了分类和略微精简):
type Linux struct {
// --- 1. 命名空间与身份映射 ---
Namespaces []LinuxNamespace `json:"namespaces,omitempty"`
UIDMappings []LinuxIDMapping `json:"uidMappings,omitempty"`
GIDMappings []LinuxIDMapping `json:"gidMappings,omitempty"`
TimeOffsets map[string]LinuxTimeOffset `json:"timeOffsets,omitempty"`
// --- 2. 资源控制 ---
CgroupsPath string `json:"cgroupsPath,omitempty"`
Resources *LinuxResources `json:"resources,omitempty"`
IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"`
MemoryPolicy *LinuxMemoryPolicy `json:"memoryPolicy,omitempty"`
// --- 3. 安全与系统调用 ---
Seccomp *LinuxSeccomp `json:"seccomp,omitempty"`
Personality *LinuxPersonality `json:"personality,omitempty"`
// --- 4. 文件系统与设备 ---
RootfsPropagation string `json:"rootfsPropagation,omitempty"`
MaskedPaths []string `json:"maskedPaths,omitempty"`
ReadonlyPaths []string `json:"readonlyPaths,omitempty"`
Devices []LinuxDevice `json:"devices,omitempty"`
NetDevices map[string]LinuxNetDevice `json:"netDevices,omitempty"`
// --- 5. 内核参数调优 ---
Sysctl map[string]string `json:"sysctl,omitempty"`
}接下来,我们按分类对其中的关键字段进行深度剖析。
1. 命名空间与身份映射
这是容器实现“视图隔离”的根基。
Namespaces
type LinuxNamespace struct {
Type LinuxNamespaceType `json:"type"`
Path string `json:"path,omitempty"`
}Namespaces 是一个数组,定义了容器需要创建或加入的命名空间(如 pid, network, mount, uts, ipc, cgroup)。 特别注意其中的 Path 字段。如果不填,runc 会调用 unshare 创建一个全新的 Namespace;如果填了一个具体的路径(例如 /proc/1234/ns/net),runc 会调用 setns 让容器加入到一个已经存在的 Namespace 中。这就是 Kubernetes Pod 中所有容器能共享同一个网络栈的底层实现原理!
UIDMappings 与 GIDMappings
type LinuxIDMapping struct {
ContainerID uint32 `json:"containerID"`
HostID uint32 `json:"hostID"`
Size uint32 `json:"size"`
}只有在启用了 User Namespace 的情况下,这部分才有效。它定义了容器内外用户 ID 的映射关系。例如:ContainerID=0, HostID=100000, Size=65536 表示将容器内的 root (0) 映射为宿主机的普通用户 (100000),从而彻底消除容器逃逸带来提权风险(Rootless 容器的核心)。
TimeOffsets (较新的特性)
支持 Time Namespace,允许容器内部拥有与宿主机不同的系统时间(例如,为了模拟某个过去的时间点来测试应用),而不会影响宿主机的真实时钟。
2. 内核参数调优 (Sysctl)
Sysctl map[string]string `json:"sysctl,omitempty"`这是一个键值对字典,对应 Linux 的 /proc/sys 下的内核参数。 在容器化环境中,某些内核参数(特别是网络栈相关的,如 net.ipv4.ip_forward 或 net.core.somaxconn)是属于 Network Namespace 的,这意味着在容器内部修改它们是安全的,不会影响宿主机。通过这个字段,用户可以为每个容器独立调优内核参数,而无需在应用启动脚本中执行 sysctl -w。
3. 安全配置:文件系统掩蔽 (MaskedPaths / ReadonlyPaths)
在默认情况下,由于容器共享宿主机内核,宿主机的 /proc 和 /sys 目录会向进程泄露大量底层硬件、内核态信息甚至密钥。
MaskedPaths []string:被指定的路径在容器内会被“掩蔽”。runc 的做法通常是在这些路径上挂载一个空的只读文件或目录(例如/dev/null),让容器内的进程无法读取其实际内容。常见的掩蔽路径包括/proc/kcore(内核物理内存镜像)、/proc/timer_list等。ReadonlyPaths []string:被指定的路径会被重新以只读 (ro) 模式挂载(Bind Mount),防止容器内进程修改系统级别配置。典型例子是/proc/sys和/proc/sysrq-trigger。
这两个数组在上一篇文章中我们通过 runc spec 生成的默认 config.json 里已经见识过了,是容器防御内核态攻击的重要防线。
4. 平台执行域 (Personality)
type LinuxPersonality struct {
Domain LinuxPersonalityDomain `json:"domain"`
Flags []string `json:"flags,omitempty"`
}这是一个非常底层的特性,对应 Linux 的 personality(2) 系统调用。它允许进程改变其“执行域”。最常见的用例是:在 64 位 (x86_64) 的内核上,为容器提供一个 32 位 (i386) 的运行环境视图,这对于兼容一些老旧的静态编译程序非常有用。
结语
在 config.go 的 Linux 结构体中,我们看到了 OCI 规范是如何细致入微地管控一个容器的“内核视野”的。从网络栈的加入方式,到内核参数的独立配置,再到敏感内核文件系统的掩蔽,每一项配置都对应着一个经典的 Linux 系统编程概念。
细心的读者可能注意到了,我们故意跳过了 Linux 结构体中极其庞大的 Resources(Cgroups 资源组)和 Seccomp(系统调用过滤)。由于这两部分包含的数据结构过于复杂且重要,我们将在下一篇(下篇)中专门对它们进行深度拆解!