docker:hermes-cron定时任务沉淀到容器管理

目标与现状

把原来跑在自动化助手自带定时器上的定时任务,沉淀为一个可整体迁移的定时任务容器:一容器多任务,目录即开关,任务高内聚,数据面可整体拷走。设计仓库只放设计与决策,实例目录不进版本管理、走同步工具备份。

现状:容器已拉起,五个任务在跑;其中两条旧链路已停用切容器侧,一条仍与旧链路并行对照,一条因依赖桌面进程与文件锁不容器化、已归档。

前置事实(实测)

事实实测值
运行模式单容器跑全部任务,非每任务一容器
宿主容器引擎用户态 rootless 守护进程,数据根在用户主目录下
宿主余量十余线程、数十 GB 内存、数据盘余数百 GB,文件系统为通用 Linux 文件系统
基础镜像与依赖精简版官方 Python 镜像构建;调度器依赖与行情采集依赖均可从公共软件源直装
构建期网络宿主守护进程注入的回环代理在默认构建网桥内不可达,构建需走宿主网络
通知链路同一容器网络内的代理容器名可达;回环地址与网桥网关地址在容器内均不可达

实现过程

容器与目录结构

实例目录下三件套:容器编排文件、镜像定义、构建入口;加一个数据目录即全部状态。数据目录下分四块:框架层、启用任务、停用归档、运行结果日志,外加跨任务公共数据区。

实例/
  编排文件        数据目录挂载为容器内数据根;设为国内时区;共享外部网络;CPU/内存上限;日志限流;行情直连所需的直连例外
  镜像定义        官方精简 Python 基础镜像 + 时区数据 + 下载工具 + 本地化数据 + 调度器依赖 + 任务依赖
  构建入口        校验框架层声明的版本号格式 → 透传当前环境代理 → 构建
  数据目录/
    框架层        版本声明、依赖清单、调度器、通知配置模板
    启用任务      每个子目录一个任务:定时表达式文件 + 说明 + 代码目录
    停用归档      移入即停用,不加载;含新任务模板
    运行结果日志  每个任务每年一个文件,每次执行一行
    公共数据区    跨任务共享数据,谁产出谁写、全员可读

调度器

框架脚本随数据目录同步,改完重启容器即生效,不必重建镜像。启动时扫描一次启用目录下各任务的定时表达式文件;改规则或搬任务后重启即生效,不做周期重扫。表达式为标准 Linux 格式,命令以该任务目录为工作目录执行,脚本用相对路径即可。到点起独立线程跑,各规则独立计时不重复触发。

任务侧契约:在标准输出里打印 RESULT: 一句话 即进入结果行;多行用分隔符合并。取不到时失败取输出末行、成功记短横。结果行不截断,宁可长不断。

任务清单

任务触发时刻说明
仿真柜台采集每日两个时刻(下午与晚间,晚间取当日结算单)采集结算单与交易快照;与旧链路错开数分钟并行对照,避免同分钟双打柜台前置
行情长表下载每日收盘后按快照口径定合约范围,下载分钟级行情长表;非交易日静默退出
站点地图更新每日下午生成站点地图并经开放接口写入目标笔记;写后回读自证,含对外下载端点逐字节比对
版本发布巡检每日中午巡检十余个上游项目的发布;本地按执行日期记账,渲染多日滚动表写入目标笔记
估值表更新每日早晨(邮件凌晨到达之后)邮箱附件下载加增量解析;入口不读笔记本,直接调本地业务对象;数据与交易日历随数据面走

数据面

任务之间可以互相读数据:公共数据区是唯一共享面,每个数据面只有一个产出者,其余只读。任务目录只放代码加凭据加定时表达式加说明。迁移单位是整个数据目录。数据目录内不放任何软链接(挂载进容器后必断);方向只能是外部建软链接指进来,且指向公共数据区下不带任务名的路径,任务改名不断链。

日志与通知

只有两个去处:运行结果日志(每次执行一行:时间、任务名、成功失败、退出码、耗时、业务结果),同一行推送到指定群话题;容器控制台(任务完整输出逐行实时写这里,带任务名前缀,标准错误并入同一路),看最近各任务在干什么只看这里。手工触发跑在新进程里,输出只到该终端,结果行照常追加。

实测证据

项结果
构建一次通过,镜像内解释器版本与框架层声明一致,调度器与采集依赖均可导入
规则加载启用任务全部列出,停用一条不出现;空启用目录明确提示无可用任务
手动触发单任务只执行一次、只写一行;结果行含业务结果
失败路径失败记失败与退出码;堆栈只在控制台,不进结果行
自动触发分钟级表达式在分钟内真实执行并落行
停用移入停用目录后不再调度(需重启生效一版)
文件权限结果日志按常规权限落盘(调度器启动时设掩码)
实时输出长任务输出逐行实时出现在控制台,多行输出间隔与真实执行间隔一致
通知结果行原文推送到指定群话题,代理走同网络代理容器名,发送期临时摘掉直连例外、发完恢复
站点地图自证写入接口成功且对外下载端点逐字节一致,含上千条地址
版本滚动表自证写后回读表头与行数相符;记账只在版本号变动时追加
估值表全链路无新增无追加时数秒内成功;新邮件到达后下载加解析行数增长;二次执行幂等

管理命令

用途命令
构建(改版本或依赖后才需)构建入口脚本
启动compose up -d
重启(改调度器、定时表达式、任务脚本后)compose restart
列出任务与规则compose exec 容器名 run
手动跑一次某任务compose exec 容器名 run 任务名
手动跑并透传参数compose exec 容器名 run 任务名 -- 参数
手工冒烟不打扰群compose exec -T 容器名 run --no-notify 任务名
看运行时视图compose logs -f
看某任务结果行tail 结果日志目录/任务名-年份.log
停用或启用在启用与停用目录之间整目录移动,改完重启

版本只有一个来源:框架层声明的版本号文件,编排目录下的环境文件是指向它的软链接,日常操作直接用编排命令即可。

踩坑记录

现象成因处置
构建期拉取或安装超时注入的回环代理在默认构建网桥内不可达构建走宿主网络
采集库加载直接崩溃,报本地化构造失败精简镜像本地化数据不全,与语言环境变量无关,只补一种也不够镜像内按宿主同一套生成所需本地化数据
巡检脚本找不到基线文件,写到容器用户主目录下脚本内写死旧数据路径,覆盖了调用方传的环境变量改为环境变量优先、缺省回旧布局;路径一律由调用方显式传
通知报网络不可达为行情直连设的直连例外被标准库的代理绕行逻辑读走,通知也被直连发送期间临时摘掉直连例外变量,发完恢复
通知地址在容器内连不上回环地址与网桥网关地址在容器内都不指向宿主代理用同一容器网络内代理容器的容器名加端口
写入接口返回成功但笔记正文变成固定字面量请求的内容类型头用错改用纯文本内容类型头,正文原样入库;写后必回读自证
任务配置里的变量进不了子进程普通加载方式不导出变量加载时开自动导出
同一任务手工跑一次写两行按规则条数取目标,多表达式任务被跑多遍同命令规则去重,命令不同才各跑一遍
邮箱登录报编码错误配置文件解析器把行内注释读进密码值注释单独成行
任务改名后外部引用全断外部软链接指向含任务名的路径外部软链接只指向公共数据区下不带任务名的路径

不做的事(用户裁定)

  • 不补跑、不记上次执行、不防重叠、不加锁、不做任务级资源限制
  • 框架不管数据滚动、上限、清理、归档,保留策略由任务脚本自己决定
  • 不介入业务幂等,重叠与失败恢复由任务脚本自己的设计负责
  • 容器内无推理与管理链路,笔记维护只做确定性更新:本地模板加本地数据足够成熟时可不读远端直接覆盖,但模板不成立时必须拒绝覆盖;凡需判断或改写结构的要么做成固定模板、要么另找能力入口
  • 完整运行流水不另存文件:控制台滚动由编排文件的日志限流管,历史只看结果行

边界

本笔记只覆盖定时任务容器这一台实例的机制与判据,不覆盖各任务内部业务语义细节(见各任务目录内的说明)。版本解读中需人工判断的部分仍走人工。本次为只读整理与写笔记,未改容器内任何文件。