mrdulin / blog

Personal Blog - 博客 | 编程技术,软件,生活

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

GKE cluster中的节点(node),一个主节点(master node),两个从节点(slave node),kubernetes节点(node)的概念详细说明

GKE cluster nodes

GKE工作负载

GKE工作负载

我已经将应用程序水平扩展到了3个副本(replicas),3个pod,每个pod中有一个docker container,应用程序运行在docker``container中,部署详情如下图:

部署详情

进入第一个托管pod(名称为gke-cron-job-issue-69bc5b645f-t4g27),pod详情如下:

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分钟):

GKE container logs

可以看到每个应用程序实例都在执行该定时任务,也就是说,接收日报(daily report)的邮箱每天会收到3封邮件,这不是我们想要的。

此问题源码地址:GKE cron job issue source code

解决方案

我们期望的结果是同一时间只有一个应用程序实例执行该定时任务,接收日报(daily report)的邮箱每天只应该收到一封邮件。

使用cloud scheduler, cloud pub/sub解决该问题,在我部署到GKE之前,先在本地机器上创建Node.js单机集群(cluster)测试下,日志如下:

Node.js cluster cron job schedule

一个Node.js实例运行在单线程中,充分利用多核计算机,我创建了4个进程(process),启动了4个Node.js应用程序实例作为集群(cluster)。

配置好的cloud scheduler作业:

cloud scheduler task

每分钟触发一次,发送一个消息到cloud pub/subtopic中,topicsubscription组成消息队列(message queue,简称mq)。在应用程序中使用cloud pub/sub监听该消息队列,消息流模型:

选用Publisher 1 => Topic => Subscription 1 => Subscriber 1, Subscriber 2的消息流模型,Publisher 1即我们设置好的cloud scheduler作业,TopicSubscripion 1组成消息队列,cloud scheduler作业会定时向消息队列中发送消息,订阅者Subscriber 1Subscriber 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个podcontainer中应用程序的日志,手动触发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中。


Flag Counter