分布式调度任务管理—Dkron
背景需求
- 多个系统上有属于自己业务上的计划任务,并且有部分计划任务不能多台服务器同时执行,具有唯一性,cron服务高可用难以实现。
- 方便管理cron
- 计划任务执行失败需要告警通知。
简单说说dkron
GO + Serf + gossip protocol +raft 的技术架构,通过阅读作者原文,总结几个关键字:分布式、轻量级、可靠、易拓展。
优点:
-
定时:支持精确到秒,支持cron语法、@every、@at。
-
任务特点:指定失败重试执行次数,任务可并发执行,父任务及子任务的链式执行机制。
-
集群:去中心化、高可扩展性和leader自动选举,完善的api接口和UI
-
警报通知:邮件通知和可接入prometheus
-
节点:每个节点都包含所有的服务。
缺点:
- 前端页面不够友好(可能是个人的使用习惯)
- 高级的邮件支持只在pro版本可用,oss版本不能设置失败邮件推送和邮件payload
注意(与我的理解的有不一致需要注意):
-
任务执行错误,只做重试,不转移其他节点。
-
节点故障, 当前跑的任务进程关闭,只能等待下次计划时间开启
关于运行机制和任务修改过程可以看如下两篇文章:
-
可以开启debug模拟进行日志校验。
Jul 15 14:59:54 tserver121 dkron: time="2021-07-15T14:59:54+08:00" level=info msg="2021/07/15 14:59:54 [DEBUG] memberlist: Initiating push/pull sync with: 10.3.247.118 10.3.247.118:8946" Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="fsm: received command" command=0 node=10.3.247.121 Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="store: Retrieved job from datastore" job=job1 node=10.3.247.121 Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="store: Setting job" job=job1 node=10.3.247.121 Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="fsm: received command" command=2 node=10.3.247.121 Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="store: Setting key" execution="executions:job1:1626332399964085243-tserver120" finished="0001-01-01 00:00:00 +0000 UTC" job=job1 node=10.3.247.121 Jul 15 14:59:59 tserver121 dkron: time="2021-07-15T14:59:59+08:00" level=debug msg="store: to detele key" execution=1626330569972455451-tserver120 job=job1 node=10.3.247.121 Jul 15 15:00:00 tserver121 dkron: time="2021-07-15T15:00:00+08:00" level=debug msg="fsm: received command" command=4 node=10.3.247.121 Jul 15 15:00:00 tserver121 dkron: time="2021-07-15T15:00:00+08:00" level=debug msg="fsm: Setting execution" execution=1626332399964085243-tserver120 node=10.3.247.121 output= Jul 15 15:00:00 tserver121 dkron: time="2021-07-15T15:00:00+08:00" level=debug msg="store: Retrieved job from datastore" job=job1 node=10.3.247.121 Jul 15 15:00:00 tserver121 dkron: time="2021-07-15T15:00:00+08:00" level=debug msg="store: Setting job" job=job1 node=10.3.247.121 Jul 15 15:00:24 tserver121 dkron: time="2021-07-15T15:00:24+08:00" level=info msg="2021/07/15 15:00:24 [DEBUG] memberlist: Initiating push/pull sync with: tserver120 10.3.247.120:8946"
使用Dkron
可以先去官方网站阅读一下指导文档
安装
方便安装是dkron的特点 downloads page可以下载rpm直接安装:
yum localinstall dkron_3.1.8_linux_x86_64.rpm
systemctl enable dkron
systemctl restart dkron
这样已经安装完成,如果不想服务向外网通信可以拦截stats.dkron.io
,因为启动dkron会向它注册验证。
端口
8946 #用于代理之间数据同步
8080 #用于API和仪表板的HTTP -->我部署已改为8100
6868 #用于代理之间的执行通信。
用户访问是8080,其他的端口都是服务器间通信,设置防火墙需要注意。
客户端命令
dkron agent - 启动
dkron doc - 文档
dkron keygen - 生成密钥
dkron leave - 离开集群
dkron raft - 客户端控制命令
dkron version - 显示版本
dkron agent --server --bootstrap-expect=1 #单个服务器配置需要 -bootstrap-expect=1 标志
dkron raft list-peers 查看当前状态
dkron raft remove-peer --peer-id 10.3.247.120 删除节点需要在leader执行, 删除后如果节点正常 则又自动加入
dkron leave 当前节点离开集群 需要重启后加入
API接口
官方文档API ,包含了:
- 节点管理
- jobs管理
- 执行日志(只能查到最近100条信息)
由于页面添加方式不太习惯,所以我常使用post localhost:8080/v1/jobs
,添加和修改jobs
curl localhost:8080/v1/jobs -XPOST -d '{
"name": "job1",
"schedule": "@every 10s",
"timezone": "Europe/Berlin",
"owner": "Platform Team",
"owner_email": "platform@example.com",
"disabled": false,
"tags": {
"server": "true:1"
},
"metadata": {
"user": "12345"
},
"concurrency": "allow",
"executor": "shell",
"executor_config": {
"command": "date"
}
}'
集群
接下来就是配置集群,我使用的是3+N server
+ N node
模式,
node不提供web UI,不做leader,只做任务执行器,部署在各个业务服务器上,当然如果是单纯的跑任务,没有任务环境依赖,我建议全部配置server,
请参照配置
server 配置文件
# Dkron example configuration file
# This node is running in server mode
server: true #false 不能成为leader
bootstrap-expect: 3 #集群初始化数量
http-addr: 0.0.0.0:8100 # false 不能成为leaderhttp访问端口默认8080
node-name: 10.13.3.26 #唯一, 默认主机名
#advertise-addr: 0.0.0.0 #代理之间通信
#bind-addr: 0.0.0.0 #API和仪表板的HTTP通信
data-dir: '/export/dkron' #数据目录
datacenter: ywkf #本地代理的数据中心标识(默认为“ dc1”)
region: sre_ywkf4 #区域
log-level: info #日志级别,默认info
tags:
project: manager
serverip: 10001
approle: leader
encrypt: XXXXXXXXXXXXXXXXXXXXXX== #用于加密网络流量的密钥,需要一致 dkron keygen - 生成密钥
raft-multiplier: 5
join: #加入的初始代理
- 10.13.3.26
- 10.13.3.25
- 10.13.3.248
retry-join: #集群创建后加入
retry-max: 6 #最大连接尝试次数。默认为0,它将无限期重试
serf-reconnect-timeout: 60s #失败节点尝试重新连接 默认24h
#mail-from: platform@example.com #oss版本不能设置失败邮件推送和邮件payload ,所以不配置
#mail-host: 10.13.3.25
#mail-password:
#mail-payload:
#mail-port: 25
#mail-subject-prefix: "[Dkron]"
#mail-username:
node 配置文件
# Dkron example configuration file
# This node is running in server mode
#server: true #false 不能成为leader
#bootstrap-expect: 3 #集群初始化数量 false 不能成为leader
#http-addr: 0.0.0.0:8100 # false 不能成为leaderhttp访问端口 m默认8080
node-name: 10.13.3.230 #唯一, 默认主机名
#默认路由不在同一网段添加如下: (不带端口!!!!!!!!!!)
#advertise-addr: 10.13.3.236 #代理之间通信
#bind-addr: 10.13.3.236 #API和仪表板的HTTP通信
data-dir: '/export/dkron' #数据目录
datacenter: ywkf #本地代理的数据中心标识(默认为“ dc1”)
region: sre_ywkf4 #区域
log-level: warn #日志级别,默认info
tags:
project: app
serverid: 10133230
approle: node
encrypt: XXXXXXXXXXXXXXXXXXXXXX== #用于加密网络流量的密钥,需要一致
raft-multiplier: 5 #lead选取时间
join: #加入的初始代理
- 10.13.3.26
- 10.13.3.25
- 10.13.3.248
retry-join: #集群创建后加入
retry-max: 6 #最大连接尝试次数。默认为0,它将无限期重试
serf-reconnect-timeout: 60s #失败节点尝试重新连接 默认24h
问题1:为什么会区分node 和server?
因为node不会启动web端口,减少了端口占用,node是部署在业务服务器上,变动性比较大,leader不稳定,不方便前端nginx做反向代理。
问题2:tags如何添加?
我建议最少需要有 唯一标签serverid
、项目标签project
, 唯一标签作用是指定服务器执行,项目标签是多台角色相同的服务器上执行,
添加和修改job
oss 版本提供了4种执行器 Executors 和3种日志处理 Execution,可以处理多种需求
dkron系统提供了两种方式添加jobs:
命令行post方式(shell/postman)推荐使用postman
curl localhost:8080/v1/jobs -XPOST -d '{
"name": "job1",
"schedule": "@every 10s",
"timezone": "Europe/Berlin",
"owner": "Platform Team",
"owner_email": "platform@example.com",
"disabled": false,
"tags": {
"server": "true:1"
},
"metadata": {
"user": "12345"
},
"concurrency": "allow",
"executor": "shell",
"executor_config": {
"command": "date"
}
}'
web UI方式 localhost:8080 --> jobs --> create -->save
配置简单介绍
Name freestyle-test #job名
Displayname freestyle-test #页面显示名称
Timezone Asia/Shanghai #时区
Schedule 30 */5 * * * ? #秒 分 时 天 月 周, 还有两种种格式 @every 10s 每隔10秒 和 “@at 2018-01-02T15:04:00Z” 将在指定的日期和时间(假设UTC时区)运行作业。
Owner admin #所属人
Owner email platform@example.com #所属人邮箱,邮件通知
Parent job parent_job #父任务,子任务在父任务之后执行
Last success 2021/4/7下午2:27:02 #最后成功时间
Last error 2021/4/7下午2:10:35 #最后失败时间
Status success #当前状态
Next 2021/4/7下午2:50:30 #下一次执行时间
Concurrency forbid #allow(默认):允许并发作业执行。
#forbid:如果作业已经在运行,则不发送执行,它将跳过执行,直到下一个计划。
Processors
{
"files":{ #日志输出 有指定files,有输出到log日志, 输出到syslog
"forward":"true" #true将日志输出转发到下一个处理器
"log_dir":"/data/logs/dkron" #指定文件
}
}
Tags #标签,用于指定对应标签节点执行,在节点的配置文件可以创建标签
{
"object":"monitor:1" #"object":"monitor" 带monitor的都执行,这是多节点并发执行
} #"object":"monitor:1" 带monitor的其中一台执行,采用随机的方式。
#可以使用多个标签组合使用
Executor shell #支持shell、http、kafka、nats
Executor config
{ #以shell 为例
"command":"bash /data/dkron/dkron.sh" #执行命令
"timeout":"200s" #超时时间
"env": "ENV_VAR=va1", #环境变量
"cwd": "/app", #执行路径
}
Disabled #是否启动 (toggle 启动/停止 切换)
Retries 3 #失败重试次数
简单的配置文件模版
{
"name": "job1",
"displayname": "job1",
"timezone": "Asia/Shanghai",
"schedule": "20 59 23 * * ?",
"owner": "master",
"owner_email": "platform@example.com",
"parent_job": "",
"ephemeral": false,
"expires_at": null,
"concurrency": "forbid",
"processors": {
"log": {
"forward": "true"
}
},
"tags": {
"project": "manager"
},
"metadata": null,
"executor": "shell",
"executor_config": {
"command": "bash /data/dkron/dkron.sh",
"cwd": "/",
"env": "",
"timeout": "60s"
},
"retries": 2,
"disabled": false
}
问题3: 计划任务执行失败
level=error msg="grpc: Error calling gRPC method" error="rpc error: code = Unknown desc = agent: Run error retrieving job: job5 from scheduler" method=RunJob node=10.13.3.26 server_addr="10.13.3.25:6868"
将disabled 改为 false 会自动执行
备份和恢复
官方给出的备份还原的方法。
curl localhost:8100/v1/jobs > backup.json
curl localhost:8100/v1/restore --form 'file=@backup.json'
在日常维护中,也可以使用/jobs/{job_name}将每个job 的配置使用json文件格式存储下来,jobs 的修改和单个jobs的恢复都可能需要它。
日志收集与报警
问题4:为什么要做日志收集?
- 虽然dkron 的web上有保存任务执行输出记录,每个jobs最多保存100条记录,频率较高的任务容易被覆盖。
- oss版本不能设置失败邮件推送和邮件payload ,所以不适合邮件告警方式
- dkron监听脚本执行的stdout 和stderr,所以命令中的重定向是没有效果的。
job 需要使用log配置
"processors": {
"log": {
"forward": "true"
}
}
// /etc/rsyslog.conf 配置转发指定文件目录下
// $template fileformat,"/export/logs/dkron/%programname%_%$year%-%$month%-%$day%.log"
// if $programname == "dkron" then ?fileformat
或者
{
"files":{
"forward":"true"
"log_dir":"/data/logs/dkron"
}
}
将任务日志输出到指定位置,通过ElasticStack
收集,再对日志级别,使用elastalert
可以做出对应的规则报警。
logstatsh grok 规则 识别日志的level
match => [ "message" , "%{SYSLOGTIMESTAMP:timestamp} %{DATA:logsource} %{SYSLOGPROG}: %{GREEDYDATA} (?:level=%{LOGLEVEL:level}) ?(?:msg=%{GREEDYDATA:msg})"]
match => [ "message" , "%{SYSLOGTIMESTAMP:timestamp} %{DATA:logsource} %{SYSLOGPROG}: (?:%{GREEDYDATA:msg})"]
参考:
Reliable Cron across the Planet
另一个实现方案xxl-job