GKE cluster中的定时任务解决方案(一)
mrdulin opened this issue · comments
前言
当容器化应用程序部署到GKE
cluster中,部署模型是多node,多pod,多docker container的,应用程序在docker container中运行,因此应用程序也是多实例的。现在我们要做一个定时任务(cron job)的需求,该定时任务在每天凌晨2点触发,根据业务需求查询数据库,发送邮件,简单说,就是日报(daily report)。这篇文章不会去介绍GKE
, GCE
, Cloud Pub/Sub
, Cloud Scheduler
, docker
, kubernetes
等概念,阅读此文章需要读者使用并熟悉这些服务和工具,及其相关概念。
问题
在应用程序开发的最初阶段,应用程序在开发者的个人电脑上运行,只有一个实例(instance),Node.js
运行环境即单个进程(process),单个线程(thread)。我们可能会使用node-schedule这个模块来实现定时任务,然后快速实现业务逻辑。示例代码:
function dailyReport() {
console.log('Setup daily report schedule');
const jobName = 'daily-report';
schedule.scheduleJob(jobName, config.DAILY_REPORT_SCHEDULE, () => {
console.log('Start daily report');
sendEmail();
});
}
启动应用程序,日志如下:
☁ issue [master] ⚡ npm start
> issue@1.0.0 start /Users/ldu020/workspace/nodejs-gcp/src/kubernetes-engine/cron-job/issue
> node ./src/main.js
Setup daily report schedule
Server listening on http://localhost:8080
Start daily report
send email
定时任务运行正常,逻辑没问题。
但是当部署到GKE
cluster上,我们来看看是什么样的结果。
GKE
cluster
GKE
cluster中的节点(node),一个主节点(master node),两个从节点(slave node),kubernetes
节点(node)的概念详细说明
GKE
工作负载
我已经将应用程序水平扩展到了3个副本(replicas),3个pod,每个pod中有一个docker
container
,应用程序运行在docker``container
中,部署详情如下图:
进入第一个托管pod(名称为gke-cron-job-issue-69bc5b645f-t4g27),pod详情如下:
可以看到容器gke-cron-job-issue
正在运行。
接下来验证使用node-schedule
实现的定时任务出现的问题。
查看pods
☁ issue [master] ⚡ kubectl get pods
NAME READY STATUS RESTARTS AGE
gke-cron-job-issue-69bc5b645f-hdd4z 1/1 Running 0 4h
gke-cron-job-issue-69bc5b645f-t4g27 1/1 Running 0 4h
gke-cron-job-issue-69bc5b645f-trsrx 1/1 Running 0 4h
查看各个pod中的docker container中应用程序打印出来的日志,开启3个终端会话窗口,分别执行:
kubectl logs -f <pod name> -c <container name>
我将定时任务的时间设置为*/1 * * * *
,每分钟执行一次,3个pod中的docker container中应用程序日志如下图(观察2分钟):
可以看到每个应用程序实例都在执行该定时任务,也就是说,接收日报(daily report)的邮箱每天会收到3封邮件,这不是我们想要的。
此问题源码地址:GKE cron job issue source code
解决方案
我们期望的结果是同一时间只有一个应用程序实例执行该定时任务,接收日报(daily report)的邮箱每天只应该收到一封邮件。
使用cloud scheduler, cloud pub/sub解决该问题,在我部署到GKE
之前,先在本地机器上创建Node.js
单机集群(cluster)测试下,日志如下:
一个Node.js
实例运行在单线程中,充分利用多核计算机,我创建了4个进程(process),启动了4个Node.js
应用程序实例作为集群(cluster)。
配置好的cloud scheduler
作业:
每分钟触发一次,发送一个消息到cloud pub/sub
的topic
中,topic
和subscription
组成消息队列(message queue,简称mq)。在应用程序中使用cloud pub/sub
监听该消息队列,消息流模型:
选用Publisher 1 => Topic => Subscription 1 => Subscriber 1, Subscriber 2的消息流模型,Publisher 1
即我们设置好的cloud scheduler
作业,Topic
和Subscripion 1
组成消息队列,cloud scheduler
作业会定时向消息队列中发送消息,订阅者Subscriber 1
,Subscriber 2
就是各个应用程序实例。从消息流模型可以看出,各个订阅者之间获取消息是竞争关系(消费者竞争模式),即一条消息只能被一个订阅者消费。回到单机集群的日志,每分钟只有一个实例执行了该定时任务。
接下来开始部署到GKE
cluster中,将加入了该解决方案的应用程序容器化,build
新的image
,新的tag
,并push
到了docker.io registry
。部署新的版本到GKE
,命令如下:
☁ solution1 [master] ⚡ kubectl set image deployment/gke-cron-job-issue gke-cron-job-issue=docker.io/novaline/gke-cron-job-solution-1:1.0
deployment.apps "gke-cron-job-issue" image updated
☁ solution1 [master] ⚡
部署成功后,开启 3 个终端会话窗口,分别打印3个pod
中container
中应用程序的日志,手动触发cloud scheduler
定时作业,观察日志:
可见,始终只有一个应用程序实例执行定时任务,接收日报(daily report)邮箱每天收到一封邮件,这是我们想要的结果。
解决方案源码地址:https://github.com/mrdulin/nodejs-gcp/tree/master/src/kubernetes-engine/cron-job/solution1
架构图
架构图中的Application包含了daily report的功能(查询数据库及相关代码逻辑)和应用程序(比如 GraphQL API),daily report的功能比较独立,可以考虑将这部分代码划分出来,放在Cloud Function
中。