Agent Runtime 通过 Kubernetes API 运行第一个工作负载

如果你正在开发代码助手或数据分析 Agent,通常需要为每个任务准备独立的执行环境,使其能够接收用户文件、运行脚本、保存结果,并在后续任务中继续使用这些文件。如果你的应用已使用 Kubernetes SDK、kubectl 或工作流编排器,可以通过 Kubernetes API 完成这些操作。

相关产品
Agent Runtime 产品页,含版本规格、价格与购买入口。

这篇以“数据分析 Agent 汇总订单文件”为例,完成一次可复现的任务:

1. 创建 Pod 作为脚本执行环境,挂载工作区保存订单和结果。

2. 统计已支付订单数量与金额,记录环境启动耗时。

3. 结束原 Pod,在另一节点创建新 Pod,接着处理原来的文件。

本例提供固定数据和脚本,无需配置模型或调用外部业务系统。实际接入 Agent 时,可将这些操作分别放到应用的“创建环境、上传文件、执行代码、读取结果、释放环境”流程中。kubectl 在这里作为 Kubernetes API 客户端使用。

一、开始前准备

你需要一个已创建的 Agent 集群,以及可访问集群的 kubeconfig。本机安装 kubectl 和 Python 3;操作账号需要查询节点、RuntimeClass 和 StorageClass,创建及删除测试命名空间,管理其中的 Pod/PVC,以及执行 pods/exec 的权限。

先配置访问,并查看运行环境:

export KUBECONFIG='/absolute/path/to/your/agc-kubeconfig'kubectl config current-contextkubectl get nodeskubectl get runtimeclasskubectl get storageclass

本例使用以下配置。将值替换为自己集群中的名称,并在同一个终端依次执行后续命令:

export AGC_DEMO_NS="agc-orders-$(date +%Y%m%d%H%M%S)"export AGC_RUNTIME_CLASS='cube'export AGC_STORAGE_CLASS='awv-btrfs'export AGC_DEMO_IMAGE='busybox:1.36.1'kubectl create namespace "$AGC_DEMO_NS"

配置 如何选择
RuntimeClass 选择 Agent 集群中用于运行任务的运行时;本例填写 cube。
StorageClass 选择支持跨节点重新挂载、ReadWriteOncePod 和 fsGroup: 1000 的工作区存储类;本例填写 awv-btrfs。
执行镜像 本例使用 BusyBox 的 Shell、awk 和校验工具;节点需要能够拉取此镜像,也可替换为包含相同工具的企业镜像。
节点 先有一个可调度且支持所选运行时和存储的 Ready 节点;跨节点继续任务还需要第二个这样的节点。

每次实验使用独立命名空间,避免与已有任务重名。下面的 Pod 只运行本例脚本,工作文件保存在 PVC 挂载的 /workspace 中。

二、第一步:准备工作区和执行环境

将以下命令复制到终端,生成工作区配置:

cat > orders-pvc.yaml <<EOFapiVersion: v1kind: PersistentVolumeClaimmetadata: name: orders-workspace namespace: ${AGC_DEMO_NS}spec: storageClassName: ${AGC_STORAGE_CLASS} volumeMode: Filesystem accessModes: [ReadWriteOncePod] resources: requests: storage: 1GiEOF

生成 Pod 配置。Pod 启动后保持运行,便于应用通过 exec 提交任务:

cat > orders-pod.yaml <<EOFapiVersion: v1kind: Podmetadata: name: orders-runner namespace: ${AGC_DEMO_NS}spec: runtimeClassName: ${AGC_RUNTIME_CLASS} restartPolicy: Never automountServiceAccountToken: false securityContext: fsGroup: 1000 containers: – name: runner image: ${AGC_DEMO_IMAGE} imagePullPolicy: IfNotPresent command: ["/bin/sh", "-c", "sleep 86400"] securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 allowPrivilegeEscalation: false capabilities: drop: ["ALL"] seccompProfile: type: RuntimeDefault resources: requests: cpu: 100m memory: 64Mi limits: cpu: 500m memory: 128Mi volumeMounts: – name: workspace mountPath: /workspace volumes: – name: workspace persistentVolumeClaim: claimName: orders-workspaceEOFkubectl create –dry-run=server -f orders-pvc.yamlkubectl create –dry-run=server -f orders-pod.yamlkubectl create -f orders-pvc.yaml

使用 WaitForFirstConsumer 的存储类时,PVC 此时显示 Pending 是正常的;创建 Pod 后才会开始绑定。请勿在创建 Pod 前一直等待 PVC Bound。

三、第二步:启动环境并记录等待时间

对业务应用而言,需要等待的是“可以执行代码且工作区可以写入”。将下面脚本保存为 measure-start.py,测量从本机发起创建到确认工作区可写的耗时:

import jsonimport osimport subprocessimport sysimport timenamespace = os.environ['AGC_DEMO_NS']def kubectl(*args): return subprocess.check_output( ['kubectl', '-n', namespace, *args], text=True, timeout=210 )started = time.perf_counter()created = json.loads(kubectl('create', '-f', sys.argv[1], '-o', 'json'))name = created['metadata']['name']print('pod:', name, file=sys.stderr, flush=True)kubectl('wait', '–for=condition=Ready', 'pod/' + name, '–timeout=180s')kubectl('exec', name, '-c', 'runner', '–', '/bin/sh', '-ec', 'echo ready > /workspace/.startup-check; cat /workspace/.startup-check; ' 'rm /workspace/.startup-check')elapsed = time.perf_counter() – startedpod = json.loads(kubectl('get', 'pod', name, '-o', 'json'))container = next(c for c in pod['status']['containerStatuses'] if c['name'] == 'runner')print(json.dumps({ 'namespace': namespace, 'pod': name, 'node': pod['spec']['nodeName'], 'workspace_ready_seconds': round(elapsed, 3), 'image_id': container.get('imageID'), 'restart_count': container['restartCount'],}, ensure_ascii=False, indent=2))

执行脚本:

python3 measure-start.py orders-pod.yaml > startup-a.jsoncat startup-a.json

成功时会输出 Pod 名称、所在节点、workspace_ready_seconds 和镜像信息。该耗时包含客户端请求、调度、镜像准备、工作区挂载以及 exec 检查;它不是单独的容器启动耗时,也不包含后面的订单统计时间。

若 180 秒内未就绪,脚本会失败并保留 Pod,请参见文末排查方法查看原因,请勿重复创建同名资源。

四、第三步:上传订单并生成统计结果

下面模拟 Agent 接收一份订单文件,并将准备好的分析脚本放到工作区。只有 paid 订单计入统计,金额单位为元:

kubectl -n "$AGC_DEMO_NS" exec -i orders-runner -c runner — /bin/sh -s <<'SCRIPT'set -eucat > /workspace/orders.csv <<'CSV'order_id,status,amountA001,paid,100A002,paid,50A003,cancelled,80CSVcat > /workspace/summarize.sh <<'SH'#!/bin/shset -euawk -F, 'NR > 1 && $2 == "paid" { count++; amount += $3 }END { printf "paid_orders=%d\\npaid_amount=%d\\n", count, amount }' \\ /workspace/orders.csv > /workspace/summary.txtSH/bin/sh /workspace/summarize.shsynccat /workspace/summary.txtSCRIPT

预期输出:

paid_orders=2paid_amount=150

将本次结果和文件校验值保存到本机:

kubectl -n "$AGC_DEMO_NS" exec orders-runner -c runner — cat /workspace/summary.txt > summary-a.txtkubectl -n "$AGC_DEMO_NS" exec orders-runner -c runner — \\ sha256sum /workspace/orders.csv /workspace/summarize.sh /workspace/summary.txt > files-a.sha256 || { echo "读取校验值失败,停止实验" >&2; exit 1; }test -s files-a.sha256 || { echo "未取得文件校验值,停止实验" >&2; exit 1; }cat summary-a.txt

至此,你已经完成一个可用的最小任务:创建环境、提交文件和脚本、执行分析、取回结果。如果只需体验第一次执行,可以直接跳到清理步骤。

五、第四步:换一个节点继续任务

假设上一轮分析结束后,你释放执行环境;后来用户追加订单,需要再次创建环境继续处理原文件。下面主动选择另一个节点,验证工作区能否随任务重新挂载。

先查看当前 Pod 的节点,以及可用节点的 hostname 标签:

kubectl -n "$AGC_DEMO_NS" get pod orders-runner -o widekubectl get nodes -L kubernetes.io/hostnamekubectl get runtimeclass "$AGC_RUNTIME_CLASS" -o yaml

选择一个不同于当前节点、状态 Ready 且未标记 SchedulingDisabled 的节点。目标节点必须满足所选 RuntimeClass 的调度条件,并在工作区存储的挂载范围内。将它的 kubernetes.io/hostname 标签值填入:

export AGC_TARGET_HOSTNAME='REPLACE_WITH_NODE_B_HOSTNAME'python3 – <<'PY'import jsonimport osimport subprocesspod = json.loads(subprocess.check_output( ['kubectl', 'create', '–dry-run=client', '-f', 'orders-pod.yaml', '-o', 'json'], text=True))pod['metadata']['name'] = 'orders-runner-b'pod['spec']['nodeSelector'] = {'kubernetes.io/hostname': os.environ['AGC_TARGET_HOSTNAME']}with open('orders-pod-b.json', 'w') as f: json.dump(pod, f, indent=2)PYkubectl create –dry-run=server -f orders-pod-b.json

仅释放原 Pod,保留 orders-workspace PVC,然后启动新 Pod:

kubectl -n "$AGC_DEMO_NS" delete pod orders-runner –wait=true –timeout=180skubectl -n "$AGC_DEMO_NS" get pvc orders-workspacepython3 measure-start.py orders-pod-b.json > startup-b.jsoncat startup-b.json

这里是“停止原环境,再挂载工作区启动新环境”,不是正在执行的进程无中断迁移。ReadWriteOncePod 限定同一时刻由一个 Pod 使用此工作区,因此需要先结束原 Pod。

六、第五步:校验原文件并处理追加订单

新 Pod 不重新上传文件,先检查原来的数据、脚本和结果是否一致:

kubectl -n "$AGC_DEMO_NS" exec orders-runner-b -c runner — \\ sha256sum /workspace/orders.csv /workspace/summarize.sh /workspace/summary.txt > files-b.sha256 || { echo "读取校验值失败,停止实验" >&2; exit 1; }test -s files-a.sha256 && test -s files-b.sha256 && diff files-a.sha256 files-b.sha256 || { echo "文件校验失败,停止实验" >&2; exit 1; }

diff 无输出且退出码为 0,说明三个文件的校验值一致。如果不一致,停止后续写入并检查工作区。校验通过后,追加一条 80 元的已支付订单,再执行原脚本:

kubectl -n "$AGC_DEMO_NS" exec orders-runner-b -c runner — /bin/sh -ec 'printf "%s\\n" "A004,paid,80" >> /workspace/orders.csv/bin/sh /workspace/summarize.shsynccat /workspace/summary.txt' > summary-b.txtcat summary-b.txt

预期输出为 paid_orders=3、paid_amount=230。这说明新环境能够继续读取、修改原文件并执行脚本,不需要重新上传上一轮的工作内容。追加订单这一步只执行一次,重复执行会重复计入该订单。

七、核对本次体验结果

核对项 预期结果
首轮分析 2 笔已支付订单,合计 150 元,已取消订单不计入。
节点变化 startup-a.json 与 startup-b.json 中的 node 不同。
文件保留 三个文件迁移前后的 SHA-256 一致。
继续分析 追加订单后为 3 笔,合计 230 元。
环境等待时间 两份记录均有 workspace_ready_seconds,容器 restart_count 为 0。

两次耗时只反映这次体验。首次拉取镜像、节点缓存、卷创建和重新挂载都会影响结果,不能直接将两次耗时差解释为加速比例。需要评估并发或性能时,固定镜像、资源规格及缓存条件,单独设计重复采样实验。

八、清理测试资源

先保留本机的统计结果、启动记录和校验文件,再删除本次测试 Pod:

kubectl -n "$AGC_DEMO_NS" delete pod orders-runner orders-runner-b –ignore-not-found –wait=true –timeout=180s

如果后续还要使用订单文件,保留 PVC。确认测试数据不再需要时,再删除 PVC 和本次独立命名空间:

kubectl -n "$AGC_DEMO_NS" delete pvc orders-workspace –wait=true –timeout=180skubectl delete namespace "$AGC_DEMO_NS" –wait=true –timeout=180s

删除 PVC 后是否删除后端数据,取决于 StorageClass 的回收策略。使用 Retain 策略时,还需按存储管理流程处理保留卷。

九、遇到问题时

kubectl -n "$AGC_DEMO_NS" get pods,pvc -o widekubectl -n "$AGC_DEMO_NS" get events –sort-by=.metadata.creationTimestamp

现象 排查方法
Forbidden 核对 kubeconfig 身份及命名空间、Pod、PVC、pods/exec 权限。
Pod Pending 查看调度事件,检查 RuntimeClass 条件、目标节点、资源余量和 PVC 绑定状态。
ImagePullBackOff 检查节点到镜像仓库的网络及镜像权限;私有镜像需配置拉取凭证。
挂载失败或提示卷仍被占用 检查存储组件和原 Pod 的卸载情况,不强制解绑仍在使用的卷。
工作区 Permission denied 检查存储类的 fsGroup 支持及卷目录权限;本例使用 UID/GID 1000。
第二个节点始终无法启动 检查节点是否满足运行时条件、是否安装存储组件,以及卷是否支持在该节点挂载。
校验不一致或统计结果不符 先停止追加数据,核对 PVC 名称、挂载路径及订单追加步骤是否重复执行。

准备接入你自己的 Agent 应用时,可以将本例脚本替换为业务任务,沿用“每次任务创建执行环境、同一工作过程复用工作区、完成后回收计算资源”的流程。

赞 (0)
上一篇 4天前
下一篇 4天前