使用Gitlab Pipelines执行定时任务
2023年6月16日
背景
在Linux服务器下执行定时任务是一个非常常见的需求,我们可能需要定时备份数据库、定时执行程序脚本、定时清理日志等等。在旧版本Linux下,我们通常会使用crontab
来执行定时任务,而新版本的Linux系统中则更推荐用systemd的timer
来执行定时任务。
但是systemd的配置麻烦又罗嗦,需要先定义一个service
,再定义一个timer
来调用service
,而且timer
的配置文件中还需要指定OnCalendar
来指定定时任务的执行时间,这个时间格式又不是很好记。
此外即使定义好了,管理起来也并不容易,更不要说排查问题了。
而如果机器上使用Docker等容器环境,定时任务就更麻烦了:为了应用的可移植性,希望尽可能将定时任务放到容器中运行,但容器又不能直接以宿主机的身份执行各种脚本(例如重启其它容器,可以通过一些手段实现,但更麻烦)。
此外,定制一个跑定时任务的容器也不是一件容易的事,首先需要有对应环境的基础镜像,然后基于这个镜像安装好crond
之类的工具,再将定时任务的脚本刷到crond
中,最后将这个容器部署到机器上。这个容器还必须有网络权限等。
总之,定时任务的配置和管理是一个非常麻烦的事情,在容器环境下尤其如此。
Gitlab Pipelines
Gitlab提供了CI/CD功能,可以自己定义Pipelines,然后在代码推送、MR合并等事件触发时执行Pipelines。利用这个功能可以很好地执行CI/CD任务。
最近发现Gitlab还有一个Pipeline Schedules的功能,可以在指定的时间点执行Pipelines。利用这个功能可以很好地解决定时任务的问题。
使用方法
首先在项目的CI/CD设置中新建一个Pipelines:
可以看到,这个界面能可视化地管理Pipelines的执行时间,非常方便。
添加好之后,就可以在管理界面看到刚添加的定时任务了:
但这里马上就会面临一个新问题:我们在Gitlab CI中定义的是CI/CD的任务,但定时任务却希望是另外的脚本,怎么办呢?事实上如果此时点击手工运行的话,会发现Gitlab CI会执行我们定义的CI/CD任务,而不是我们希望的定时任务。
这个问题的解决方法是,我们可以在CI/CD的任务中单独定义需要执行的定时任务,并通过条件来指定只有在定时任务触发时才执行。例如:
ci-task:
script:
- echo "This is a CI task"
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"
cron-job:
script:
- echo "This is a cron job"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
ci-task:
script:
- echo "This is a CI task"
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"
cron-job:
script:
- echo "This is a cron job"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
上述配置中,我们定义了两个任务,一个是CI任务,一个是定时任务。其中CI任务只有在push
到master
分支时才会执行,而定时任务只有在定时任务触发时才会执行。这样就可以很好地解决定时任务的执行的问题了。
详细说明可参考官方文档:https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-jobs-for-scheduled-pipelines
接下来的问题是:不是说好的要解决Linux系统下的定时任务问题吗?这里的任务是在Gitlab CI中执行的,怎么执行自己服务器上的任务呢?
这就需要我们从Gitlab CI环境使用SSH连接到自己的服务器上,然后执行对应的任务脚本来实现了。
SSH连接
首先需要在Gitlab CI/CD的设置中将SSH Key作为变量添加进去,这样才能在Gitlab CI环境中获取到对应的Key,并使用SSH连接到自己的服务器上。
截图中SSH_CONFIG
的内容如下:
Host *
KexAlgorithms +diffie-hellman-group1-sha1
StrictHostKeyChecking no
Host *
KexAlgorithms +diffie-hellman-group1-sha1
StrictHostKeyChecking no
这里是为了解决SSH客户端与服务端使用的加密方案不同造成无法连接的情况,可视情况修改或者添加对应的配置。
对应的CI任务脚本如下:
script:
- apk update&&apk add openssh
- mkdir ~/.ssh
- echo "$SSH_CONFIG">~/.ssh/config
- echo "$SSH_KEY_PRIVATE">~/.ssh/id_rsa
- echo "$SSH_KEY_PUBLIC">~/.ssh/id_rsa.pub
- chmod 400 ~/.ssh/id_rsa
- ssh root@100.1.2.3 "sh /data/scripts/db-bak.sh&&docker restart example"
script:
- apk update&&apk add openssh
- mkdir ~/.ssh
- echo "$SSH_CONFIG">~/.ssh/config
- echo "$SSH_KEY_PRIVATE">~/.ssh/id_rsa
- echo "$SSH_KEY_PUBLIC">~/.ssh/id_rsa.pub
- chmod 400 ~/.ssh/id_rsa
- ssh root@100.1.2.3 "sh /data/scripts/db-bak.sh&&docker restart example"
首先安装SSH客户端,然后将SSH Key写入到对应的文件中,最后连接到服务器执行对应的脚本。
小结和可改进点
总的来说,这个方法可以让我们不用花太多心思去配置“定时”这件事情,只需要在Gitlab中添加一个定时任务,然后通过SSH连接到服务器上执行对应的脚本即可。
但这个方法依然不是特别“干净”,因为最终还是需要留一个脚本在服务器上,并不完全符合容器可移植的要求。但这个问题也有一些可改进的方向:
- 将脚本放到容器中,通过SSH连接到服务器后执行
docker
相关命令进行调用 - 将需要定时任务的任务通过HTTP接口等形式暴露出来,然后在Gitlab CI中通过
curl
等工具调用 - 将定时任务的脚本放到Gitlab CI中,通过SSH连接到服务器后将脚本写入到服务器上,然后执行