Ona:用 Project、Runner 与 Dev Container 交付可复现开发环境
一个新同事从仓库启动云端开发环境,十几分钟后仍在等待依赖安装;另一个同事复用昨天停止的环境,发现未提交代码还在,却误以为重建 Dev Container 也会保留全盘文件;第三个人把服务端口打开后收到 503 Service Unavailable,应用日志明明写着“Listening on localhost:3000”。这些现象看似分别属于构建、存储和网络,实际都指向同一个问题:团队只记住了“云端工作区”这个入口,没有建立 Ona 对象与生命周期的完整模型。
为 Ona 新项目配置环境时,应使用 Project、Runner、Environment、Dev Container 与 automations.yaml 这组当前对象。旧 Gitpod Classic 的 workspace、.gitpod.yml 和 Classic API 不是这些对象的同义名称;Ona Environment 模型使用 Project 连接仓库与 Runner,用 Environment 承载隔离虚拟机和持久存储,再消费标准 Dev Container 配置与任务、服务声明。旧仓库迁移时,先把镜像、初始化命令、端口、任务和密钥逐项映射到新对象,不能把旧配置文件改个名字就期待行为等价。
先认清四个对象,故障才有落点
Project 不是正在运行的机器。它把仓库 URL、默认分支、允许使用的 Environment Class、Dev Container 路径、Automations 路径和推荐编辑器组合成团队入口。Runner 是执行基础设施:它克隆代码、创建隔离虚拟机、投递密钥、执行构建和任务。Environment 才是一次可启动、停止、恢复、归档和删除的工作实例。Environment Class 则是 Runner 发布的资源规格,决定 CPU、内存、存储和区域等资源属性。
一次创建请求从 Ona 托管的管理平面进入 Runner。Ona 的双平面运行方式中,管理平面处理认证、组织设置、策略与编排;Runner 所在的数据执行面接触源码、仓库凭据、构建产物和运行时密钥。使用 Ona Cloud 时,Runner 基础设施由 Ona 管理;使用 AWS 或 GCP Runner 时,Environment 位于企业自己的云网络中,网络、配额、镜像访问和基础设施账单也随之回到企业责任内。这个边界直接决定私有仓库、内网依赖和受监管数据能否进入环境。
判断故障位置时不要把状态混在一起。Project 创建成功,只证明仓库入口和配置对象存在;Environment 进入 Running,仍不证明 Dev Container 构建、服务就绪或端口可达;停止后能恢复文件,也不代表删除后还能找回。每一层都要保留自己的证据。
安装 CLI,并确认身份落在哪个组织
Ona 的云端操作需要账号、组织、至少一个 Runner,以及能访问 GitHub 或 GitLab 仓库的身份。Ona Cloud 账号通常已有托管 Runner;自有云 Runner 需要管理员先完成 AWS 或 GCP 部署。CLI 支持 macOS、Linux 与 Windows。macOS 或 Linux 可通过 Homebrew 安装:
brew install gitpod-io/tap/ona
ona version
ona login
ona whoami没有 Homebrew 时,应从 Ona CLI 安装页选择对应操作系统与 CPU 架构的二进制。对供应链要求较高的环境,优先使用文档提供的 SLSA 校验安装方式;直接下载也要把清单中的 SHA-256 与本地文件摘要比对。Windows 下载的 ona.exe 需要放进受控的 PATH 目录,再从新终端检查版本。
ona login 会打开浏览器并在本机保存认证信息。自动化脚本可使用个人访问令牌与 ONA_TOKEN,但令牌不能写进仓库、命令历史或 CI 明文日志;脚本结束后应清理环境变量,并按用途设置短生命周期与定期轮换。登录完成后,ona whoami 应显示预期身份和访问级别,ona project list 应只出现当前身份可见的 Project。若浏览器登录成功而列表为空,先检查组织是否切错,再查成员权限和仓库授权,不要重复创建同名项目。
没有 Ona 租户时,本机只能确认 CLI 下载、摘要、版本与帮助输出,无法证明组织、Runner、Project 和 Environment 的云端链路已成功。下面的状态与输出特征是接入租户后应观察的判据。
自有 Runner 先定数据边界,再执行云模板
Ona Cloud 不需要团队部署 Runner;AWS 与 GCP Runner 则属于 Enterprise 能力,分别通过 CloudFormation 和 Terraform 部署到企业 VPC。Runner 架构把源码克隆、密钥注入、Environment 创建、任务和服务执行放在 Runner 一侧,管理平面负责身份、策略与协调。选择自有 Runner 的理由应是数据驻留、私网依赖、区域或网络控制,而不只是“想自己维护”;它同时把云配额、升级、监控、出网和闲置资源账单交给企业。
以 GCP Runner 为例,先在 Ona 控制台创建 Runner,取得部署所需身份,再准备启用计费和 API 的项目、VPC、Runner 子网、域名以及同时覆盖根域名与通配符域名的证书。Terraform 运行身份需要创建和管理 Compute Engine、Cloud Storage、Artifact Registry、Secret Manager 与服务账号的权限;部署完成后应收敛为运行期最小权限,不能长期保留项目管理员身份。将官方模块固定到已评审版本,敏感 token 放在 CI Secret 或受控变量文件中:
terraform init
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform output load_balancer_ip关键字段不是简单的“机器规格”:project_id 决定资源与账单归属,region/zone 决定数据位置和故障域,vpc_name 与 runner_subnet_name 决定路由边界,负载均衡模式决定入口是公网还是企业内网,域名与证书决定编辑器、端口代理和回调是否能建立可信 TLS。使用 Shared VPC 时,vpc_project_id 指向网络宿主项目,Runner 服务项目还必须获得目标子网的 compute.networkUser。无公网出站的子网要启用 Private Google Access,并为控制面、镜像、制品和依赖源建立明确的 allowlist 或代理路径。
DNS 生效后,不要用 curl -k 作为最终判据;它会跳过最容易在编辑器和 agent 上失败的证书验证。应让根域名与通配符域名解析到负载均衡地址,以正常 CA 校验访问 https://<runner-domain>/_health,期望 HTTP 200 和 {"status":"ok"},同时在 Settings → Runners 看到 Online。反向实验可以从隔离测试子网阻断控制面域名或镜像仓库:Runner readiness 应退化,Environment 创建日志出现 DNS、TCP 443 或拉取错误。恢复规则后再次创建干净 Environment,确认错误消失;不能为了让状态变绿而开放不受限出网。
AWS Runner 的同类字段落在 CloudFormation:Runner 服务运行于 ECS/Fargate,Environment 使用 EC2;私有子网可经 NAT 或 VPC Endpoint 访问 Secrets Manager、CloudWatch Logs、ECR、S3、STS 等服务。使用 Internet Gateway 时若 Fargate task 没有公网 IP,只有路由仍不能出网。企业应从目标网络模型选择一种路径,并用 Runner readiness 与端点级连接证据定位失败,避免同时叠加 NAT、代理和宽泛公网规则后失去流量归因。
从 Project 启动第一个 Environment
进入组织后,在 Projects 中创建 Project,选择仓库、项目名和至少一个 Environment Class。Project 可允许多个规格,但日常入口不要暴露一排含义相近的大规格;先用能够完成依赖安装、索引和测试的最小规格,再按构建内存峰值、并发服务数和磁盘增长调整。资源不足通常表现为进程被杀、构建变慢或 No space left on device,并不总会显示成 Ona 平台错误。
Project 设置里应显式确认 Dev Container 路径和 Automations 路径,例如 .devcontainer/devcontainer.json 与 .ona/automations.yaml。Ona 默认在仓库根目录查找 automations.yaml;使用 .ona/automations.yaml 等其他位置时,必须在 Project 中配置对应路径。这样仓库重构后,任务未出现可以直接核对 Project 路径与目标提交,而不是猜测发现规则。
创建 Environment 时选择 Project、分支和 Environment Class。CLI 也提供仓库 URL 入口;下面是目标租户中的验证命令,不代表只安装本机 CLI 就已完成云端创建:
ona environment create https://github.com/your-org/your-project.git --inactivity-timeout 1h
ona environment list -o json生产团队更适合从已治理的 Project 创建,因为 Project 已限制 Runner、规格和配置路径;直接仓库 URL 适合个人试验,管理员可以通过组织策略禁止成员绕过现有 Project。创建后用 ona environment logs <environment-id> 观察 provisioning、Dev Container build 与 startup 阶段。预期 Environment 最终进入可连接状态;若停在构建阶段,应从最早的非零退出码、镜像拉取错误或 Feature 冲突开始处理。
用 Dev Container 固化工具链,而不是固化一台旧机器
仓库加入 .devcontainer/devcontainer.json:
{
"name": "your-project",
"image": "mcr.microsoft.com/devcontainers/javascript-node:20",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": false
}
},
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint"]
}
},
"forwardPorts": [3000]
}image 决定基础用户空间与预装工具,是可复现性的最大输入;应固定到团队验证过的标签,要求更严格时固定镜像 digest。features 会在镜像之上安装能力,版本变化可能改变包和启动行为,也要显式写主版本。customizations 只塑造编辑器体验,不能代替构建依赖。forwardPorts 告诉 Dev Container 消费者关注哪些端口,但它不等同于 Ona 已把端口共享给外部,也不会修复只监听 loopback 的应用。
Ona 可消费标准 Dev Container,因此同一配置也能由 VS Code Dev Containers 等实现使用。不过“遵循同一规范”不代表所有宿主行为完全一致:云端 Runner 通常是 Linux,Feature、挂载、网络模式和特权能力仍需在目标 Runner 验证。构建或启动失败时,Ona 会进入 recovery mode,用临时恢复容器挂载工作区;此时可以查看日志并修复配置,但失败镜像、生命周期命令和编辑器定制都没有真正生效。
让任务完成,让服务保持运行
按照 Tasks 与 Services 的执行语义,把一次性动作和长进程写进 .ona/automations.yaml:
services:
api:
name: Demo API
commands:
start: >-
node -e "require('http').createServer((req,res) => {
res.writeHead(200, {'content-type':'application/json'});
res.end(JSON.stringify({status:'ok'}));
}).listen(3000, '0.0.0.0')"
ready: curl --fail --silent http://127.0.0.1:3000/ > /dev/null
readinessTimeout: 2m
triggeredBy:
- postDevcontainerStart
tasks:
smoke:
name: Probe Demo API
command: curl --fail --silent http://127.0.0.1:3000/
dependsOn:
- api
triggeredBy:
- manualservices.api 的键是 CLI 与依赖引用使用的稳定标识,name 只是展示名。commands.start 必须持续阻塞;进程存活时服务才保持 Active,正常退出会进入 Stopped,非零退出进入 Failed。commands.ready 会被重复执行,直到退出码为零;没有它,Ona 会在 start 刚启动时就把服务视为就绪。readinessTimeout 限制无休止等待。dependsOn 让 smoke 在 api 就绪后再运行,而不是用脆弱的 sleep 10 猜测启动时间。
提交配置后更新或重建 Environment,再执行:
ona automations service list
ona automations task start smoke
ona automations task logs smoke在 Environment 内,CLI 会推断当前 Environment;从本机执行时增加 --environment-id <environment-id>。正常结果是 api 已就绪、smoke 进入 SUCCEEDED,日志正文包含 {"status":"ok"}。这三项分别证明长进程存活、依赖闸门通过和真实 HTTP 请求成功,仅看到 Environment 为 Running 不能替代它们。
接入真实 Node 项目时,把 start 换成 npm run dev,把 ready 指向无需登录且能反映依赖状态的健康端点,把 smoke 换成仓库已有的测试脚本。数据库、缓存等服务也应分别提供就绪命令。任务键一旦被脚本和团队入口引用,就按 API 名称治理;修改键名要同步 CLI、文档与自动触发配置。
两个反向实验,把错误机制稳定暴露出来
先把 start 改成会立即退出的后台命令:
commands:
start: node server.js &Shell 启动子进程后立即结束,Ona 观察到的服务主进程已经退出。即使 Node 子进程短暂存活,服务也会进入 Stopped;如果启动命令以非零码结束,则进入 Failed。证据应从 ona automations service list 的阶段和服务日志取得。修复方式是让前台进程成为 start 的主进程,例如直接执行 node server.js,再重启服务并确认阶段保持 Active。
第二个实验把监听地址从 0.0.0.0 改成 127.0.0.1,然后开放端口:
ona environment port open 3000 --name demo-api --admission creator_only
ona environment port list这个反例应表现为 Environment 内部的 curl http://127.0.0.1:3000/ 成功,而共享 URL 返回 503 Service Unavailable。原因是 Ona 端口代理从 loopback 外部连接服务。恢复监听 0.0.0.0 后,共享 URL 才应返回健康 JSON。若应用无法改变绑定地址,可用受控转发规则把另一个 0.0.0.0 端口转到 loopback,并记录规则的删除命令;不要为了省事默认使用 everyone,也不要把数据库管理端口公开到互联网。
--admission creator_only 仅允许创建者,organization 允许组织成员,everyone 允许未认证访问;省略时默认是 everyone。Runner 版本过旧时可能无法强制端口认证,端口会表现得像公开入口。端口开放前要同时检查 Runner 能力、组织策略和服务自身认证,关闭时执行 ona environment port close 3000 并再次列出端口确认入口消失。
Prebuild 加速的是已知输入,不是隐藏漂移
Prebuild 会创建临时 Environment,构建 Dev Container,执行相关生命周期命令以及带 prebuild 触发器的任务,随后快照 Dev Container 文件系统并删除临时 Environment。新 Environment 会尽力选择与 Environment Class 匹配的最近成功快照;没有可用快照时正常冷启动,因此业务流程不能假设每次都命中。
依赖安装任务可以同时响应预构建和用户重建:
tasks:
install-deps:
name: Install locked dependencies
command: npm ci
triggeredBy:
- prebuild
- postDevcontainerStart
prebuildRequiresSuccess: trueprebuild 把锁文件对应依赖放入快照,postDevcontainerStart 则覆盖用户主动重建 Dev Container 的路径。prebuildRequiresSuccess: true 很关键:默认情况下,预构建任务失败只产生警告,快照仍可能成功;依赖安装属于环境正确性的组成部分,应让非成功结果使 Prebuild 失败。触发后可检查:
ona prebuild trigger <project-id>
ona prebuild list --project-id <project-id>可用证据不是“启动感觉更快”,而是 Prebuild 进入 Completed、新 Environment 使用匹配规格的快照、锁定依赖已经存在且测试通过。快照有保留周期,且只有最近成功版本会被使用;变更基础镜像、锁文件、Feature 或规格后,应重新触发并比较冷启动与命中快照的耗时分段。
密钥在构建、预构建和运行阶段不是同一份
Ona 支持组织、Project 和用户三层密钥。同名且挂载点相同时,用户级优先于 Project,Project 优先于组织。共享测试服务凭据通常放 Project;跨项目通用的镜像凭据可由组织管理;个人身份令牌留在用户级。环境变量易被子进程、诊断输出和错误日志带出,证书、私钥和密码优先使用文件密钥,并限制挂载路径权限。
Dev Container 镜像构建只能使用组织与 Project 密钥,且 Dockerfile 的 RUN 必须显式请求 BuildKit secret mount。用户密钥不能进入构建,因为构建缓存可能在 Project 成员间共享。下面的写法把令牌限制在单个构建步骤,并在同一步删除临时配置:
# syntax=docker/dockerfile:1
FROM node:20
WORKDIR /workspace
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=NPM_TOKEN,env=NPM_TOKEN \
printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > .npmrc \
&& npm ci \
&& rm -f .npmrc临时挂载不会自动阻止命令把值写入镜像层,因此删除动作必须发生在同一个 RUN。Docker Compose 形式的 Dev Container 目前不能使用这条构建期密钥路径。Prebuild 同样没有用户上下文:组织和 Project 密钥可用,用户密钥不可用;预构建执行身份应使用服务账号,避免启用者离职或 SCM 授权撤销后持续失败。
运行中的 Environment 会接收作用域内密钥。密钥更新传播需要时间;文件密钥会原位更新,环境变量的新值虽然写入磁盘,已运行进程却不会自动刷新,需重启 Environment 或重建 Dev Container;镜像仓库密钥在下一次拉取时生效。轮换后要重新启动依赖旧值的服务并验证认证,不能只看控制台显示“已更新”。日志、任务命令和健康端点都不得回显真实值。
权限反证应使用两个真实身份而不是截图配置页。普通成员可以创建自己被授权 Project 的 Environment,但不应修改 Runner、组织策略或其他成员的 Secret;组织管理员执行同一管理动作应成功。失败记录要保留 actor、subject、action 和时间,不保存 token。Enterprise 组织可以用 Audit Logs查询 Environment、Runner、Project、Environment Class、Task、Service、Token、Secret 与登录事件:
ona audit-logs --subject-type="environment" --actor-principal="user" --limit=100
ona audit-logs --subject-type="runner" --from="<RFC3339-time>" --limit=100如果越权操作成功,应立即收紧角色、Project 成员和组织策略并轮换受影响凭据;如果操作被拒绝但审计事件缺少主体或动作,则需要补齐日志接入后再把平台用于受监管项目。审计日志可揭示仓库名、用户身份和资源操作,导出权限应只授予安全与平台值班角色,并设置与离职、事故调查和合规周期一致的保留规则。
停止、重建、归档和删除有四种数据后果
Environment 的持久存储附着在底层虚拟机。停止再启动时,磁盘内容保留,仓库、未提交改动、用户目录和安装文件仍可能存在。重建 Dev Container 会从镜像重新创建大部分文件系统,但 /workspaces/<repository-name> 通过 bind mount 保留;需要跨重建保存的包缓存应显式挂到 /workspaces 下的受控目录,例如:
{
"mounts": [
"source=/workspaces/.cache,target=/home/gitpod/.cache,type=bind"
]
}这能降低重复下载,却也会让损坏缓存和旧凭据跨重建存活。缓存目录必须可删除、不可承载唯一数据,并设置容量观察。磁盘报警时先执行 df -h,再用项目对应工具定位 node_modules、镜像层、构建产物和缓存增长;不要在不确认挂载点的情况下递归删除 /workspaces。
Stop 停止计算并保留恢复入口;自动 Archive 只处理达到不活跃条件的已停止 Environment,手工归档运行中 Environment 时 Ona 会先停止它。归档只把对象移到归档视图,存储仍在,可解除归档;Delete 永久删除全部内容,只有归档对象能恢复,已删除对象不能恢复。关闭编辑器窗口并不等于 Stop。归档和自动删除周期受套餐、组织策略和用户设置影响,应在目标组织策略页面确认,不要把未 push 的代码寄托在保留天数上。
生命周期可以用一份无敏感信息的哨兵文件证明。先写入文件并记录远端 Git 状态,执行 ona environment stop <id>、ona environment start <id>,再经 SSH 读取哨兵文件;正常结果是 Stop 后计算停止、Start 后文件仍在。随后把同一提交创建为第二个 Environment:它应从 Project 配置和可用 Prebuild 初始化,而不是继承第一个 Environment 的未提交文件。删除测试 Environment 后,ona environment list 不再返回它,重新 create 得到的是新环境;若旧哨兵仍出现,应检查 Prebuild 是否错误快照了不该共享的数据或初始化脚本是否从外部恢复了旧状态。
安全清理顺序是:先运行测试,提交并 push 需要保留的代码;导出必要但不能入库的诊断材料到批准位置;关闭共享端口;停止 Environment;确认远端分支或制品可恢复;最后删除 Environment。离职或 Project 下线还要撤销 PAT、SCM 授权、Project/用户密钥和服务账号,禁用 Prebuild 配置,并检查自有云 Runner 是否仍保留磁盘、快照、日志或镜像缓存。
从一个仓库推广到团队时,配置必须有所有者
可复现不是“每个人最终都能跑起来”,而是同一提交、同一 Project 配置和同一资源规格产生可解释的环境。基础镜像和 Feature 由平台团队维护,语言依赖由仓库锁文件维护,Tasks 与 Services 由应用团队维护,Environment Class、Runner 网络和保留策略由基础设施团队维护。每一层都要有升级节奏和故障联系人。
升级时先在测试 Project 或非关键分支构建新镜像,记录 Dev Container 构建、服务就绪、测试完成和磁盘使用的基线,再逐步放开。回滚不是恢复旧 Environment,而是把镜像标签或 digest、Feature 版本和自动化配置恢复到已知提交,触发新的 Prebuild,再从干净 Environment 验证。保留一个冷启动路径,能及时发现快照掩盖的初始化缺陷。
治理证据应围绕趋势:各阶段启动耗时是否持续上升,Prebuild 命中与失败原因是否集中在某个规格,停止 Environment 数和存储量是否单调增长,磁盘使用是否在清理后回到稳定区间,公开端口是否存在超期入口。固定阈值要来自团队并发、仓库体积、SLO 和预算,而不是照抄示例数字。
Runner 选型决定数据位置,也决定运维账单
Ona Cloud 适合快速启用和不需要私网依赖的小团队,基础设施由平台管理,但源码、构建与密钥会在 Ona 管理的 Runner 基础设施中处理。AWS 或 GCP Runner 适合需要 VPC 内访问、私有制品库、定制 CA、区域约束和更强网络控制的企业;代价是云账号配额、实例、磁盘、负载均衡、出网、升级和监控都需要企业承担。
Environment Class 不只是“机器大小”。CPU 不足拖慢编译,内存不足导致 OOM,存储不足会在镜像拉取或依赖展开中失败,区域选择影响代码宿主和制品库延迟。Spot 规格降低计算单价,但会引入回收与重新调度;有状态开发任务必须确认磁盘可重新挂载,长测试也要能重试。需要稳定交互时优先常规实例,批量、可重试的任务再评估 Spot。
成本至少包含运行中的 Environment 计算、持久存储、自有云基础设施、镜像和依赖下载流量、Prebuild 执行时间、快照或缓存,以及保留日志。Prebuild 用额外计算换取多人重复等待的下降;只有当相同输入被足够多的新 Environment 消费时才划算。为 Environment 设置合理 inactivity timeout,按 Project 汇总规格与生命周期,定期删除已合并分支对应的环境,并把大规格申请和公开端口纳入策略。
退出设计应在采用时就存在。仓库中的 Dev Container 和应用任务尽量保持标准、可在本地 Dev Containers 或其他兼容平台运行;平台专有的 Project、Runner、Prebuild、端口 admission 和生命周期策略单独记录映射。离开 Ona 时,先证明仓库能在另一消费者中从干净状态构建,再迁移密钥存储和端口入口,最后导出需要的审计与成本数据、删除 Environment 和 Project、撤销凭据,并按云资源清单拆除自有 Runner。这样,开发环境是一份能迁移的工程合同,而不是一台没人敢删的远程机器。
