网络打通之后,下一个问题是:Agent 到底在哪里运行?
写代码时,一个函数调用可以完成很多事情。部署以后,这些事情可能属于不同进程:API 接收请求,Worker 调用模型,沙箱运行生成的分析代码。每个进程都有自己的资源、文件和退出时机,任务也会受到这些边界影响。
这篇继续使用分析 Excel 的例子,把程序从源码变成运行实例的过程整理一下。
本文由 AI 辅助整理。架构、命令及资源配置均用于说明机制,不代表生产环境部署或性能实测。
先把三个执行角色分开
用户上传 Excel 后,API 创建带有 run_id 的任务。Worker 获取任务、准备上下文、调用模型;需要运行分析代码时,再请求隔离沙箱执行对应的 step_id。
| 角色 | 主要职责 | 需要关心的运行条件 |
|---|---|---|
| API | 接收请求、鉴权、查询状态、输出事件 | 连接数、响应时间、流式连接 |
| Worker | 推进任务、调用模型与工具、记录结果 | 执行并发、外部等待、恢复与退出 |
| 沙箱 | 运行分析代码,读写任务工作目录 | CPU、内存、文件权限、网络与时限 |
这些角色可以部署在同一台机器,也可以分别运行在不同平台。先按职责拆开,有助于判断某个资源限制应该加在哪里。
例如模型响应慢,增加沙箱内存未必有帮助;分析大表时进程被终止,则应检查执行代码的沙箱,而不仅是 API 服务。
进程有自己的资源和生命周期
程序启动后形成进程。进程使用内存、打开文件、创建网络连接,也可能启动线程或子进程。协程通常用于组织一个进程中的并发工作,任务最终仍受所在进程和运行平台的生命周期影响。
对 Agent 来说,先观察几个具体问题就很有价值:
- 当前进程在计算,还是等待模型、数据库和工具返回?
- 并发任务是否共享了不该共享的全局变量?
- 数据库连接池最多建立多少连接?
- 浏览器或分析脚本是否留下了未回收的子进程?
- 文件和日志是否持续增长,耗尽了磁盘?
CPU 使用率低,也可能有很多任务在等待。反过来,读取一个压缩文件或展开一张大表,可能瞬间申请大量内存。因此,运行状态应结合任务阶段和资源指标一起看。
下面是观察本机进程与资源的命令片段:
ps -eo pid,ppid,stat,%cpu,%mem,args
free -h
df -h
ss -lntp
它们分别帮助查看进程关系、内存、磁盘和监听端口。容器中看到的资源范围还要结合容器配置解释,不能只看宿主机总量。
Dockerfile、镜像与容器的关系
我会把它们放在三个时间点理解。
写 Dockerfile 时,是在描述如何构建。 例如选择 Python 基础环境,安装依赖,复制代码,定义启动命令。
构建结束后,得到镜像。 镜像保存启动应用所需的文件、依赖和配置模板。镜像内容确定后,修改程序通常需要构建一个新镜像。
启动镜像后,得到容器。 容器是运行实例,同一个镜像可以启动多个实例。容器运行时产生的文件写入可写层或挂载存储,不会回写到原镜像。Docker 镜像说明
镜像标签还需要单独理解。agent-worker:v1 是一个引用名称,标签可以被重新指向其他内容。为了追溯发布,应记录构建对应的代码提交与镜像 digest,避免只知道“运行的是 latest”。
在 Excel 场景中,API 镜像主要包含 Web 服务依赖,Worker 镜像包含任务执行逻辑,沙箱镜像则包含分析工具和运行环境。可以根据实际需求复用基础层,不必把所有依赖都装进每个镜像。
从代码仓库到运行平台
将 Dockerfile、云效、ACR 和 FC 放到同一条交付流程里,可以这样理解:
- 代码仓库保存源码、依赖声明和 Dockerfile。
- 云效或其他 CI 获取指定提交,执行检查并构建镜像。
- 将镜像推送到 ACR。
- FC 或 K8s 拉取确定版本的镜像,并注入运行配置。
- 启动后检查服务,再接入任务或流量。
这里,ACR 承担镜像的保存与分发;OSS 用来保存输入文件、报告和其他对象。即使把 Dockerfile 放进 OSS,它仍然只是构建说明,必须经过构建才会产生可部署的镜像。
运行配置也应与镜像内容区分。数据库地址、模型地址、凭证等可以通过平台配置、挂载或受控凭证机制提供。这样,同一份构建产物能用于不同环境,敏感信息也不必写进镜像层。
另外要核对目标平台要求:CPU 架构、启动命令、运行用户、监听地址和端口。阿里云 FC 的 Web Server 自定义镜像模式要求服务监听 0.0.0.0:配置端口 或相应通配地址;只监听回环地址会让平台无法正常连接。FC 自定义镜像说明
文件的寿命不等于任务的寿命
沙箱分析 Excel 时,会产生输入副本、临时数据、图片和最终报告。我会按恢复需求区分它们:
| 文件或数据 | 保存方式 | 失去当前实例后怎么办 |
|---|---|---|
| 原始 Excel | OSS 等持久对象存储 | 按任务中的对象引用重新读取 |
| 中间计算文件 | 沙箱工作目录或临时磁盘 | 能重建则重新计算,不能重建则及时持久化 |
| 最终报告 | OSS 等持久对象存储 | 根据权限和对象引用继续访问 |
| 任务状态与步骤结果 | 持久数据库 | 用于判断完成情况与恢复位置 |
容器可写层随容器删除而丢失,挂载卷可以把数据生命周期与容器分开。阿里云 FC 也明确说明,自定义镜像的可写层会随实例销毁而删除,需要持久化的数据应使用外部存储。Docker 文件挂载 · FC 存储边界
“已生成报告”最好包含可核验的对象引用、大小或校验信息。上传还没完成就把任务标为成功,用户可能拿到一个不存在的文件。
挂载本身也不等于备份。共享目录仍然需要访问权限和清理策略,实例能重新挂上卷,也不代表误删除的数据能够恢复。
资源限制应该加在实际消耗资源的地方
普通容器通过操作系统机制组织隔离和资源控制。运行时可以限制内存、CPU 等资源,但需要明确配置与行为。Docker 资源约束
比如一个任务启动多个浏览器页面,或者分析代码持续创建大数组,首先受到影响的是沙箱。API 和 Worker 的资源限制不能代替沙箱自己的限制。
我会分别考虑执行时长、内存、CPU、临时磁盘、进程数和输出体积。还需要限制单个用户同时占用多少沙箱,避免一个任务创建大量执行实例。
看到进程退出时,应结合退出原因、平台事件和日志判断。某些退出码只说明收到特定信号,不能单凭一个数字就认定发生了 OOM。资源指标与任务阶段放在一起,才能知道应该调整配额、修复代码,还是降低并发。
发布时,正在执行的任务怎样退出
API 可以停止接收新请求,Worker 则还需要处理已经领取的任务。
K8s 等平台会为正常终止提供宽限过程,应用通常先收到终止信号,期限到达后仍未退出的进程可能被强制结束。Pod 终止流程
Worker 应利用这个窗口:停止领取新任务;完成能及时结束的工作,或保存恢复所需状态;按队列协议处理确认与租约;最后关闭连接和相关子进程。
容器启动命令也会影响信号能否到达真正的程序。若使用 shell 包装,应理解它如何转发信号、回收子进程。容器内的主进程需要承担相应的生命周期责任。Docker 运行命令
不过,优雅退出只能覆盖正常停止的一部分情况。机器故障、OOM 或强制终止可能不给清理机会。持久检查点和幂等仍然是恢复基础,这部分会在长任务可靠执行中继续展开。
沙箱需要独立的权限边界
普通容器通常共享宿主机内核。它能提供进程和文件系统等隔离机制,但不应直接推导成“可以安全运行任意用户代码”。K8s Namespace 也需要结合权限、网络和存储策略,才能形成相应的租户隔离。Kubernetes 多租户说明
对于模型生成的分析代码,我会把授权限制到当前任务:只提供本次 Excel 和工作目录,按需要授予上传报告的能力,限制网络出口,并避免暴露宿主机管理接口或与任务无关的凭证。
隔离要求更高时,可以评估 gVisor、虚拟机类运行环境或托管沙箱。它们有各自的性能、兼容性和隔离边界,需要结合代码可信程度选择。gVisor 通过用户态应用内核处理工作负载的系统调用,是一种增加运行时隔离边界的方式。gVisor 安全架构
第一轮验证可以用同一个分析任务完成三件事:删除运行实例后,仍能找到已经保存的报告;终止 Worker 后,能明确知道哪些步骤完成、哪些需要恢复;让沙箱访问任务范围之外的文件和服务时,权限控制确实生效。这样才能看清程序、文件和权限各自的生命周期。
参考资料
上一篇:从一次请求理解网络 · 下一篇:K8s 与 Serverless