Agent Lightning Controller 配置实践:用 Hydra 配置 k8s 与 local 两种 rollout 执行后端
【免费下载链接】agent-lightningThe absolute trainer to light up AI agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-lightning
Agent Lightning 中的 Controller 负责把声明式的 rollout 翻译成真实的 Agent 执行:它从 API Gateway 领取QUEUING状态的 rollout,在 Kubernetes Job 或本地子进程中运行 Agent,并把执行结果回写状态。本文基于docs/6-controller-configuration.md,结合agentlightning/controller/下的入口、K8s 协调器与本地协调器源码,完整讲解 Controller 的启动方式、Hydra 默认配置、命令行覆盖、两种 runner 的连接与限流参数,以及 Agent 进程的最终注入机制,帮助读者掌握可复制、可运行的 Controller 部署与调优方案。
Controller 是什么、如何启动
Controller 通过agl-controller命令启动。这个命令入口在 pyproject.toml 中注册:
[project.scripts] agl-server = "agentlightning.server.__main__:main" agl-controller = "agentlightning.controller.__main__:main"入口函数定义在 agentlightning/controller/main.py,它是一个标准的 Hydra 应用:
@hydra.main(version_base=None, config_path="../config", config_name="controller") def main(config: DictConfig) -> None: asyncio.run(_run_controller(config))从源码结构看,启动流程分为三步:
- Hydra 加载完整默认配置(配置组目录为
../config,即 agentlightning/config/controller.yaml); _run_controller用config.agl_server.url和config.agl_server.key创建AgentLightningAsyncClient,作为访问 API Gateway 的通道;- 根据
config.runner_type分支:k8s时导入K8sReconciler(该路径在main.py 中是延迟导入,若缺少kr8s会抛出提示安装 controller 依赖的RuntimeError),local时导入LocalReconciler;其它取值直接抛ValueError。
此外入口还为SIGTERM/SIGINT注册了信号处理器(main.py),收到信号后调用reconciler.stop()优雅退出——对本地模式而言,这意味着正在运行的 Agent 子进程会被终止并标记为 FAILED(详见下文本地协调器部分)。项目要求 Python ≥ 3.12,并固定使用hydra-core==1.3.2与omegaconf==2.3.0(见 pyproject.toml 的dependencies),因此下述配置语法均以 Hydra 1.3 为准。
完整默认配置
Controller 的完整默认配置来自 agentlightning/config/controller.yaml,全文如下:
runner_type: k8s agl_server: url: http://localhost:8080 agent_url: null key: "" k8s_runner: namespace: default ttl_after_finished: 1200 max_jobs_per_minute: 100 poll_interval: 5 local_runner: maximum_size: 50 poll_interval: 10其中runner_type是全局开关,决定后四组参数中哪一组生效:k8s模式读取k8s_runner.*,local模式读取local_runner.*。其余配置对两种模式都生效(尤其是agl_server.*)。
用 Hydra 命令行覆盖配置
启动时可以用任意 Hydra 命令行参数覆盖配置中的任意一项,无需修改 YAML 文件。例如切换到本地模式、指定 API Gateway 地址与鉴权 key、缩小进程池:
agl-controller \ runner_type=local \ agl_server.url=http://localhost:8080 \ agl_server.key="$AGL_KEY" \ local_runner.maximum_size=32这种覆盖方式与项目其它组件(Trainer、API Gateway)保持一致:agl_server.key必须与 API Gateway 的 key 一致,否则请求会被拒绝。
runner_type:k8s 与 local 二选一
| 键 | 默认值 | 说明 |
|---|---|---|
runner_type | k8s | 本 Controller 实例使用的唯一执行后端:k8s或local |
一个 Controller 实例同一时间只能运行一种模式,不能混用。两种模式的执行语义为:
- k8s 模式:每个 rollout 运行为一个 Kubernetes Job。Controller 使用本机
~/.kube/config的默认 Kubernetes 配置访问集群——从源码看,协调器通过kr8s.asyncio.api()惰性初始化集群客户端(见 k8s_reconciler.py),这正是依赖默认 kubeconfig 的原因; - local 模式:每个 rollout 运行在 Controller 所在机器上的一个本地子进程中,多个 rollout 由进程池统一调度。
连接 API Gateway:两个 URL、一个 key
Controller 配置中包含两个 API Gateway URL,对应两条不同的网络路径:
| 键 | 默认值 | 说明 |
|---|---|---|
agl_server.url | http://localhost:8080 | Controller 自身使用的 API Gateway 地址 |
agl_server.agent_url | null | Agent 使用的 API Gateway 地址;为null时回退到agl_server.url |
agl_server.key | "" | Controller 与 Agent 共用的 Bearer key |
两者可达性的要求不同:
agl_server.url必须能从 Controller 进程访问,因为 Controller 用它查询和打补丁/api/rollouts;agl_server.agent_url必须能从 Agent 进程(或 Pod)访问,因为注入到 Agent 的 Gateway 代理地址和事件上报地址就是基于它拼出来的。
大多数场景下agent_url保持null即可,Agent 自动复用agl_server.url。只有当 Agent 无法通过agl_server.url到达 Gateway 时才需要单独设置——典型场景是 Minikube Docker driver:Controller 跑在宿主机上,Agent 跑在 Minikube 内部,两者网络不通。此时 Controller 侧可用http://localhost:8080,而 Agent 侧需要http://host.minikube.internal:8080访问同一个 Gateway(controller.yaml 中的注释也给出了这个示例)。
源码印证了这个回退逻辑:构造 Job 时,k8s_reconciler.py 以agent_url优先、url兜底的方式确定 Agent 侧基址:
agent_base_url = str( controller_config.agl_server.get("agent_url", None) or controller_config.agl_server.url ).rstrip("/")本地模式同样如此:local_reconciler.py 用self._config.agl_server.url拼接注入的环境变量。
k8s_runner:Job 创建限流与完成清理
| 键 | 默认值 | 说明 |
|---|---|---|
k8s_runner.namespace | default | Job 创建的命名空间 |
k8s_runner.max_jobs_per_minute | 100 | Controller 每分钟最多创建的 Kubernetes Job 数 |
k8s_runner.ttl_after_finished | 1200 | 完成的 Job 被 Kubernetes 自动删除前保留的秒数 |
k8s_runner.poll_interval | 5 | 周期性对账循环的间隔秒数 |
max_jobs_per_minute防止 Controller 在短时间内创建过多 Job,ttl_after_finished防止已完成的 Job 堆积、压垮 Kubernetes API server。
从源码看,这两个参数的落地机制很明确:
限流采用 60 秒滑动窗口。K8sReconciler用一个deque记录每次成功创建 Job 的时间戳(k8s_reconciler.py):每次创建前,先丢弃 60 秒(常量JOB_CREATION_WINDOW_SECONDS = 60)之前的时间戳,若窗口内计数达到max_jobs_per_minute,则记录日志并延迟本轮创建——rollout 保持QUEUING状态,等下一轮对账再来,不会失败。若创建时遇到 422/Unprocessable/invalid等非法规格错误,rollout 会被直接标记为FAILED并附带错误信息,避免反复重试坏模板。
TTL 直接写入 Job spec。build_job_spec 把ttl_after_finished写进spec.ttlSecondsAfterFinished,同时强制backoffLimit: 0与restartPolicy: Never,保证每个 rollout 只执行一次、失败不重试、完成后由集群自动清理。若 rollout 配置了timeout_seconds,还会写入spec.activeDeadlineSeconds作为 K8s 侧的超时兜底。
双循环架构。K8sReconciler.run()并发运行两个任务(k8s_reconciler.py):
_periodic_reconcile_loop——按poll_interval轮询QUEUING/RUNNING状态的 rollout,与集群中带app.kubernetes.io/managed-by=agentlightning标签的 Job 对账:为缺失的 QUEUING rollout 创建 Job,发现 RUNNING rollout 对应的 Job 消失时标记为FAILED("Job disappeared");_watch_jobs_loop——用kr8s.asyncio.watch监听 Job 的ADDED/MODIFIED事件,Job 出现Complete/Failedcondition 时立即把 rollout 更新为SUCCEEDED/FAILED,watch 中断后自动 5 秒重连。
也就是说,poll_interval影响的是"新 rollout 何时被领取"的延迟,而完成事件主要由 watch 循环低延迟处理。
local_runner:本地进程池的并发与同步
| 键 | 默认值 | 说明 |
|---|---|---|
local_runner.maximum_size | 50 | Controller 机器上并发管理的 Agent 子进程数上限 |
local_runner.poll_interval | 10 | 本地进程与 rollout 状态自动同步检查的间隔秒数 |
当进程池达到maximum_size时,排队的 rollout 会等待容量释放。源码层面(local_reconciler.py)每轮_reconcile_once会:
- 拉取
QUEUING/RUNNING状态、limit 50 的 rollout 列表; - 统计存活子进程数(
returncode is None),仅为QUEUING且未达maximum_size的 rollout 派生子进程; - 发现
RUNNING状态但本地无对应进程时(例如 Controller 重启过),标记FAILED("local subprocess is not running"),保证状态不会悬挂; - 对已超时的子进程发送
SIGKILL整个进程组,并标记FAILED("local subprocess timed out")。
子进程本身以sys.executable -c方式启动一个 worker,start_new_session=True使每个 Agent 处于独立进程组,超时和 Controller 关闭时都能用os.killpg干净地连根终止(local_reconciler.py)。退出码为 0 则 rollout 标记SUCCEEDED,否则FAILED并附带退出码;Controller 收到停止信号时,存活子进程会被终止并标记为FAILED("local controller shutdown"),最后做一次收尾对账。
两种模式下 Agent 是如何被拉起的
k8s 模式:渲染 Jinja Job 模板。Trainer 配置中的agentlightning.k8s.job_template_path指定模板文件(模板写法与示例见 docs/4-trainer-configuration.md 的 "Rollout execution" 小节,仓库中 examples/calc_x/job-template.yaml 是一个可直接参考的完整示例)。模板文本被 Trainer 读入并写入每个 rollout,Controller 侧的build_job_spec(k8s_reconciler.py)负责最终渲染与装配:
- 用 rollout 的
input和确定性job_name(agl-rollout-{rollout_id})渲染 Jinja 模板,模板中可用的过滤器包括yaml_escape(用于把输入安全地写进 YAML 字符串);渲染结果必须是且仅是一个kind: Job的 YAML 文档,否则报错; - 补全
metadata.name/metadata.namespace(来自k8s_runner.namespace)和管理标签app.kubernetes.io/managed-by、agentlightning/rollout-id、agentlightning/attempt-id,供 watch 与对账反查; - 向每个容器注入 rollout 专属的三个环境变量:
AGL_OPENAI_BASE_URL(指向 Gateway 的该 rollout 代理路径,含 train/val mode)、AGL_EVENT_URL(该 rollout 的事件上报端点)和AGL_KEY。若模板里已定义同名变量,则以 Controller 注入值覆盖。
tests/controller/test_k8s_reconciler.py 在无集群环境下验证了上述行为:断言标签集合、AGL_KEY注入,以及AGL_OPENAI_BASE_URL中包含/rollout/test-id/attempt/0/mode/train/路径,可以作为模板正确性的参照。
local 模式:agent_class + env_map。Controller 从 rollout 中读取config.local.agent_class与config.local.env_map:动态导入 Agent 类(支持module.Class或module:Class两种写法),在子进程中实例化并调用run()(兼容同步与协程,见 local_reconciler.py)。env_map把环境变量名映射到 rolloutinput中的字段路径(local_reconciler.py):
- 支持点路径如
input.question,以及列表索引如input.answers.0; - 值为字符串时原样注入,非字符串(如列表、字典)自动
json.dumps后注入,不可序列化时直接报错; - 路径在
input中找不到会抛出local.env_map path not found错误。
例如 Trainer 侧配置:
agentlightning: local: agent_class: examples.search_r1.agents.search_r1_agent.SearchR1Agent env_map: QUESTION: input.question GOLDEN_ANSWERS: input.golden_answersController 就会为每个 rollout 导入SearchR1Agent、启动一个本地子进程,并从该 rollout 的input中取出QUESTION、GOLDEN_ANSWERS环境变量。与 k8s 模式一样,rollout 专属的AGL_OPENAI_BASE_URL、AGL_EVENT_URL、AGL_KEY会自动注入。
适用前提与配置要点小结
- 两种模式共用一套
agl_server连接配置;key需要与 API Gateway(agentlightning/config/server.yaml 中的key字段)以及 Trainer 的agentlightning.agl_key保持完全一致; - k8s 模式要求 Controller 机器可读取
~/.kube/config且对k8s_runner.namespace有 Job 操作权限;local 模式则要求 Controller 机器上能直接导入并运行agent_class,即 Agent 代码及其依赖必须安装在 Controller 环境里; - rollout 的
timeout_seconds(Trainer 配置中的agentlightning.rollout_timeout_seconds,默认 1800)在两种模式下都由 Controller 负责执行:k8s 侧体现为activeDeadlineSeconds,local 侧体现为进程组SIGKILL; - 默认值(Job 限流 100/分钟、TTL 20 分钟、本地并发 50、本地轮询 10 秒)是通用起点,实际训练中可按集群 API server 负载与 Controller 机器 CPU/内存容量,通过 Hydra 命令行参数逐项调整,无需改动 YAML。
参考文件:docs/6-controller-configuration.md、agentlightning/config/controller.yaml、agentlightning/controller/main.py、agentlightning/controller/k8s_reconciler.py、agentlightning/controller/local_reconciler.py、tests/controller/test_k8s_reconciler.py。
【免费下载链接】agent-lightningThe absolute trainer to light up AI agents.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-lightning
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考