kubectl 可以安装在 Mac 上,但 Mac 不能因此成为受支持的 Kubernetes Worker。只要任务依赖 Xcode、Apple SDK 或 Simulator,你就应把 Kubernetes 放在队列与控制层,把真正的构建交给独立 Mac 池;生产签名节点还要与普通构建机分开。

本周建议动作:先选一个非生产 Xcode 工作流,验证任务创建、Mac Runner 接单、日志回传、制品上传、节点重启和工作区清理。验证闭环后,再决定固定 Mac 数量与峰值弹性容量。

01

谁应该采用这套架构

这篇文章适合已经运营 Kubernetes 平台,希望统一调度 Apple 平台构建任务的基础设施负责人。

如果你正在为多个 iOS 团队规划共享 Mac 构建池,或者需要在固定采购、按需租赁与混合资源之间做预算决策,也可以直接使用文中的验收条件。

如果团队完全没有 Apple 构建任务,只运行后端、代码扫描和通用测试,那么你不需要为 Kubernetes 额外引入 Mac。

02

先划清 Kubernetes 与 Mac 的边界

Kubernetes 的标准 Node 由控制面管理,并运行 kubelet、容器运行时和 kube-proxy 等节点组件。官方文档明确描述了 Linux 与 Windows 节点场景;Windows 节点也有独立的容器、网络和版本兼容要求。官方资料没有提供 macOS 原生 Worker Node 支持。详见 Kubernetes Node 组件与节点管理文档 和 Kubernetes Windows 节点支持说明。(kubernetes.io)

这里有三个经常被混淆的概念:

  • 控制面统一:你可以用 Kubernetes API 管理构建请求、队列和期望状态。
  • 任务调度统一:你可以用同一套策略决定任务进入 Linux Pod 还是 Mac Runner。
  • 执行环境统一:这并不意味着 Linux Pod、Mac 主机和生产签名节点拥有相同的运行时。

只有前两项适合统一。第三项必须按任务类型拆分。

Apple 的命令行工具文档说明,完整 Xcode 环境包含 xcodebuild、xcrun 等工具;仅安装 Command Line Tools 并不等于拥有完整 Xcode。Apple 还要求通过 xcode-select 选择实际使用的 Xcode 路径。(developer.apple.com)

因此,下面这种思路不能作为生产架构:

Kubernetes Pod
  └── 安装 Xcode
      └── 执行 xcodebuild

正确的边界应是:

Kubernetes 控制面
  ├── Linux Pod:检查、测试、预处理
  ├── Mac Runner:Xcode 构建、Simulator、归档
  └── 可信 Mac:签名、发布、凭证操作

⚠️ kubectl 是客户端工具。它可以在很多操作系统上运行,但“可以访问 Kubernetes”与“可以作为 Kubernetes Node 运行 Pod”是两件事。

03

第一类场景:代码检查与通用测试留在集群

凡是不依赖 Apple 工具链的任务,都应优先留在 Kubernetes 集群内。这包括代码扫描、通用脚本、后端测试、依赖检查、制品预处理,以及不需要 iOS SDK 的共享模块测试。

这样做有三个好处:

✅ Linux Pod 可以按项目负载快速扩展。
✅ 构建失败更容易通过容器镜像复现。
✅ Mac 资源不会被低价值任务长期占用。

你需要在两个环境之间定义清晰的交接契约。至少包含以下字段:

  • task_id:一次构建的唯一 ID;
  • commit_sha:对应的代码版本;
  • input_artifact:传给 Mac 的源码包或预处理制品;
  • required_xcode:需要的 Xcode 工具链标识;
  • test_destination:模拟器或设备测试目标;
  • output_artifact:归档包、测试报告或符号文件地址;
  • failure_state:失败原因、退出码和重试建议。

Kubernetes 负责创建和追踪请求,不负责假装自己已经执行了 Xcode。这样才能把“任务已排队”“Mac 已接单”“构建失败”和“制品已上传”区分开。

04

第二类场景:Xcode 构建必须路由到外部 Mac

xcodebuild 可以执行构建、测试、分析和归档操作。Apple 的命令行构建文档也明确提供了 build、test、build-for-testing 和归档相关流程。(developer.apple.com)

Simulator 同样属于 Mac 上的开发工具链。Apple 的自动化测试文档说明,xcodebuild 可以指定不同测试目标,包括 Simulator;如果构建过程返回非零退出码,CI 应将任务判定为失败。(developer.apple.com)

企业常见的连接方式有三种:

连接方式 适合场景 优点 主要风险
CI 平台原生 Mac Runner 已有成熟 Runner 管理能力 接入快,状态模型较完整 对复杂资源池和跨项目隔离控制有限
队列消费者 任务类型较少,平台团队希望快速落地 架构简单,便于先做 PoC 需要自行处理重试、超时和节点回收
Custom Controller / Operator 多项目、多 Mac 池、需要声明式管理 可把 Mac 资源纳入 Kubernetes API 开发与运维成本更高,需维护控制器

如果采用 Custom Resource,可以把 MacBuildRequest、MacRunner 或 MacPool 作为 Kubernetes API 中的业务对象。Kubernetes 官方文档说明,Custom Resource 可以扩展 API;单独的 Custom Resource 只负责存储结构化状态,真正的自动化需要配合 Controller。(kubernetes.io)

Operator 的价值也在这里:它不是把 Mac 变成 Node,而是监听构建请求,再调用外部 Mac 服务或 Runner 接口。Operator 模式本质上是用控制循环管理自定义资源。(kubernetes.io)

推荐的任务流

控制流:
Kubernetes API → Controller → Mac 池调度器

任务流:
源码检查完成 → 创建 MacBuildRequest → Mac Runner 接单

制品流:
源码或中间制品 → Mac 构建 → IPA、归档、测试报告 → 制品存储

凭证流:
任务授权 → 临时签名权限 → 可信 Mac 执行 → 凭证撤销或失效

不要只通过 SSH 远程执行一段脚本,然后根据 SSH 是否断开判断成功或失败。Apple 文档特别提醒,远程 SSH 环境运行依赖 macOS 图形会话的测试任务时,可能需要已经登录的 Aqua session;没有正确会话时,Simulator 或依赖 UI 框架的任务可能失败。(developer.apple.com)

05

第三类场景:普通构建池与生产签名节点分离

普通构建可以共享 Mac 池,但签名发布不能简单等同于普通构建。

生产环境中,以下内容应单独控制:

  • Developer ID 或 App Store 发布相关私钥;
  • Keychain 与签名证书;
  • App Store Connect 或发布 API 凭证;
  • 发布环境变量;
  • 生产制品上传权限;
  • 失败后的撤销、吊销或重新轮换流程。

建议采用三层路径:

  1. Kubernetes 集群完成前置验证。
    执行代码扫描、依赖检查、通用测试和制品完整性检查。

  2. 普通 Mac 池完成构建与回归。
    执行 xcodebuild、Simulator 测试、归档和非生产验证。

  3. 可信 Mac 完成签名与发布。
    仅接收已经通过前两层的制品,使用受控 Keychain 和最小权限凭证。

Apple 的签名文档显示,代码签名可以通过 Xcode 或命令行工具完成,签名身份与发布用途需要明确区分。(developer.apple.com)

这套分层的重点不是“多准备几台机器”,而是限制凭证流向。验收时要记录:

  • 哪个任务获得了签名授权;
  • 授权有效期和作用域;
  • 私钥是否离开可信 Mac;
  • 构建目录是否在任务结束后清理;
  • 失败后是否能够撤销或轮换凭证;
  • 谁可以查看签名日志和发布制品。
06

第四类场景:发布峰值与故障恢复使用弹性远程 Mac

固定 Mac 池适合基础负载稳定、Xcode 版本相对固定、排队时间可预测的团队。

弹性远程 Mac 更适合以下场景:

✅ 版本发布窗口出现短时任务峰值。
✅ 多个 iOS 项目需要共享构建资源。
✅ 新 Xcode 或新项目需要短周期验证。
✅ 固定节点故障时需要临时替补。
✅ 企业正在评估自购 Mac 与租赁 Mac 的长期比例。

容量不能按开发者人数直接推导。你至少要记录:

  • 排队任务数量;
  • 单个 Mac 在目标工作流中的有效并发能力;
  • Xcode 和 Simulator 环境交付时间;
  • 任务失败后的重试占用;
  • 发布窗口需要的冗余节点;
  • 节点重启、清理和重新注册时间。

换句话说,企业需要的是“有效构建产能”,不是一个看起来足够大的 Mac 数字。

第五步:把外部 Mac 接入生产队列前完成验收

建议按下面顺序落地,至少保留一条完整审计记录:

  1. 建立环境基线。
    固定 macOS、Xcode、SDK、Simulator Runtime、依赖管理器和 Runner 版本。不同版本必须用标签或资源池隔离。

  2. 定义任务协议。
    明确任务输入、状态枚举、日志位置、制品地址、超时策略和取消行为。

  3. 完成 Runner 注册。
    Runner 只能接收指定项目或资源池的任务。不要把所有 Mac 注册为无差别通用节点。

  4. 执行真实 Xcode 构建。
    不只运行 echo 或简单脚本。至少验证编译、单元测试、Simulator、归档和失败退出码。

  5. 验证状态回传。
    集群必须能看到 queued、assigned、running、succeeded、failed、cleaned 等状态变化。

  6. 测试重启恢复。
    在构建运行期间重启 Runner 或 Mac,确认 Controller 能识别失联、停止等待并重新调度。

  7. 测试节点回收。
    删除源码、派生数据、临时 Keychain、日志缓存和任务变量,再把节点放回共享池。

  8. 记录容量证据。
    用实际队列与构建记录决定固定容量和弹性容量,不用团队人数替代测量。

经验上,最容易被忽略的是“回收成功”。一台 Mac 能完成构建,不代表它适合进入共享生产池;如果旧项目缓存、临时证书或工作区残留,下一次任务就可能出现难以复现的污染。

07

用条件分支决定资源方案

你可以按下面的条件直接做初步决策:

  • 若团队没有 Xcode、Simulator 或 Apple SDK 任务,选 Kubernetes-only 架构,不准备 Mac Worker。
  • 若任务稳定、项目数量有限、发布峰值可预测,选 Kubernetes 加固定 Mac 池。
  • 若多个项目共用构建资源,且发布或回归存在明显峰值,选固定 Mac 池加弹性远程 Mac。
  • 若生产签名凭证需要独立审计,无论普通构建规模多小,都要单独准备可信签名 Mac。
  • 若任务状态、日志和制品无法回传,先回退到非生产 PoC,不要接入正式发布队列。
  • 若节点重启后无法恢复或清理不完整,不要通过增加 Mac 数量掩盖架构缺陷,应先修复 Runner 生命周期管理。

关于 Mac 节点成本,你可以先参考 Mac mini M4 租赁方案 与 Mac 租赁价格说明,但不要把页面价格直接当成企业 TCO。正式预算还要加入网络、存储、凭证管理、维护、故障替补与峰值容量成本。

08

常见决策问题

macOS 能不能直接加入 Kubernetes 集群当 Worker?

不能按受支持的原生 Worker Node 方式处理。官方节点文档描述的节点组件包括 kubelet、容器运行时和 kube-proxy,官方操作系统支持边界覆盖 Linux 与 Windows,并未提供 macOS Worker Node 支持。Mac 上能运行 kubectl,只代表它能访问集群,不代表它能运行 Kubernetes Pod。

怎样让 Kubernetes 调用外部 Mac 执行 Xcode 构建?

把 Kubernetes 放在队列和控制层,在集群内创建构建请求,再由 Controller、Operator 或队列消费者把请求交给已注册的 Mac Runner。Mac 负责执行 xcodebuild、模拟器测试和归档,并回传状态、日志、制品地址与节点健康结果,不要只用 SSH 启动一段无法追踪的脚本。

iOS CI 是否可以全部放在 Kubernetes 集群里?

不能把依赖 Xcode、Apple SDK 或 Simulator 的阶段当作普通 Pod 运行。代码扫描、通用测试、后端测试和制品预处理可以留在 Linux Pod;需要 Apple 工具链的编译、模拟器和归档阶段,应路由到安装了对应 Xcode 的真实 Mac 环境。

Kubernetes 和 Mac Runner 之间如何同步任务状态?

建议为每次构建建立唯一任务 ID,并定义 queued、assigned、running、succeeded、failed、cancelled、cleaned 等状态。Mac Runner 通过受控 API 或消息队列回传状态,日志与制品写入独立存储,Controller 负责超时、重试、节点失联和回收,避免依赖 SSH 会话是否仍然存在。

企业到底要准备多少台 Mac 才够 Kubernetes 调用?

不能根据开发者人数直接推导。应使用排队任务量、单节点有效产能、Xcode 环境交付时间、发布峰值和故障冗余共同计算。稳定基础负载适合固定 Mac 池;发布窗口、回归测试或灾备需求明显时,再增加经过验收的弹性远程 Mac。

如果你现在采用的是“Linux 集群加一台长期共享 Mac”的方案,真实缺点通常不是 Kubernetes 本身,而是任务路由不可追踪、Mac 环境容易被项目互相污染、签名凭证与普通构建混在一起,以及发布高峰没有可验证的备用容量。直接采购多台 Mac 可以获得物理控制权,但会把折旧、维护、闲置容量和故障替换一起锁定。

更稳妥的做法,是先用 VpsMesh 的短周期远程 Mac 验证一个非生产 Xcode 工作流:确认 Runner 注册、任务状态回传、环境清理、重启恢复和制品交付都能闭环。验证通过后,再决定哪些容量值得固定采购,哪些容量更适合按需租赁。