轻量的定时任务工具 Cronicle:前篇

本篇文章将介绍一款轻量的、自带简洁 Web UI,适用于中小团队以及个人的定时任务工具:Cronicle。

本文是关于 Cronicle 的第一篇文章,主要聊聊这个软件在容器封装下的常见问题,以及容器封装思路。

写在前面

Cronicle 自 2016 年正式开始开源,到现在已经过去了五年多了。而我第一次注意到这款软件则是在 2018 年,当时我正在为我的 HomeLab 挑选合适的定时任务工具。在过去的几年里,可以看到软件一直在细节功能上优化,目前已经做的已经比较完善了,尤其是近两年,基本没有功能上特别大的改版和变更发生。

软件除了支持基础的定时任务之外,还包含了非常多有用的功能:

  • 支持多实例搭建分布式定时任务系统
  • 具备故障自愈和服务自动迁移能力
  • 支持服务发现、以及具备自动组网的能力
  • 允许实时查看任务执行状况
  • 具备基础的插件系统,支持使用任意语言和方式来扩展能力
  • 可以针对不同时区创建定时任务
  • 可以针对降级执行时间比较长的任务做排队处理
  • 基础的任务性能图标和统计数据
  • 具备开放的 API、支持应用 API 密钥
  • 具备 Web Hook 通知能力

如果你只将它作为任务触发器使用,它的内存资源消耗将会非常小,在我重新封装的镜像中,运行超过20个小时的程序,面板展示内存使用仅 80MB 出头,而 docker stats 的结果,则连 50 MB 都不到。

轻量的资源消耗

CONTAINER ID   NAME                                    CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O    PIDS
ec4d5ab18b68   docker_cronicle_1                       5.56%     46.09MiB / 15.64GiB   0.29%     4.71MB / 20.6MB   0B / 0B      11

相比较使用传统的方式运行任务,和多数软件一样,它自带了任务执行列表,默认最低精度是 10秒,足够多数场景下的使用。

历史任务执行列表

同时,也支持针对任务输出简单的统计摘要。

简单的统计摘要

为了能够更好的使用它,我们需要对它先进行容器化封装。

如果你已经迫不及待的想开始使用它,可以跳转至至下一个章节 “在容器中使用 Cronicle”。

使用容器封装 Cronicle

第一次使用容器封装这个软件,应该是在 2018 年这个 PR 前后。随后虽然也有不少网友对这个软件进行了封装,但是都有一些不完善的地方,比如:如果你将容器进行了迁移或重建,软件将会无法运行,除非你手动进行修复;比如首次使用的时候,需要等待至少一分钟的时间才能够让软件自己组网成功,然后才可以开始使用

所以,这篇文章里,我们就先来解决这两个问题吧。

减少 Cornicle 启动等待时间

jhuckaby/Cronicle/blob/master/lib/engine.js 中,有一个名为 startup 的函数,大概有一百多行,其中记录了 Cronicle 启动后需要做的事情,造成我们需要等待 60 秒才能够使用软件的逻辑主要是这部分:

...

startup: function(callback) {
    // start cronicle service
    var self = this;
    this.logDebug(3, "Cronicle engine starting up");

    // create a few extra dirs we'll need

    ...

    // archive logs daily at midnight
    this.server.on('day', function () {
        self.archiveLogs();
    });

    // determine master server eligibility
    this.checkMasterEligibility(function () {
        // master mode (CLI option) -- force us to become master right away
        if (self.server.config.get('master') && self.multi.eligible) self.goMaster();

        // reset the failover counter
        self.multi.lastPingReceived = Tools.timeNow(true);

        // startup complete
        callback();
    });
}
...

因为软件默认运行环境假设是多机的分布式环境,所以有一个比较长时间的服务发现和注册的过程。

而在本篇文章中,我们主要以单机模式运行,所以我们可以对它进行一些细微的改造,让 self.goMaster(); 这个注册当前运行实例的动作在 this.checkMasterEligibility 前执行就可以了。

考虑到 Cronicle 团队接收 PR 的时间比较漫长,为了快速实现这个功能,可以采取自制补丁,并在容器构建过程中进行“补丁应用”的方式来实现。

对程序进行适当调整之后,执行 diff -u lib/engine.js /tmp/engine.js > engine.patch 就可以轻松创建一个类似下面内容的程序补丁啦。

--- lib/engine.js	2021-06-17 19:03:36.000000000 +0000
+++ /tmp/engine.js	2021-12-04 09:50:13.000000000 +0000
@@ -152,7 +152,8 @@
 		this.server.on('day', function() {
 			self.archiveLogs();
 		} );
-		
+		// for docker env
+		self.goMaster();
 		// determine master server eligibility
 		this.checkMasterEligibility( function() {
 			// master mode (CLI option) -- force us to become master right away

而要应用补丁也很简单,只需要执行 patch -p3 < engine.patch lib/engine.js 即可。

我们先将补丁文件保存好,稍后再使用。

Cronicle 在容器中运行的其他常见问题

想要正常的运行 Cronicle ,默认情况下需要执行三条命令:

/opt/cronicle/bin/build.js dist
/opt/cronicle/bin/control.sh setup
/opt/cronicle/bin/control.sh start

前两条命令中包含了程序启动和运行过程中依赖的目录结构,以及包含了当前运行环境信息,并将其中一些信息以配置的形式进行了持久化保存。而如果我们重新创建容器环境,容器的网络、主机名都有可能产生变化,这也是为什么如果我们进行运行环境迁移,很容易遇到程序无法正常工作,需要重新部署配置程序的原因。

而第三条命令中,则是以 Daemon 的方式启动程序,因此以往有一些 Cronicle 的容器封装者会使用类似 tini 之类的程序,来完成容器封装。但其实,如果我们将容器直接以前台方式运行,就不需要这些额外的程序来做僵尸进程捕获和系统信号转发了。

当这三条命令执行完毕,软件运行所需要的目录、配置将自动初始化完毕,然后软件将运行在系统后台。

如果包含了程序的容器在运行过程中出现异常中断,软件运行时创建的 PID 文件并不会“销毁”,这同样会导致程序无法重新运行起来。

所以,为了避免和解决上面的问题,以及改进使用体验,我们需要额外的写一个小程序。

编写适合容器内使用的启动脚本

上面清楚的提到了容易发生的问题,以及问题的根源,所以编写一个用来解决这些问题的程序,也就很简单了:

#!/usr/bin/env node

const { existsSync, unlinkSync } = require('fs');
const { dirname } = require('path');
const { hostname, networkInterfaces } = require('os');
const StandaloneStorage = require('pixl-server-storage/standalone');

if (existsSync("./logs/cronicled.pid")) unlinkSync("./logs/cronicled.pid");

process.chdir(dirname(__dirname));

const config = require('../conf/config.json');

const storage = new StandaloneStorage(config.Storage, function (err) {
    if (err) throw err;

    const dockerHostName = (process.env['HOSTNAME'] || process.env['HOST'] || hostname()).toLowerCase();

    const networks = networkInterfaces();
    const [ip] = Object.keys(networks).
        filter(eth => networks[eth].
            filter(addr => addr.internal === false && addr.family === "IPv4").length).
        map(eth => networks[eth])[0];

    const data = {
        "type": "list_page",
        "items": [{ "hostname": dockerHostName, "ip": ip.address }]
    };

    const key = "global/servers/0";
    storage.put(key, data, function () {
        storage.shutdown(function () {
            console.log("Record successfully saved: " + key + "\n");
            storage.get(key, function (_, data) {
                if (storage.isBinaryKey(key)) {
                    console.log(data.toString() + "\n");
                } else {
                    console.log(((typeof (data) == 'object') ? JSON.stringify(data, null, "\t") : data) + "\n")
                }
                storage.shutdown(function () {
                    console.log("Docker Env Fixed.");
                    require('../lib/main.js');
                });
            });
        });
    });
});

上面不到五十行代码主要做了几件事情:

  • 检测是否有之前运行程序遗留下来的 PID 文件,如果有,则清理掉,避免影响程序启动。
  • 将目前实际运行的容器环境中的 IP、主机名更新到程序配置中,避免程序不能正确启动。
  • 以前台的方式运行程序,避免再经手其他程序,保证容器足够简单。

编写容器镜像文件

这里因为 Cronicle 实际运行会使用到 shell,所以不推荐使用之前 《使用以语言为中心的容器基础镜像 distroless》 一文中提到的方式进行最小化镜像构建,仅使用普通的二阶段构建即可:

FROM node:16 AS Builder
ENV CRONICLE_VERSION=0.8.62
WORKDIR /opt/cronicle
COPY Cronicle-${CRONICLE_VERSION}.tar.gz /tmp/
RUN tar zxvf /tmp/Cronicle-${CRONICLE_VERSION}.tar.gz -C /tmp/ && \
    mv /tmp/Cronicle-${CRONICLE_VERSION}/* . && \
    rm -rf /tmp/* && \
    npm install --registry=https://registry.npm.taobao.org
COPY ./patches /tmp/patches
RUN patch -p3 < /tmp/patches/engine.patch lib/engine.js


FROM node:16-alpine
COPY --from=builder /opt/cronicle/ /opt/cronicle/
WORKDIR /opt/cronicle

ENV CRONICLE_foreground=1
ENV CRONICLE_echo=1
ENV CRONICLE_color=1
ENV debug_level=1

ENV HOSTNAME=main-server

RUN node bin/build.js dist && \
    bin/control.sh setup
COPY docker-entrypoint.js ./bin/
CMD ["node", "bin/docker-entrypoint.js"]

因为即使是普通的二阶段构建,和基础镜像切换,也能够将软件的镜像体积由 1G 降低到 150M 不到,更加适合分发和保存。

cronicle                                      latest                              c0575a5b900b   22 hours ago    1.04GB
cronicle                                      latest                              e31626eac385   3 seconds ago        146MB

在容器中使用 Cronicle

想要让 Cronicle 快速运行起来,可以使用我预构建好的容器镜像,为了让这个镜像能够正常运行起来,我们需要两个编排文件,分别用于程序“初始化”和“正常运行”,先来编写正常运行的文件:

version: "3.6"

services:

  cronicle:
    image: soulteary/cronicle:0.8.62
    restart: always
    hostname: cronicle
    ports:
      - 3012:3012
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./data/data:/opt/cronicle/data
      - ./data/logs:/opt/cronicle/logs
      - ./data/plugins:/opt/cronicle/plugins
    extra_hosts:
      - "cronicle.lab.io:0.0.0.0"
    environment:
      - TZ=Asia/Shanghai
    healthcheck:
      test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:3012/api/app/ping || exit 1"]
      interval: 5s
      timeout: 1s
      retries: 3
    logging:
        driver: "json-file"
        options:
            max-size: "10m"

将上面的文件保存为 docker-compose.yml 后,继续来编写初始化运行的配置:

version: "3.6"

services:

  cronicle:
    image: soulteary/cronicle:0.8.62
    hostname: cronicle
    command: /opt/cronicle/bin/control.sh setup
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./data/data:/opt/cronicle/data
      - ./data/logs:/opt/cronicle/logs
      - ./data/plugins:/opt/cronicle/plugins
    environment:
      - TZ=Asia/Shanghai

将上面的文件保存为 docker-compose.init.yml,然后执行 docker-compose -f docker-compose.init.yml up,不出意外,你将会得到类似下面的内容:

cronicle_1  | 
cronicle_1  | Setup completed successfully!
cronicle_1  | This server (main) has been added as the single primary master server.
cronicle_1  | An administrator account has been created with username 'admin' and password 'admin'.
cronicle_1  | You should now be able to start the service by typing: '/opt/cronicle/bin/control.sh start'
cronicle_1  | Then, the web interface should be available at: http://main:3012/
cronicle_1  | Please allow for up to 60 seconds for the server to become master.
cronicle_1  | 
docker-cronicle_cronicle_1 exited with code 0

接着再使用 docker-compose up -d 启动服务即可,大概几秒钟后,使用 docker-compose ps 检查服务,就能够看到服务运行正常的结果了。

           Name                         Command                  State               Ports         
---------------------------------------------------------------------------------------------------
docker-cronicle_cronicle_1   docker-entrypoint.sh node  ...   Up (healthy)   0.0.0.0:3012->3012/tcp

此时,我们在浏览器中打开 localhost:3012 就能够开始使用软件啦,软件的默认账号和密码都是 admin

软件默认界面

因为软件功能界面非常直观,这里就不多针对软件的基础使用进行赘述啦。

最后

关于分布式使用、容器内灾备转移、插件编写,或许适合在下一篇关于 Cronicle 的文章中展开。文中相关代码我已经上传至 GitHub ,有需要的小伙伴可以自取。

谨以此文献给刚刚创建的技术讨论群中的小伙伴,权作抛砖引玉。

–EOF


我们有一个小小的折腾群,里面聚集了几百位喜欢折腾的小伙伴。

在不发广告的情况下,我们在里面会一起聊聊软硬件、HomeLab、编程上的一些问题,也会在群里不定期的分享一些技术沙龙的资料。

喜欢折腾的小伙伴欢迎扫码添加好友。(添加好友,请备注实名,注明来源和目的,否则不会通过审核)

关于折腾群入群的那些事


如果你觉得内容还算实用,欢迎点赞分享给你的朋友,在此谢过。


本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2021年12月05日
统计字数: 4412字
阅读时间: 9分钟阅读
本文链接: https://soulteary.com/2021/12/05/cronicle-a-lightweight-tool-for-timed-tasks-part-1.html

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一款定时/循环执行任务的绿色小软件,可以用来定时执行程序、DOS命令、从HTTP服务器下载程序运行、结束进程、 模拟按键、发送EMAIL、网络唤醒、消息提示、关机、重启、注销、锁定、待机等操作,都是本人曾经所需要的功能!   合理使用和搭配软件的各功能,可做更多事情!可供对系统较了解的人自由发挥。 提示: a. 显示主窗口热键Ctrl+F1(可在设置里自定义) b. OnTimer.exe /h 启动隐藏,但不隐藏托盘图标除非使用OnTimer.exe /h-all c. 如果想更改消息提示背景只要找张238x139(其他尺寸软件会缩放)的图片替换OnTimer.jpg d. 普通运行、参数运行、 执行DOS “备注”开头为“-h”则隐藏执行 e. 关机、重启、注销、锁定、待机 “内容”开头为数字则执行前倒计时(可选择取消) 1.普通运行:  可以打开程序,目录,网站,文件,和Windows运行一样,只是不能带参数 如: [内容]http://www.yryz.net [内容]d:\mp3\爱上你是个错.mp3(可实现音乐闹钟) 2.参数运行: 可以带参数运行程序 如: [内容]shutdown -s [内容]ping www.baidu.com 3.下载运行:你可以从网上下载文件并执行 如: [内容]http://www.yryz.net/soft/OnTimer.rar [内容]http://dl_dir.qq.com/qqfile/qq/QQ2009/qq2009sp6_installer.exe 4.结束进程: 以最高权限结束进程,可以结束系统进程(winlogon.exe)! 如: [内容]QQ.exe 5.执行DOS: 就是CMD啦(dir del ...) [内容]del c:\*.log /q /s 6.模拟按键: 用来发送按键(附录有相关按键说明) 如: [内容]^%z 相当于按了Ctrl+Alt+z ,QQ出来了吧!…… 7.发送邮件: 用来定时发送Email的,当然你要先设置好SMTP,也就是用来发信的账号,然后就可以添加任务了, [内容]邮件内容 [参数]收信地址 注意: 如果你只是想发送文字,就直接在[内容]中输入,如: 生日快乐! 如果是想发送文本文件,那就在[内容]中输入文件路径如:c:\boot.ini 程序执行此操作时,会先内容是否为存在的文件,否则就把内容当作文字发送! 8.网络唤醒:  用来远程开机,[内容]中输入MAC地址如:00-e0-4d-df-7e-8a 9.消息提示:  用来定时提醒的,在屏幕的右下角以动画形式显示. 10.关闭系统 11.重启系统 12.注销登陆 13.锁定系统 14.系统待机 例: ------------------------ 先: 执行DOS: ipconfig /all >c:\ip.txt 再: 发送邮件: c:\ip.txt 可用于获得ADSL的动态IP,知道的人应该了解用处! ---------------------------------- 20110330 v1.3d - 修复在Windows 7下按“Alt”键按钮消失的BUG - 修复权限问题导致“随系统启动”无效 20101205 v1.3c + 关机等任务支持倒计时,以便取消执行 * 优化列表显示效果 20101128 v1.3b + 普通运行、参数运行、 执行DOS 支持隐藏执行 + 双击任务即可编辑 - 修复“移动”->“尾部”报错 - 修复分类切换时,任务状态显示不正确 * 调整执行次数显示 20101125 v1.3a + 支持任务分类管理(可拖动) + 支持“每月”任务 + 支持任务排序(上下移动任务,可拖动) + 支持任务执行次数存储 + 支持“内容”、“参数/备注”搜索 + 支持临时暂停所有任务 * 一些细节改进和BUG修复 ! 因数据库结构有变动,要使用原来的数据库(OnTimer.db),可执行 OnTimer.exe /update,当然出现异常时软件也会提示你修复. 20101020 v1.2h - 修复软件中文目录支持问题(数据库读取异常) - 修复托盘图标重建问题 + 添加任务状态托盘提示(活动任务/总任务) * 调整显示字体和列表排序 20101018 v1.2g + 加入“系统待机”功能 + 设置中加入“随系统启动”选项 + 任务列表支持点击“表头”进行排序 + 支持给任务添加备注 * 优化一些小细节 - 修复Win7下添加任务时提示“时间格式有误”的BUG 20100903 v1.2f * 优化消息提示框,使其提示消息时不影响你的工作 * 调整了任务类型的顺序,可能会导致旧版任务类型不正常,更新时请注意 * 调整任务列表顺序,把新添加的任务放到最前面 20100623 v1.2e * 改进列表选择框 * 窗口焦点设置(热键唤醒时) - 去掉提示消息窗口自动关闭 + 可自定义热键 ! 因数据库结构有变动,使用 OnTime.exe /12d-12e 启动就可把旧版的数据库转换成v1.2e版 20100511 v1.2d % 解决多任务时列表闪烁问题。 % 修复计时部分一些Bug!(星期) 20100510 v1.2c + 加入按星期执行!并优化计时部分。 20100510 v1.2b * 重写,效果更好,并作大量优化!  + 使用加密SQLite存储数据。 2008 v1.2  边学边做自己用。 按键附录: Shift + Ctrl ^ Alt % Enter ~ 以下按键要用{}括起来: BKSP, BS, BACKSPACE BREAK CAPSLOCK CLEAR DEL DELETE DOWN END ENTER ESC ESCAPE F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 HELP HOME INS LEFT NUMLOCK PGDN PGUP PRTSC RIGHT SCROLLLOCK TAB UP WIN ( ) ~ % ^ + 可参考下例重复发送某按键: {DEL 4} ;连续4次按下 DEL 键 {S 30} ;发送30个字符“S” +{TAB 4} ;连续4次按下 SHIFT+TAB -------------------------------------------------------- SendMail.log是的日志代码,可参考下表 邮件服务返回代码含义 500 格式错误,命令不可识别(此错误也包括命令行过长) 501 参数格式错误 502 命令不可实现 503 错误的命令序列 504 命令参数不可实现 211 系统状态或系统帮助响应 214 帮助信息 220 服务就绪 221 服务关闭传输信道 421 服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应) 250 要求的邮件操作完成 251 用户非本地,将转发向 450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙) 550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问) 451 放弃要求的操作;处理过程中出错 551 用户非本地,请尝试 452 系统存储不足,要求的操作未执行 552 过量的存储分配,要求的操作未执行 553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误) 354 开始邮件输入,以.结束 554 操作失败 535 用户验证失败 235 用户验证成功 334 等待用户输入验证信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值