從 Linux Crontab 到 K8s CronJob,定時任務正在經歷怎樣的變革

背景

Job 表示短週期的作業,定時 Job 表示按照預定的時間運行Job,或者按照某一頻率週期性的運行 Job。比如:

許多傳統企業使用 Linux 自帶的 crontab 來做定時任務的方案,該方案非常簡單,適合做主機上的運維工作,比如定時清理日誌、週期性做健康檢查。隨着信息化時代的高速發展,業務變得越來越複雜,很多場景都需要定時任務,但是 crontab 方案存在高可用問題,不適合應用在業務應用上。

在雲原生時代,K8s CronJob 設計了一套高可用的定時任務解決方案,保障了業務的穩定。但是把 K8s CronJob 應用在生產上,發現定時任務真的出問題的時候排查起來很麻煩,於是越來越多用戶對定時任務的可觀測有了更多的訴求,阿里雲也推出了自己的雲原生定時任務解決方案,可以託管原生 K8s CronJob,提供可報警、可觀測、可運維等能力,幫助企業提效。

Linux Crontab 方案面臨的問題

什麼是 Crontab

Crontab 是 Linux 系統中的一個服務,用於創建、編輯和管理定時任務。通過 crontab 命令,用戶可以設置系統在指定時間自動執行某個命令或腳本。

Crontab 命令的語法分爲兩部分,分別是時間表達式和命令。時間表達式如下:

# ┌───────────── 分鐘 (0 - 59)
# │ ┌───────────── 小時 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(週日到週一;在某些系統上,7 也是星期日)
# │ │ │ │ │                          或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *

命令常用來執行某個腳本,舉個例子:

  • 每隔 5 分鐘執行 hello.sh:*/5 * * * * sh /root/script/hello.sh
  • 每天早上 6 點半執行 world.py: 30 6 * * * python /root/script/world.py

Crontab 的工作原理

Crontab 由一個名爲"Crond"的守護進程負責調度任務,當 Crond 啓動的時候,就會從配置文件(路徑在 /var/spool/cron 下)加載所有的定時任務。當執行 crontab 命令的時候,會動態的添加新的定時任務,並加入到配置文件中。Crontab 每次執行任務,都會產生執行記錄,目錄在 /var/log/cron 下。

Crontab 的痛點問題

使用 crontab 主要有如下痛點:

  • 無高可用:爲了保證業務冪等執行,需要在不同的機器配置不同的 crontab 任務。crontab 只能調度本機器上的定時任務,如果某一個機器掛了,那上面的定時任務也都不會執行了,有穩定性風險。
  • 無自動負載均衡:不同的腳本放在不同的機器上,需要手動負載均衡,如果腳本比較多,運維代價很高。
  • 無權限隔離:一般企業生產的機器只有運維才能登陸,但是開發要新增/修改腳本和定時任務,也需要登錄到生產的機器上,沒法做到權限隔離。

雲原生 K8s CronJob 方案的優勢

什麼是 K8s CronJob

Job 是 K8s 中的一種資源,用來處理短週期的 Pod,相當於一次性任務,跑完就會把 Pod 銷燬,不會一直佔用資源,可以節省成本,提高資源利用率。CronJob 也是 K8s 中的資源,用來週期性的重複調度 Job。

下面是一個 CronJob 的示例,每隔 5 分鐘調度腳本 edas/schedulerx-job.sh:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command: ["/bin/sh", "/root/script/edas/schedulerx-job.sh"]
          restartPolicy: OnFailure

K8s CronJob 的優勢

與單純使用 Crontab 相比,使用 K8s CronJob 帶來了如下優勢:

  • 高可用:K8s 會保證集羣的高可用,如集羣中有節點掛了,都不會影響定時任務的調度。
  • 自動負載均衡:Pod 默認選擇負載最低的 node 執行,支持 NodeSelector 和親和性等多種負載均衡策略。
  • 權限隔離:只有運維可以登錄 master 和 worker 節點,開發通過管控或者 ApiServer 來創建和更新 CronJob,並且支持命名空間隔離,RBAC 權限管理。

K8s CronJob 的進階能力

Linux Crontab 只能週期性調度本機的腳本,功能比較簡單,K8s 定時任務支持更多的進階能力:

  • 在 Job 資源上
    • 並行執行:通常一個 Job 只啓動一個 Pod,可以通過配置 spec.completions 參數,來決定一個 Job 要執行多少個 Pod。
    • 索引任務:並行執行通常需要和索引任務結合使用,當配置 .spec.completionMode="Indexed" 時,這個 Job 就是一個索引任務,每個 Pod 會獲得一個不同的索引值,介於 0 和 .spec.completions-1 之間,這樣就可以讓不同的 Pod 根據索引值處理不同的數據。
    • 並行限流:並行執行的時候,通常還需要做限流,可以配置 .spec.parallelism 參數,來控制一個 Job 最多同時跑多少個 Pod。
    • 失敗自動重試:可以配置 .spec.backoffLimit,來設置 Job 失敗重試次數。
    • 超時:可以配置 .spec.activeDeadlineSeconds,來設置 Job 超時的時間。
  • 在 CronJob 資源上
    • 時區:可以通過設置 .spec.timeZone 參數,決定 CronJob 按照哪個時區的時間來調度任務。
    • 併發性規則:當一個 Job 還在執行,下次調度時間到了,是否執行新的 Job,可以通過 .spec.concurrencyPolicy 來配置,取值爲 Allow/Forbid/Replace。
    • 任務歷史限制:可以通過配置 .spec.successfulJobsHistoryLimit 和 .spec.failedJobsHistoryLimit 來決定保留多少成功和失敗的 Job。

阿里雲 K8s CronJob 提效新模式

阿里雲分佈式任務調度 SchedulerX 和雲原生結合,推出可視化 K8s Job 解決方案。針對腳本使用者,屏蔽了容器服務的細節,不用構建鏡像就可以讓不熟悉容器的同學(比如運維和運營同學)玩轉 K8s Job,受益容器服務帶來的降本增效福利。針對容器使用者,SchedulerX 不但完全兼容原生的 K8s Job,還能支持歷史執行記錄、日誌服務、重跑任務、報警監控、可視化任務編排等能力,爲企業級應用保駕護航。

快速遷移 Crontab 腳本任務

通過上面的章節,我們知道 Linux Crontab 存在許多問題,遷移到 K8s CronJob 可以帶來很多好處,但是要從 crontab 遷移到 K8s CronJob 還是挺麻煩的,這裏以通過 python 腳本訪問數據庫爲例,來對比兩種方案的差異。

K8s 原生解決方案

1. 將 crontab 腳本拷貝到本地,取名爲 edas/schedulerx-job.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import MySQLdb

# 打開數據庫連接
db = MySQLdb.connect("localhost", "testuser", "test123", "TESTDB", charset='utf8' )

# 使用cursor()方法獲取操作遊標 
cursor = db.cursor()

# SQL 查詢語句
sql = "SELECT * FROM EMPLOYEE \
WHERE INCOME > %s" % (1000)
try:
    # 執行SQL語句
    cursor.execute(sql)
    # 獲取所有記錄列表
    results = cursor.fetchall()
    for row in results:
        fname = row[0]
        lname = row[1]
        age = row[2]
        sex = row[3]
        income = row[4]
        # 打印結果
        print "fname=%s,lname=%s,age=%s,sex=%s,income=%s" % \
        (fname, lname, age, sex, income )
        except:
    print "Error: unable to fetch data"

# 關閉數據庫連接
db.close()

2. 在本地編寫 Dockerfile

FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY edas/schedulerx-job.py /root/edas/schedulerx-job.py

CMD [ "python", "/root/edas/schedulerx-job.py" ]

3. 製作 docker 鏡像,推到鏡像倉庫中

docker build -t registry.cn-beijing.aliyuncs.com/demo/edas/schedulerx-job:1.0.0 .
docker push registry.cn-beijing.aliyuncs.com/demo/edas/schedulerx-job:1.0.0

4. 編寫 K8s CronJob 的 YAML 文件,image 選擇第 3 步製作的鏡像,command 的命令爲執行腳本

apiVersion: batch/v1
kind: CronJob
metadata:
  name: demo-python
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: demo-python
            image: registry.cn-beijing.aliyuncs.com/demo/edas/schedulerx-job:1.0.0
            imagePullPolicy: IfNotPresent
            command: ["python",  "/root/edas/schedulerx-job.py"]
          restartPolicy: OnFailure

我們看到把一個 contab 遷移到 K8s CronJob,就需要這麼多步驟,如果之後要修改腳本,還需要重新構建鏡像和重新發布 K8s CronJob,這裏先不計算開始之前的學習成本,單純從使用角度來看,有着較高的上手成本。

阿里雲解決方案

阿里任務調度 SchedulerX 結合雲原生技術,提出了一套可視化的腳本任務解決方案,通過任務調度系統來管理腳本,直接在線編寫腳本,不需要構建鏡像,就可以將腳本以 Pod 的方式在用戶的 K8s 集羣當中運行起來,使用非常方便,如下圖:

1. 在你的 K8s 集羣中部署一個 schedulerx-deployment(只需要裝一次),註冊到 SchedulerX 上來,讓 SchedulerX 可以調度你的 K8s 上的 Pod

2. 在 SchedulerX 任務管理新建一個 K8s 任務,資源類型選擇 Python-Script(當前支持 shell/python/php/nodejs 四種腳本類型),把腳本拷貝進去,然後配置定時表達式

這裏的鏡像只需要構建一個基礎鏡像即可,如果腳本內容有修改,只要依賴的庫沒有改變,就不需要重新構建鏡像。

3. 等調度時間到了,或者通過控制檯手動運行一次,可以在 K8s 集羣中看到以 Pod 的方式運行腳本,Pod 名稱爲 schedulerx-python-{JobId}

下面通過一個表格更方便的看到兩個方案的差異:

  K8s原生解決方案 阿里雲解決方案
腳本管理 不支持 支持,通過SchedulerX控制檯可以進行腳本管理
開發效率 慢,每次修改腳本都需要重新構建鏡像 快,在線修改腳本,不需要構建鏡像,自動部署
學習成本 高,需要學習Docker和K8s等容器相關知識 低,不需要容器相關知識,會寫腳本就行

增強原生 K8s CronJob

SchedulerX 不但能夠快速開發 K8s 腳本任務,屏蔽容器服務的細節,給不熟悉容器服務的同學帶來福音,同時還能託管原生 K8s Job/CronJob,增強可運維可觀測等能力。

K8s 原生解決方案

以官方提供的 CronJob 爲例。

1. 編寫 hello.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: perl:5.34
            command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(100)"]
          restartPolicy: OnFailure

2. 在 K8s 集羣中運行該 CronJob,查看 pod 歷史記錄和日誌

發現原生的 CronJob 只能查看最近 3 條執行記錄和日誌,想要查看更久之前的記錄無法看到,這在業務出現問題想排查的時候就變得尤爲困難。雖然可以通過配置 .spec.successfulJobsHistoryLimit 和 .spec.failedJobsHistoryLimit 來保留更多的 Pod 歷史記錄,但是保留更多的 Pod,就會更加佔用 K8s 集羣的資源,因爲 Job 已經跑完了,只是爲了查看日誌保留更多歷史記錄,成本太高了。

阿里雲解決方案

阿里任務調度 SchedulerX 可以託管原生 K8s Job/CronJob,方便移植,使用 SchedulerX 託管,可以具有更強的可運維可觀測能力,比如任務重跑、日誌服務、報警監控等。

1. 新建 K8s 任務,任務類型選擇 K8s,資源類型選擇 Job-YAML,打印 bpi(-1)

2. 通過工具來生成 cron 表達式,比如每小時第 8 分鐘跑

3. 調度時間還沒到,也可以手動點擊“運行一次”來進行測試

4. 在 K8s 集羣中可以看到 Job 和 Pod 啓動成功,每個任務只會保留最近一次調度的 Pod,減少 K8s 集羣的資源佔用

5. 在 SchedulerX 控制檯也可以看到歷史執行記錄,發現運行失敗

6. 在 SchedulerX 控制檯可以看到任務運行日誌,查看失敗原因

7. 在線修改任務的 YAML,打印 bpi(100)

8. 不需要刪除 Job,通過控制檯來重跑任務

9. 任務重跑成功,且能看到新的日誌

下面通過一個表格來對比兩個方案的差異:

  K8s原生解決方案 阿里雲解決方案
手動運行一次 不支持 支持
手動重跑任務 不支持 支持
Cron定時調度 支持,YAML配置 支持,兼容開源CronJob的YAML,也支持通過控制檯動態配置
K8s資源佔用 高,保留最近3次Pod 低,僅保留最近1次Pod
歷史記錄 最近3次 最近300次
日誌 最近3次 最近2周,支持搜索
報警 不支持 支持,企業級報警通知服務
操作記錄 不支持 支持

總結

在雲原生時代,使用 K8s CronJob 在很多場景下可以作爲 Linux Crontab 替換的解決方案,解決了crontab的一系列痛點問題。通過阿里雲 SchedulerX 來調度你的 K8s CronJob,能夠降低學習成本,加快開發效率,讓你的任務失敗可報警,出問題可排查,打造雲原生可觀測體系下的定時任務。

作者: 黃曉萌(學仁)

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章