运行你自己的脚本与工程¶
一行 echo 命令能做的事有限——真实的流水线往往是一个脚本,或者一整个目录的代码和数据文件。本章介绍如何把这些代码作为工程上传到 cronova,并在任务中运行它,每次尝试都会使用一份干净的隔离副本。
为什么需要工程¶
shell 任务的 command 在调度器的工作目录下执行,所以除非 main.py 恰好在那里,python3 main.py 就会失败。你可以硬编码绝对路径、手动把代码部署到调度器旁边——也可以只上传一次工程,让工作流调度器为每次运行自动准备好代码。
创建一个小工程¶
在本机建一个小应用——一个脚本加上一个通过相对路径读取的数据文件:
# my_app/main.py
import os
print("cwd:", os.getcwd())
print("project dir:", os.environ["CRONOVA_PROJECT_DIR"])
with open("data/greeting.txt") as f:
print(f.read().strip(), "on", os.environ["CRONOVA_LOGICAL_DATE"])
相对路径 data/greeting.txt 正是关键所在:脚本假定自己从工程根目录运行,就像在你的笔记本电脑上一样。
在控制台上传¶
打开控制台 http://localhost:8090,编辑一个 DAG,在任务编辑器中打开一个任务——上传功能就在 Project 区域。可以上传单个脚本、整个文件夹,或一个 .zip(自动解压),并给工程起一个名字,比如 my_app。工程名允许字母、数字和 . _ -;上传有大小限制(单文件和整个工程各有上限),并防范路径穿越(path traversal)/ zip-slip 攻击。
验证一下: 通过 REST API 列出已上传的工程:
在任务中引用它¶
用 project 字段让 shell 任务指向该工程。创建 dags/my_app_report.yaml:
dag_id: my_app_report
tasks:
- id: run_main
type: shell
command: python3 main.py # cwd is a clean copy of my_app, so this resolves
project: my_app
触发并观察:
验证一下: 在控制台中打开 my_app_report → 最新一次运行 → run_main。日志显示脚本是从工程的一份全新副本中运行的——位于系统临时目录下的每次尝试独立目录(具体路径因操作系统而异):
cwd: /tmp/cronova-ws-9f8a3c21d4e5/my_app_report__manual_...-run_main
project dir: /tmp/cronova-ws-9f8a3c21d4e5/my_app_report__manual_...-run_main
hello from my_app on 2026-07-07
工程暂存(staging)的工作原理¶
当 shell 任务设置了 project,调度器会在每次尝试前准备好代码:
- 已上传工程的一份全新隔离副本成为本次尝试的工作目录(
cwd)。各次尝试互不干扰,重试永远从干净副本开始——绝不会从失败尝试留下的半成品状态继续。 - 副本的绝对路径通过
CRONOVA_PROJECT_DIR环境变量导出,因此脚本即使cd到别处,也能定位自己捆绑的数据文件。 - 文件权限位会被保留,可执行脚本仍然可执行——
./run.sh可以直接运行。 - 副本位于系统临时目录下,并在尝试结束后被删除。
Warning
工作目录是临时的——它是每次尝试的一次性副本,尝试结束后即被删除。不要把需要持久保存的结果留在 cwd。要么打印到 stdout(会被任务日志捕获),要么写到外部存储:数据库、对象存储,或工作区之外的绝对路径。
更新你的代码¶
改了 main.py?重新上传这个改动的文件即可——上传是增量/覆盖式(upsert)的,无需重传整个文件夹。因为每次尝试都会复制当前工程,改动在下一次运行时生效;已在运行中的尝试继续使用它开始时的那份副本。
Tip
带工程的 shell 任务可以运行主机上的任何语言——Python、Node、Go 或 Rust 二进制、psql、JAR 包。调度器与任务语言完全解耦:上传代码,用对应的解释器调用它,就这么简单。
仅限 shell 任务¶
project 字段只对 shell 任务生效。python、sql 和 http 类型要么在进程内运行,要么有自己的执行模型,暂存工作目录对它们没有意义——下一章会介绍每种类型的用途。
工程存放在哪里¶
上传的工程就是服务器工程目录下的普通目录——默认是 ~/.cronova/projects,可通过 -projects 标志或 CRONOVA_PROJECTS 环境变量覆盖。如果没有配置工程目录,上传功能会被禁用,并且任何引用工程的任务都会在运行时失败。
在运行前发现缺失的工程¶
一个 DAG 引用了从未上传的工程,解析时不会报错——然后在第一次运行时失败。先做校验;dry-run 端点恰好能标记这种问题:
./cronova api POST /api/dags/validate \
'{"dag_id":"my_app_report","tasks":[{"id":"run_main","type":"shell","command":"python3 main.py","project":"ghost"}]}' \
-server http://localhost:8090
验证一下: 响应中 "valid": true,但带有一条警告:
Note
“为什么我的工程任务立刻就失败了?”几乎总是这两个原因之一:工程没有上传,或者服务器没有配置工程目录。POST /api/dags/validate 会在任何东西运行之前把这两种情况都以警告的形式暴露出来。
本章小结¶
- 在控制台的任务编辑器中把脚本、文件夹或
.zip作为命名工程上传,并通过project: my_app挂到 shell 任务上。 - 每次尝试都在工程的一份全新隔离副本中运行(即它的
cwd,同时也在CRONOVA_PROJECT_DIR中);副本是临时的,所以持久化输出要写到 stdout 或外部存储。 - 重新上传在下一次运行时生效;工程目录默认是
~/.cronova/projects(-projects/CRONOVA_PROJECTS)。 POST /api/dags/validate会对引用了未上传工程的情况发出警告——在首次运行前先检查。
下一步:shell 只是五种任务类型之一——看看 python、sql、jar 和 http 任务能做什么。