avatar🌌
TangentTangent Blog

技术博客 | Go & Kubernetes & Linux

技术博客 | Go & Kubernetes & Linux

Reading runc Source Code (Part 7): Core OCI Spec Data Structures (Part 1) — Base Config and Process

Introduction ​

In the preceding seven articles, we systematically read through runc/libcontainer/SPEC.md, grasping the theoretical foundations of the isolation, limitation, and security mechanisms relied upon by Linux containers.

Theory needs to be translated into engineering. In the Open Container Initiative (OCI) standard, the only communication protocol between the higher-level container engine (like Docker, containerd) and the low-level runtime (runc) is a configuration file named config.json.

In the runc source code, the Go language data structure corresponding to this JSON resides in a third-party dependency package: github.com/opencontainers/runtime-spec/specs-go/config.go. This file is over 1000 lines long. For clarity, we will deeply deconstruct it across several articles. This is the first piece, primarily focusing on the container’s top-level base structure (Spec) and the critical process environment (Process).

1. The Top-Level Structure: Spec ​

The Spec struct is the entry point for the entire config.json, outlining the complete picture of the container.

go
type Spec struct {
	// Version of the Open Container Initiative Runtime Specification with which the bundle complies.
	Version string `json:"ociVersion"`
	
	// Process configures the container process.
	Process *Process `json:"process,omitempty"`
	
	// Root configures the container's root filesystem.
	Root *Root `json:"root,omitempty"`
	
	// Hostname configures the container's hostname (corresponds to UTS Namespace).
	Hostname string `json:"hostname,omitempty"`
	
	// Domainname configures the container's domainname.
	Domainname string `json:"domainname,omitempty"`
	
	// Mounts configures additional mounts (on top of Root) (corresponds to Mount Namespace).
	Mounts []Mount `json:"mounts,omitempty"`
	
	// Hooks configures callbacks for container lifecycle events.
	Hooks *Hooks `json:"hooks,omitempty" platform:"linux,solaris,zos"`
	
	// Annotations contains arbitrary metadata for the container (key-value pairs).
	Annotations map[string]string `json:"annotations,omitempty"`

	// Linux is platform-specific configuration for Linux based containers.
	Linux *Linux `json:"linux,omitempty" platform:"linux"`
	// ... Windows, Solaris, VM specific configs omitted here
}

Analysis:

  • Cross-platform Design: You can see that Spec abstracts all common attributes of containers at the top level (process, root directory, mounts, hostname), while encapsulating underlying system-level features (like Cgroups, Namespaces, Seccomp) into platform-specific fields (like Linux, Windows).
  • Hooks (Lifecycle Callbacks): This is crucial. It allows external programs to intervene and execute custom logic before the container starts (PreStart), after creation (CreateRuntime), after startup (PostStart), and after stopping (PostStop). For example, allocating a virtual NIC for the host and pushing it into the container’s network namespace.

2. The Root Filesystem: Root ​

Root describes what the / (root directory) looks like from the perspective of the container process.

go
type Root struct {
	// Path is the absolute path to the container's root filesystem.
	Path string `json:"path"`
	
	// Readonly specifies if the root filesystem should be mounted as readonly.
	Readonly bool `json:"readonly,omitempty"`
}

Linking back to our SPEC knowledge, runc utilizes this Path, combined with the pivot_root system call, to "jail" the container process within this path. If Readonly = true is configured, it effectively prevents malicious processes from tampering with base system files.

3. The Core Execution Unit: Process ​

If Root and Linux define the stage and the walls, then Process is the actor dancing on the stage. It defines the complete context of the container’s main process.

go
type Process struct {
	// Terminal creates an interactive terminal for the container.
	Terminal bool `json:"terminal,omitempty"`
	
	// ConsoleSize specifies the size of the console.
	ConsoleSize *Box `json:"consoleSize,omitempty"`
	
	// User specifies user information for the process.
	User User `json:"user"`
	
	// Args specifies the binary and arguments for the application to execute.
	Args []string `json:"args,omitempty"`
	
	// Env populates the process environment for the container.
	Env []string `json:"env,omitempty"`
	
	// Cwd is the current working directory for the process and must be relative to the container's root.
	Cwd string `json:"cwd"`
	
	// Capabilities are linux capabilities that are kept for the container.
	Capabilities *LinuxCapabilities `json:"capabilities,omitempty" platform:"linux"`
	
	// Rlimits specifies rlimit options to apply to the process.
	Rlimits []POSIXRlimit `json:"rlimits,omitempty" platform:"linux,solaris"`
	
	// NoNewPrivileges controls whether additional privileges could be gained by processes in the container.
	NoNewPrivileges bool `json:"noNewPrivileges,omitempty" platform:"linux"`
	
	// ApparmorProfile specifies the apparmor profile for the container.
	ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"`
	// SelinuxLabel specifies the selinux context that the container process is run as.
	SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"`
	
	// OOMScoreAdj adjusts the oom-killer score.
	OOMScoreAdj *int `json:"oomScoreAdj,omitempty" platform:"linux"`
}

Deep Dive into Process Fields ​

  1. User (Identity)

    go
    type User struct {
        UID            uint32   `json:"uid" platform:"linux,solaris"`
        GID            uint32   `json:"gid" platform:"linux,solaris"`
        AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"`
        // ...
    }

    The UID and GID here are identifiers inside the container. If User Namespaces are enabled, there will be a mapping table in the underlying Linux config, mapping the internal UID (like 0/root) to a normal unprivileged UID on the host (like 100000). This is the security cornerstone of Rootless containers.

  2. Capabilities (Privilege Control)

    go
    type LinuxCapabilities struct {
        Bounding    []string `json:"bounding,omitempty"`
        Effective   []string `json:"effective,omitempty"`
        Inheritable []string `json:"inheritable,omitempty"`
        Permitted   []string `json:"permitted,omitempty"`
        Ambient     []string `json:"ambient,omitempty"`
    }

    Remember the default Capabilities list (like CAP_NET_BIND_SERVICE) we saw in the Security post? Linux Capabilities are divided into 5 sets:

    • Bounding: The upper limit; boundaries the process can never surpass.
    • Effective: The capabilities currently active and taking effect for the process.
    • Permitted: The capabilities the process is allowed to possess (and can activate into Effective at any time). These three sets precisely delineate the maximum blast radius should a hacker compromise the process.
  3. NoNewPrivileges This maps to the Linux prctl(PR_SET_NO_NEW_PRIVS) syscall. If set to true, even if a process inside the container executes a binary with the suid flag (like the sudo command), the kernel will refuse to grant it higher privileges. This is a mandatory security option in multi-tenant clusters.

  4. Rlimits (Process-Level Hard/Soft Limits) Do not confuse this with Cgroups. Cgroups restrict the CPU and memory of a group of processes, whereas Rlimits (via the setrlimit call) restricts system-level quotas for a single process, such as the famous RLIMIT_NOFILE (max open file descriptors) or RLIMIT_NPROC (max child processes).

Conclusion ​

Through the Spec and Process structs in config.go, we can clearly see how the OCI specification passes runtime instructions to runc via JSON data structures.

In the next article, we will delve into the most hardcore part of config.go—the Linux struct—and meticulously deconstruct the configuration definitions for Namespace isolation, Cgroups resource binding, and Seccomp syscall filtering.

runc 源码解读(六):从 SPEC.md 看容器运行时、Init 进程与生命周期 (Actions)
runc 源码解读(七):OCI Spec 核心数据结构解析 (上) —— 基础配置与 Process
由 Valaxy v1.2.2 驱动|主题-Yunv1.2.2