Obi Madu 的博客
返回所有文章
InfrastructureAIAI Engineering

Coder 任务、工作空间和 OpenCode

Coder 的工作空间、模板和配置器,以及任务如何把它们变成 AI 编程代理的临时执行环境。

Coder 任务、工作空间和 OpenCode

当你不再把 Coder 只看作一个远程 IDE 平台,它就更容易理解了。Coder 的核心是按需给你可重复的开发环境。用户点一个按钮或跑一个 CLI 命令,Coder 就从预定义的模板部署一个工作空间。这个工作空间可以跑在 Docker 容器、Kubernetes 集群、云 VM 或其他基础设施目标里。开发者用 VS Code、JetBrains IDE、终端或浏览器编辑器连上去。

Coder 任务(Coder Tasks)建立在同一个基础上,但把它重新用于 AI 编程代理。任务不是给人类开发者交互用的工作空间,而是给自主代理跑的隔离、临时工作空间。这个区别很重要。Coder 任务不是一个独立的执行平台。它们就是标准的 Coder 工作空间,带一个面向 AI 代理的界面。

Coder 基本模型

理解 Coder 需要三个概念:工作空间(workspaces)、模板(templates)和配置器(provisioners)。

工作空间是一个隔离的开发环境。它是开发者或 AI 代理交互的计算实体。模板定义这个工作空间怎么构建。Coder 模板用 Terraform 写,这意味着它们可以描述本地容器、Kubernetes Pod、云 VM、持久卷、代理、暴露的应用、元数据和用户可配置的参数。

配置器是执行 Terraform 代码的进程。这个细节比看起来重要。你在模板里指定的基础设施提供商跑在配置器跑的地方,不是在新创建的工作空间里。

Coder server -> Provisioner runs Terraform -> Docker, Kubernetes, or cloud provider creates workspace

如果你的模板用 Docker 提供商,跑配置器的机器上必须有 Docker 访问。如果配置器本身是容器化的,那个容器需要直接访问主机的 Docker socket、远程 Docker 主机,或你配置的任何 Docker 连接。

模板是带 Coder 数据源的 Terraform

一个最小的模板通常声明 Coder 提供商、一个基础设施提供商、工作空间数据源、一个 Coder 代理和构成工作空间的资源。

terraform {
  required_providers {
    coder = {
      source  = "coder/coder"
      version = ">= 2.13"
    }
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

resource "coder_agent" "main" {
  arch = data.coder_provisioner.me.arch
  os   = "linux"
}

Coder 代理让 Coder 服务器连进工作空间、暴露运行的 web 应用、执行启动脚本和上报状态。基础设施提供商创建环境。用 Docker,就是容器和卷。用 Kubernetes,就是 Pod 和持久卷声明。用云提供商,就是 VM 和持久磁盘。

Terraform 变量与 Coder 参数

最容易搞混的地方之一是 Terraform 变量和 Coder 参数的区别。

Terraform 变量由模板管理员在模板推送到服务器时设置。它们用于基础设施级配置和强制静态值,用户不应该选这些。Coder 参数则是在用户创建工作空间或任务时展示给用户。它们让用户选容器镜像、地理区域、CPU 大小、LLM 模型或自定义工作目录这类东西。

概念设置者设置时间最适用于
Terraform 变量模板管理员模板推送时机密、默认值、基础设施配置
Coder 参数最终用户工作空间或任务创建时用户选项

这个例子同时展示两者:

variable "anthropic_api_key" {
  type      = string
  sensitive = true
}

data "coder_parameter" "container_image" {
  name         = "container_image"
  display_name = "Container Image"
  type         = "string"
  default      = "ubuntu:22.04"
  mutable      = false
}

coder_parameter 块是一个数据源。在工作空间创建期间,它从 Coder UI 取用户选的值。

这里有一个安全细节值得知道。把变量标为 sensitive = true 能阻止 Terraform 和 Coder 日志打印这个值,但一旦这个值进了运行中的工作空间,它就不再受保护。如果你把机密作为纯环境变量注入容器,任何有 shell 访问权限的人都能读它。

模板执行的两个阶段

Coder 模板在模板导入时和在工作空间创建时表现不一样。在 coder templates push 期间,Coder 解析 Terraform 代码、提取定义的参数、构建 UI 表单、要 Terraform 变量的值,并检测模板是否支持 AI 任务。

在工作空间创建期间,Coder 把那个表单展示给用户、存下选的参数值、跑 terraform apply,让 data "coder_parameter" 块在执行中取这些值。这种分叉是 Coder 参数一开始让人感觉奇怪的原因。它们不是普通的 Terraform 变量。它们是 Coder 管理的值,Terraform 只在 apply 阶段读它们。

临时参数(Ephemeral parameters)加了一条严格规则。如果你声明 ephemeral = true,这个参数也必须标为 mutable,并且必须有默认值。

data "coder_parameter" "api_key" {
  name      = "api_key"
  type      = "string"
  mutable   = true
  ephemeral = true
  default   = ""
}

临时参数在你想要在创建时提供敏感或临时值、但不想存到工作空间长期元数据里时有用。

Coder 任务加了什么

Coder 任务提供一个简化界面,在隔离的工作空间里跑 AI 编程代理。用户用自然语言 prompt 创建一个任务。Coder 存下 prompt。Terraform 部署工作空间。模板取回存的 prompt,传给环境里的代理集成。

用户提交任务 prompt
        |
        v
Coder 存 prompt
        |
        v
Terraform 部署工作空间
        |
        v
代理收 prompt 并在工作空间里跑

每个任务有自己的工作空间。这给 AI 代理一个隔离的文件系统、干净的依赖、工具访问和运行时环境。工作空间通常不需要 GPU,因为代理通过网络跟外部 LLM API 通信。Coder 模板在定义 coder_ai_task 资源时就变得支持任务了。

resource "coder_ai_task" "task" {
  app_id = module.opencode.task_app_id
}

存的任务 prompt 可以用 data "coder_task" 数据源取回。

data "coder_task" "me" {}

module "opencode" {
  ai_prompt = data.coder_task.me.prompt
}

从最终用户角度看,启动一个任务可以简单到一条命令:

coder tasks create \
  --template my-template \
  --parameter container_image="python:3.12" \
  "Refactor the authentication module"

关键部分是任务 prompt 变成基础设施的输入。工作空间围绕代理要做的那个工作来创建。

OpenCode 的定位

OpenCode Coder 模块把 OpenCode 集成进 Coder 工作空间。它可以把 OpenCode 暴露为交互式应用、在后台任务模式跑它、传 prompt 进去、配置认证或模型设置。

一个简单的模块配置长这样:

module "opencode" {
  source   = "registry.coder.com/coder-labs/opencode/coder"
  version  = "0.1.1"
  agent_id = coder_agent.main.id
  workdir  = "/home/coder/project"
}

用于任务时,它连到 data.coder_task.me.promptcoder_ai_task 资源。

data "coder_task" "me" {}

resource "coder_ai_task" "task" {
  app_id = module.opencode.task_app_id
}

module "opencode" {
  source           = "registry.coder.com/coder-labs/opencode/coder"
  version          = "0.1.1"
  agent_id         = coder_agent.main.id
  workdir          = "/home/coder/project"
  ai_prompt        = data.coder_task.me.prompt
  auth_json        = data.coder_parameter.opencode_auth_json.value
  config_json      = data.coder_parameter.opencode_config_json.value
  opencode_version = "latest"
}

如果你只想要 OpenCode 在工作空间终端里、不要 web UI 或任务报告,模块也支持纯 CLI 模式。

module "opencode" {
  source       = "registry.coder.com/coder-labs/opencode/coder"
  version      = "0.1.1"
  agent_id     = coder_agent.main.id
  workdir      = "/home/coder"
  report_tasks = false
  cli_app      = true
}

要点:Coder 提供工作空间并管理它的生命周期,OpenCode 提供工作空间里的代理体验。

Docker 提供商陷阱

很多本地 Coder 设置依赖 Docker,所以 Docker Terraform 提供商变成模板逻辑的一大块。默认它跟本地 Unix socket 通信。

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

它也可以配置成通过 SSH 连远程主机。

provider "docker" {
  host     = "ssh://user@remote-host:22"
  ssh_opts = ["-o", "StrictHostKeyChecking=no"]
}

关键细节是 SSH 密钥和 Docker 访问必须在配置器跑的地方存在。如果配置器在容器里跑,把 SSH 密钥挂载到你的 laptop 上不够。配置器容器需要挂载访问它。

Docker socket 权限是另一个常见问题。如果 Coder 在 Docker 沙箱里跑,但需要通过主机的 Docker socket 创建兄弟容器,Coder 容器需要访问 /var/run/docker.sock 并有正确的组权限。

services:
  coder:
    image: ghcr.io/coder/coder:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    group_add:
      - "998"

组 ID 必须跟主机上的 Docker 组匹配。

网络也可能让你意外。如果工作空间容器需要到达绑在主机 localhost 上的 Coder 服务器,从容器内部解析 localhost 会指回容器自己。一个常见的修复是用 host.docker.internal 加手动 host gateway 映射。

基础镜像与工作空间生命周期

Coder 提供的基础镜像是有原因的。它们包含预配置的 coder 用户、预期的 home 目录行为、代理依赖、骨架文件和常见开发工具。

用像 ubuntu:latest 这样的纯镜像可以工作,但意味着你自己承担更多设置。你得创建用户、装工具、配置 home 目录,并确保 Coder 代理能正确启动。

工作空间生命周期也重要。有些资源应该只在工作空间启动时存在。Coder 暴露一个 start_count 指标,模板把它跟 Terraform 的 count 元参数配对。

resource "docker_container" "workspace" {
  count = data.coder_workspace.me.start_count
}

module "opencode" {
  count = data.coder_workspace.me.start_count
}

持久卷是另一个问题。如果你想让用户的 home 目录或工作空间数据在停止和重建后存活,保护这个卷。

resource "docker_volume" "home" {
  name = "coder-${data.coder_workspace.me.id}-home"

  lifecycle {
    ignore_changes = all
  }
}

把它放在一起

Coder 是一个管理可重复开发环境的控制平面。Terraform 是描述这些环境的语言。配置器跑 Terraform 代码。工作空间是结果 VM、容器或 Pod。任务是围绕 AI 代理 prompt 创建的专门工作空间。

OpenCode 作为跑在工作空间里的工具融入这个模型。它不是基础设施层本身;它是上层的代理层。一旦这个分离清楚了,让人困惑的部分就更容易调试。

如果 Docker 资源失败,看配置器和 Docker 主机。如果用户参数缺了,追 Coder 参数流。如果任务 prompt 没到代理,检查 data "coder_task" 和模块接线。如果机密出现在运行中的容器里,记住 Terraform 的敏感性概念不等于运行时机密。

Coder 任务强大是因为它把可重复的基础设施和隔离的代理执行结合起来。这个力量的代价是你要理解两半:Coder/Terraform 部署模型和跑在工作空间里的代理集成。

参考资料