# scheduler **Repository Path**: kybs/scheduler ## Basic Information - **Project Name**: scheduler - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-19 - **Last Updated**: 2026-05-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Scheduler — Temporal-native 调度平台 [![Version](https://img.shields.io/badge/version-v1.0.1-blue)](./CHANGELOG.md) [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE) [![Coverage](https://img.shields.io/badge/coverage-root%2051%25%20%2F%20gRPC%2077%25-brightgreen)](./Makefile) 基于 Temporal 的分布式定时任务平台。**单一 gRPC 契约**(`scheduler.v1`,12 个月不破坏)暴露完整的 schedule 生命周期,同一份契约自动落地为 **HTTP/JSON Gateway** 和 **中文 Web UI**。worker 进程独立部署,水平扩展;调度器与 worker 通过 Temporal 串起来,控制面与执行面解耦。 ``` Web UI / curl Operator binaries │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────────────────────────┐ │ HTTP/JSON :8080│ │ cmd/scheduler │ │ + OpenAPI v2 ├──►│ • gRPC ScheduleService │ └──────────────┘ │ • HTTP gateway (grpc-gateway) │ │ • Admin /metrics /healthz /readyz │ │ • Auth interceptor (OIDC + JWT) │ └─────────────┬────────────────────────┘ │ ▼ ┌─────────────────┐ │ Temporal Server │ └────────┬────────┘ │ ▼ ┌──────────────────────┐ │ cmd/worker (× N) │ │ • 运行业务 Task │ │ • IO / Compute / │ │ Mixed 队列分离 │ └──────────────────────┘ ``` --- ## ✨ 核心特性 (v1.0.1) - **冻结契约**:`scheduler.v1` gRPC 契约 12 个月不破坏(ADR-0003 §5.2);buf-breaking gate 在 CI 上守门 - **三套等价入口**:gRPC、HTTP/JSON(gateway 自动转译)、中文 Web UI(Next.js 14 静态导出) - **生产级可观测**:结构化日志(zap)+ OpenTelemetry tracing + Prometheus metrics + `/healthz` `/readyz` - **公示级 SLO**:1000 schedules × 1m × 1h disclosure run,end-to-end P50 20.3s / P99 155.4s(详见 `docs/benchmarks/`) - **认证就位**:OIDC + 自签 JWT 双模式,env-driven 接线,**fail-closed** 启动(配错不会悄悄裸跑) - **安全默认**:loopback 监听、零任务注册、CORS deny — 想暴露要 operator 显式 opt-in - **完整 lifecycle**:CRUD + Trigger + Pause/Unpause + 执行历史 + 单 run 详情(含 Temporal 真实失败消息) --- ## 🚀 快速开始(本地 dev,5 分钟) ### 前置 | 组件 | 版本 | |---|---| | Go | 1.25+ | | Temporal Server | 1.30+(`temporal server start-dev` 即可)| | Node.js | 20+(仅前端编译需要)| ### 步骤 ```bash # 1. 拉代码 git clone https://gitee.com/kybs/scheduler.git cd scheduler # 2. 起 Temporal devserver(另一终端) temporal server start-dev # 3. 编译 + 启动 scheduler make server ./bin/scheduler # 默认监听: # gRPC 127.0.0.1:9090 # HTTP 127.0.0.1:8080 # Admin 127.0.0.1:9464 (metrics + healthz + readyz) # 4. 验证 HTTP gateway curl http://localhost:8080/v1/schedules # {"schedules":[], "nextPageToken":""} ``` 到这里 server 起来了,但**没有注册任何 Task**——这是出厂安全默认,你需要 vendor 这个 binary 并接入自家业务 Task。 --- ## 📦 注册业务 Task `cmd/scheduler/main.go` 和 `cmd/worker/main.go` 都有空体的 `registerOperatorTasks(platform)` 钩子。两种姿态: ### A. 直接改源码(最简单) ```go // cmd/scheduler/main.go 末尾 import "your-company/scheduler-tasks/billing" func registerOperatorTasks(p scheduler.Platform) { p.RegisterTask(billing.NewDailyReport()) p.RegisterTask(billing.NewMonthlyClose()) } ``` `cmd/worker/main.go` 同样的位置贴同一份 —— **两端注册表必须一致**,否则 schedule 触发后 worker 找不到对应 WorkflowType。 ### B. Vendor 二进制(推荐生产) 把 `cmd/scheduler` 复制到自家 repo,在 vendored 副本里注册自家 Task。这样升级时不会被覆盖。 > ⚠️ `examples/` 下有 `shell-command` / `http-request` / `database-operation` 三个示例 Task,**仅作接口实现参考**——它们直接从 `Schedule.args` 取 command/url/SQL 执行,等同 RCE 攻击面,**不要在没有 auth 的公网部署上启用**。 --- ## 🎯 创建 / 触发一个 schedule ```bash # 创建(task_name 必须已经在 worker 上注册过) curl -X POST http://localhost:8080/v1/schedules \ -H "Content-Type: application/json" \ -d '{ "id":"daily-report", "task_name":"billing.daily-report", "cron":"0 0 9 * * *", "timezone":"Asia/Shanghai", "args":{"recipient":"ops@example.com"} }' ``` **Cron 格式**:6 字段含秒,`秒 分 时 日 月 周`。5 字段(标准 Linux cron)和 7 字段(带年份)都会被拒。也支持 `@hourly` / `@daily` 描述符。 **时区**:留空默认 `Asia/Shanghai`(v1.0 起的产品基线决策;想要 UTC 显式设 `"timezone":"UTC"`)。 ```bash # 立即触发一次(不影响定时) curl -X POST http://localhost:8080/v1/schedules/daily-report:trigger # 暂停 / 恢复 curl -X POST http://localhost:8080/v1/schedules/daily-report:pause -d '{"note":"维护中"}' curl -X POST http://localhost:8080/v1/schedules/daily-report:unpause # 改 cron(只有 cron + timezone 可改;task_name / args / description / timeout 是 create-only) curl -X PATCH http://localhost:8080/v1/schedules/daily-report \ -d '{"cron":"0 0 10 * * *","timezone":"Asia/Shanghai"}' # 查执行历史 / 单 run 详情 curl 'http://localhost:8080/v1/schedules/daily-report/executions?pageSize=10' curl 'http://localhost:8080/v1/schedules/daily-report/executions//runs/' # 删除 curl -X DELETE http://localhost:8080/v1/schedules/daily-report ``` --- ## 🏭 生产部署 ```bash # 网络暴露(默认 loopback,对外要显式设) export SCHEDULER_GRPC_ADDR=0.0.0.0:9090 export SCHEDULER_HTTP_ADDR=0.0.0.0:8080 # Temporal 集群 export SCHEDULER_TEMPORAL_HOSTPORT=temporal-frontend.prod.svc:7233 export SCHEDULER_TEMPORAL_NAMESPACE=scheduler-prod # Auth (v1.0.1+, fail-closed 接线) export SCHEDULER_AUTH_ENABLED=true export SCHEDULER_AUTH_DB=/var/lib/scheduler/auth.db export SCHEDULER_OIDC_ISSUERS='[ {"issuer":"https://login.example.com", "audience":"scheduler", "tenant_claim":"https://scheduler.example.com/tenant"} ]' export SCHEDULER_JWT_PUBLIC_KEY="$(cat /etc/scheduler/jwt-pub.pem)" # 可选 # CORS allowlist 给 Web UI export SCHEDULER_ALLOWED_ORIGINS=https://scheduler-ui.example.com /opt/scheduler/bin/scheduler # Worker 单独起(可水平扩展) /opt/scheduler/bin/worker \ -host=temporal-frontend.prod.svc:7233 \ -namespace=scheduler-prod \ -queue=scheduler-queue ``` 完整 env vars 一览见 [docs/CONFIGURATION.md](docs/CONFIGURATION.md) ;部署细节(K8s manifest 模板、反向代理样例)见 [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)。 --- ## 🎨 Web UI ```bash cd web NEXT_PUBLIC_API_BASE=https://scheduler-api.example.com npm run build # 产物 web/out/ 静态导出,nginx / S3 / CF Pages 均可 ``` UI 功能:列表 + 检索 + 按 task_name / enabled 过滤、详情 + 暂停/恢复/触发、执行历史 + 单 run 详情(含 Temporal 真实失败消息)、Create + Edit 表单(task_name registry 校验、cron 提示、Asia/Shanghai 默认时区、编辑模式 disable create-only 字段)。 登录走手动粘贴 OIDC ID token(v1.0 没做自动跳转,M3-OIDC-redirect 是后续)。 --- ## 📊 SLO 公示 dev-class Temporal devserver,1000 × 1m × 1h disclosure run(`bench/slo`): | metric | P50 | P90 | P99 | P999 | |---|---|---|---|---| | **fire_to_activity_start (PRIMARY SLO)** | **20.3s** | **59.6s** | **155.4s** | **208.6s** | | `Manager.Create` (client-side) | 79ms | 106ms | 132ms | 155ms | production-grade Temporal 集群上各 percentile 预期会显著更好(瓶颈在 SQLite WAL 写争抢)。详见 [`docs/benchmarks/2026-05-12-scenario-a.md`](docs/benchmarks/2026-05-12-scenario-a.md)。 跑自己的 substrate: ```bash BENCH_SCHEDULES=1000 BENCH_INTERVAL=1m BENCH_DURATION=1h BENCH_DEV_TAG=0 \ TEMPORAL_HOSTPORT=your-temporal:7233 \ go run ./bench/slo ``` --- ## 🛡️ 安全姿态(v1.0.1 出厂默认) 1. **loopback default** — `SCHEDULER_GRPC_ADDR=127.0.0.1:9090`、`SCHEDULER_HTTP_ADDR=127.0.0.1:8080` 2. **零任务注册** — `examples/` 不被自动接入;operator 必须改 `registerOperatorTasks` 3. **AUTH_ENABLED fail-closed** — 配错(OIDC 不通 / SQLite 不可写)直接拒启 4. **CORS deny by default** — `SCHEDULER_ALLOWED_ORIGINS` 显式 allowlist 或 `SCHEDULER_CORS_DEV=true` dev escape 5. **`UpdateSchedule` immutable 字段双层 pin** — gRPC 层 + Manager 层;防 silent no-op 6. **task_name 必须已注册才能 Create** — 避免创建"永远没人执行"的影子 schedule 7. **v1.0 单租户** — `tenant_id` 字段保留但未 enforce;多租户客户走多实例隔离(M2-T06 触发条件出现时实做) 详见 [docs/grpc-tenant-isolation.md](docs/grpc-tenant-isolation.md) 的 "v1.0 status" 章节 + [CHANGELOG.md](CHANGELOG.md)。 --- ## 📚 文档导航 ### 入门 - [QUICKSTART](docs/QUICKSTART.md) — 详细 quickstart + cron 说明 - [CONFIGURATION](docs/CONFIGURATION.md) — env vars 全集 - [DEPLOYMENT](docs/DEPLOYMENT.md) — 生产部署细节 ### 开发 - [BEST_PRACTICES](docs/BEST_PRACTICES.md) — Task 实现规范 - [API_REFERENCE](docs/API_REFERENCE.md) — gRPC + HTTP API 完整文档 - [TESTING](docs/TESTING.md) — 测试方法 ### 契约 / 错误 - [gRPC Server](docs/grpc-server.md) — 契约 + HTTP 映射 + UpdateSchedule mutable / create-only 矩阵 - [gRPC Errors](docs/grpc-errors.md) — 错误码 + `ErrorInfo.reason` 映射 + 客户端重试策略 - [gRPC Tenant Isolation](docs/grpc-tenant-isolation.md) — M2-T06 多租户设计 + **v1.0 status** ### 运维 - [observability](docs/observability.md) — logs/metrics/tracing/health 入口 - [SLO](docs/SLO.md) — SLO 定义 + 错误预算政策 - [MONITORING](docs/MONITORING.md) — 监控告警配置 - [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) — 排错速查 - [Auth Operator Guide](docs/operations/auth.md) — OIDC trust list / JWT 签名 key 轮换 / service account 生命周期 ### 决策记录 - [ADR-0003](docs/adr/0003-grpc-contract-versioning-strategy.md) — proto in-tree + buf-breaking 门禁 - [ADR-0004](docs/adr/0004-multi-tenant-isolation-granularity.md) — 多租户隔离粒度 - [ADR-0006](docs/adr/0006-authn-idp-strategy.md) — auth/IdP 策略 ### 里程碑 - [M1 closeout](docs/milestones/m1-closeout.md) — Observability + CI baseline - [M2 closeout](docs/milestones/m2-closeout.md) — gRPC 契约 + Web UI + Auth + Audit - [CHANGELOG](CHANGELOG.md) — 版本变更日志 --- ## 🛠️ 开发者入口 ```bash # 全套测试 + 覆盖率门禁(pre-push 钩子也跑这个) make test-cover # → root coverage 50%+ AND internal/grpc coverage 75%+ 才算过 # 普通测试 go test ./... # 前端类型检查 cd web && npx tsc --noEmit # 前端单元测试(vitest) cd web && npm run test:unit # 前端 e2e(playwright,需要 server 跑着) cd web && npm run test:e2e # proto 改完后重生 make proto # 启动开发钩子(pre-commit / pre-push) make install-hooks ``` --- ## 🤝 贡献 1. **Bug**:[gitee.com/kybs/scheduler/issues](https://gitee.com/kybs/scheduler/issues) 2. **Pull Request**:在 PR 描述里说明改动动机 + 测试覆盖;CI 会跑 buf-breaking + coverage + test-unit 3. **新功能**:先在 issue / discussion 对齐范围;scope discipline 见 m2-closeout §8 --- ## 📄 许可证 [MIT License](LICENSE) --- ## 🙏 致谢 - [Temporal](https://temporal.io/) — 工作流编排引擎 - [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) — gRPC ↔ HTTP/JSON 转译 - [shadcn/ui](https://ui.shadcn.com/) + [TanStack Query](https://tanstack.com/query) — Web UI 栈 - 各级 agent 协作开发(详见 commit `Co-Authored-By` trailer)