Read OSS

架构与导航指南:apple/container 的项目结构解析

中级

前置知识

  • 了解容器基本概念(镜像、命名空间等)
  • 具备基本的 Swift 代码阅读能力
  • 在概念层面熟悉 macOS 进程与进程间通信

架构与导航指南:apple/container 的项目结构解析

macOS 上的大多数容器运行时,都是将所有容器运行在同一个共享的 Linux VM 中。apple/container 采用了截然不同的设计思路:每个容器都拥有独立的轻量级虚拟机。这不只是一个实现细节,而是影响了整个代码库中每一个架构决策——从进程模型,到网络栈,再到 stdio 文件描述符在进程间的传递方式。要真正理解这个项目的运作原理,首先需要理解它为什么要这样设计。

本文是一张导航地图。读完之后,你将清楚地知道在代码库的哪里能找到任何功能模块,也将理解从终端中输入的 CLI 命令,到 VM 内部 Linux 内核启动之间,各个进程边界是如何串联起来的。

apple/container 究竟做什么

container 工具让你能够在 Apple Silicon Mac 上构建、运行和管理兼容 OCI 标准的 Linux 容器。它使用标准 OCI 镜像——与 Docker 或 Podman 所用的镜像完全相同——因此不存在厂商锁定。其核心差异在于隔离模型。

传统的 macOS 容器运行时会启动一个 Linux VM(通常由 Lima 或 Docker Desktop 后端管理),然后在这个共享 VM 内以 Linux 进程的形式运行容器。apple/container 则不同——它通过 Apple 的 Virtualization.framework 为每个容器创建专属的 VM。项目的技术概述文档对此有清晰的说明:每个容器获得完整的 VM 级隔离,只挂载该容器实际需要的数据(而非共享 VM 可能需要的所有内容),同时启动时间与共享 VM 方案相当。

VM 的创建与管理工作由配套的 apple/containerization 库负责。container 工具本质上是构建在该库之上的用户侧应用层,负责处理 CLI 交互、进程编排、镜像管理、网络配置和数据持久化。

提示: containerization 库在 Package.swift 中作为依赖项出现在几乎每一个 target 中。当你看到 VZVirtualMachineManagerContentStorePlatform 等类型时,它们来自那个库,而非本仓库。

四层架构

apple/container 并非单一进程,而是由五个独立的可执行文件协同组成,在逻辑上分为四个层次:

flowchart TD
    CLI["container CLI<br/>(user-facing)"]
    API["container-apiserver<br/>(central coordinator)"]
    RT["container-runtime-linux<br/>(one per container)"]
    NET["container-network-vmnet<br/>(one per network)"]
    IMG["container-core-images<br/>(singleton)"]
    VF["Virtualization.framework"]
    VM["vmnet.framework"]
    XPC_FW["XPC / launchd"]

    CLI -->|XPC| API
    API -->|XPC| RT
    API -->|XPC| NET
    API -->|XPC| IMG
    RT --> VF
    NET --> VM
    API --> XPC_FW

    style CLI fill:#4A90D9,color:#fff
    style API fill:#D94A4A,color:#fff
    style RT fill:#7B68EE,color:#fff
    style NET fill:#2ECC71,color:#fff
    style IMG fill:#F39C12,color:#fff

第一层:CLI。 container 二进制文件是用户直接使用的入口。它负责解析参数、构造 XPC 消息并发送给 API server,自身几乎不包含任何业务逻辑。

第二层:API Server。 container-apiserver 是一个常驻的 launch agent,也是整个系统的核心协调者——负责管理容器状态、编排网络分配、向 launchd 注册运行时插件,以及运行两个 DNS server。所有与容器、网络、卷和插件相关的 XPC 路由均在此注册。

第三层:Helper 守护进程。 三个 helper 进程各自负责特定类型的资源:

  • container-runtime-linux——每个运行中的容器对应一个实例,管理 VM 生命周期
  • container-network-vmnet——每个虚拟网络对应一个实例,通过 vmnet.framework 管理 IP 分配
  • container-core-images——单例进程,管理 OCI 镜像存储和镜像仓库交互

第四层:macOS 系统框架。 Virtualization.framework、vmnet.framework、XPC 和 launchd 提供底层 OS 原语支持。

APIServer+Start.swift 中 API server 的启动流程展示了这些组件是如何整合在一起的。run() 方法依次初始化插件加载器、容器服务、网络服务、健康检查处理器和两个 DNS server,最后通过 TaskGroup 并发启动所有组件。

Swift Package 结构与 Target 依赖图

Package.swift 中定义了 5 个可执行 target 和约 20 个库 target。可执行 target 与上文描述的进程一一对应:

可执行 Target 二进制名称 路径
container container Sources/CLI
container-apiserver container-apiserver Sources/Helpers/APIServer
container-runtime-linux container-runtime-linux Sources/Helpers/RuntimeLinux
container-network-vmnet container-network-vmnet Sources/Helpers/NetworkVmnet
container-core-images container-core-images Sources/Helpers/Images

库 target 遵循客户端/服务端分离的设计模式。每个服务都有独立的 target,分别对应服务端逻辑和客户端 XPC 封装:

graph LR
    subgraph "ContainerAPIService"
        APIS["Server<br/>ContainerAPIService"]
        APIC["Client<br/>ContainerAPIClient"]
    end
    subgraph "ContainerSandboxService"
        SS["Server<br/>ContainerSandboxService"]
        SC["Client<br/>ContainerSandboxServiceClient"]
    end
    subgraph "ContainerNetworkService"
        NS["Server<br/>ContainerNetworkService"]
        NC["Client<br/>ContainerNetworkServiceClient"]
    end
    subgraph "ContainerImagesService"
        IS["Server<br/>ContainerImagesService"]
        IC["Client<br/>ContainerImagesServiceClient"]
    end

    APIC --> SC
    APIC --> IC
    APIS --> NC
    APIS --> SC

注意,API server 的服务端 target 依赖了网络和沙箱服务的客户端 target——因为 API server 在与 helper 守护进程通信时,自身扮演的是客户端角色。这是理解整个架构的关键:每一个进程边界都被建模为一对客户端/服务端。

共享基础库(ContainerXPCContainerPluginContainerPersistenceContainerResource)几乎被所有 target 使用。其中 ContainerXPC 尤为关键——它提供了贯穿所有进程边界的消息格式与传输机制。

目录结构速查

以下是源码目录的导航速查表:

目录 职责说明
Sources/CLI/ 精简的 @main 入口,直接委托给 Application
Sources/ContainerCommands/ 所有 CLI 命令定义(run、build、exec 等)
Sources/ContainerBuild/ 基于 gRPC 的 container build 构建系统
Sources/ContainerResource/ 共享数据类型:ContainerConfigurationNetworkConfiguration
Sources/ContainerPersistence/ 基于 JSON 文件的实体存储,配合内存索引
Sources/ContainerPlugin/ 插件发现、配置 schema、launchd 注册
Sources/ContainerXPC/ XPC 消息类型、server 和 client
Sources/Services/ API、Sandbox、Network、Images 服务的客户端/服务端实现
Sources/Helpers/ 四个 helper 可执行文件的入口点
Sources/DNSServer/ 基于 SwiftNIO 构建的自定义 UDP DNS server
Sources/TerminalProgress/ 终端进度条渲染
Sources/ContainerOS/ OS 级工具函数
config/ 内置运行时和网络 helper 的插件 config.json 文件

CLI 入口文件 ContainerCLI.swift 极为精简,只有 19 行代码,完全委托给 Application。完整的命令树定义在 Application.swift 中,使用 Swift Argument Parser 的 groupedSubcommands 将命令按 Container、Image、Volume 和 Other 分组,并以 DefaultCommand 作为隐藏的默认子命令来处理插件分发逻辑。

XPC:进程通信的骨干

apple/container 中所有的进程边界都使用 XPC——Apple 基于 Mach 消息机制的原生进程间通信框架。各 macOS 侧进程之间不使用 socket、共享内存或 HTTP 进行通信。(gRPC 确实有使用,但仅用于与 VM 内部 Linux 进程的通信——详见第 6 篇文章。)

为什么选择 XPC?有三个原因:

  1. 权限隔离。 XPC 连接携带 audit token,用于标识调用方进程。服务端验证客户端是否具有相同的有效用户 ID,从而防止跨用户访问。
  2. launchd 集成。 XPC 服务以 Mach 服务的形式注册到 launchd,由其负责按需启动进程和管理生命周期。
  3. 文件描述符传递。 XPC 支持跨进程传递文件描述符——这对于将 stdio 管道从 CLI 经由 API server 传递到容器运行时至关重要。

项目在底层 xpc_object_t C API 之上构建了一套自定义抽象层。XPCServer 提供基于路由的消息分发机制,XPCClient 将回调式的发送 API 封装为 Swift async/await 风格,XPCMessage 则在底层字典结构之上提供类型安全的访问器。下一篇文章将深入探讨这些内容。

sequenceDiagram
    participant CLI as container CLI
    participant API as container-apiserver
    participant RT as container-runtime-linux

    CLI->>API: XPC: containerCreate
    API->>API: Persist ContainerSnapshot
    API->>API: Register runtime with launchd
    CLI->>API: XPC: containerBootstrap
    API->>RT: XPC: createEndpoint
    RT-->>API: XPC endpoint
    API->>RT: XPC: bootstrap (via endpoint)
    RT->>RT: Boot Linux VM
    RT-->>API: OK
    API-->>CLI: OK

提示: 阅读代码时,务必留意某个文件运行在哪个进程中。Sources/Services/ContainerAPIService/Server/ 下的代码运行在 container-apiserver 内;Sources/Services/ContainerAPIService/Client/ 下的代码则运行在需要与 API server 通信的进程中——通常是 CLI。混淆这两者是最容易造成困惑的地方。

下一步

有了这张全局地图,下一篇文章将深入 XPC 通信层——正是这套自定义抽象机制让所有进程间协调成为可能。我们将详细分析 XPCServer 如何按路由分发消息、XPCClient 如何通过可配置超时将 XPC 回调模型桥接为 async/await,以及 container-runtime-linux 采用的巧妙双服务端安全模式——这一设计有效缩小了其对外暴露的攻击面。