LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

cronジョブ監視サービスCronitorの設定をコード管理してCI/CDする

まえがき

こんにちは、インフラグループの yjszk です。
今回は、Cronitorというツールについてご紹介します。Cronitorはジョブの状態を簡単に管理することができるモニタリングサービスです。
このサービスは、Cronジョブの監視を行うことができ、提供されているSDKは複数のプログラミング言語で利用可能で、設定をYAMLでエクスポートし、コード管理することができるのも魅力の1つです。
今回は、Python SDKを使用してCronitorを設定する方法について説明します。

Cronitorとは

Cronitorは、ジョブの状態を管理するためのモニタリングサービスであり、ジョブの開始時や終了時にCronitorのエンドポイントを叩くことで、ジョブの成功可否や実行時間、標準出力や標準エラー出力などを監視することができます。
CronitorCLIやcURLを使う方法でCronitorを利用することができます。
Cronitorは、既存のジョブに変更を加えることなく監視設定を追加できるため、ジョブに改修を加えたくない場合にも適しています。
例えば、1分おきに動くジョブがある場合、Cronitorの設定例は以下のようになります。

*/1 * * * * cronitor exec {JOB_KEY} /home/ec2-user/test.sh >> /home/ec2-user/test.log 2>&1
  • cURLを使う場合
MONITOR_URL='https://cronitor.link/p/{API_KEY}/{JOB_ID}'
*/1 * * * * curl $MONITOR_URL?state=run; /home/ec2-user/test.sh >> /home/ec2-user/test.log && curl $MONITOR_URL?state=complete || curl $MONITOR_URL?state=fail 2>&1

このように監視設定を既存のジョブに変更なく加えることが可能です。
通知はSlackやメールなど多数のサービスに対応しています。

Python SDKによる設定のコード管理

ある程度大きいサービスになると、バッチ処理は大量に存在し、ジョブ一つ一つにGUIで設定するのは骨が折れ、設定をコード管理をしたい思いが溢れてしまいます。
APIのドキュメントを見ると全ての機能がAPI経由で利用可能とあります。エクセレント。
まずは雰囲気を掴むためにGUIでMonitorを作ってみます。

GUIでのジョブ作成

  • 監視設定
    • LinuxのCronジョブ
    • 15分おきに実行
    • タイムゾーンは日本時間
    • 通知先はあらかじめ設定したSlack(Settings→Alertsで任意の通知方法を設定)

設定ができたら、表示される画面にCronitorへテレメトリーを送る方法が書かれているので、それに従い、crontabにCronitor通知を埋め込みます。

すると、以下のような画面になり、モニタリングが開始されます。

SDKによる設定

今回はPythonのSDKを使用します。

pip install cronitor

Pythonがインストールされてる環境において、やることはこれだけです。グレート。

既存設定のエクスポート

以下のコードでエクスポートが可能です。コードと同階層にcronitor.yamlで出力されます。

import cronitor

# APIKEYは https://cronitor.io/app/settings/api
cronitor.api_key = 'APIKEYを入れる'
cronitor.generate_config()

先ほどのジョブはこのようにYAMLで出力されます。

jobs:
  XXXXXX: # jod keyはユニークなので隠しています
    environments:
      - production
    grace_seconds: 60
    group: dev
    name: testing-job
    notify:
      - slack
    platform: linux cron
    realert_interval: every 8 hours
    schedule: '*/15 * * * *'
    timezone: Japan

これでコード管理できる状態になりました。これをAPI経由で変更してみましょう。

コード化した設定の適用

Monitor名を変更します。

jobs:
  XXXXXX: # jod keyはユニークなので隠しています
    environments:
      - production
    grace_seconds: 60
    group: dev
    name: testing-job-rename # -renameを追加
    notify:
      - slack
    platform: linux cron
    realert_interval: every 8 hours
    schedule: '*/15 * * * *'
    timezone: Japan

以下のコードを実行します。

import cronitor

cronitor.api_key = 'APIKEYを入れる'
cronitor.read_config('./cronitor.yaml')
cronitor.apply_config()

変更されました。素晴らしい。

Dry-Run

Dry-Runで適用前に設定を確認することもできます。試しに誤った設定を書いてDry-Runしてみます。

jobs:
  XXXXXX: # jod keyはユニークなので隠しています
    environments:
      - production
    grace_seconds: 60
    group: dev
    name: testing-job-rename
    notify:
      - slack
      - discord # 存在しない通知先を追加した
    platform: linux cron
    realert_interval: every 8 hours
    schedule: '*/15 * * * *'
    timezone: Japan

以下コードでDry-Runできます。

import cronitor

cronitor.api_key = 'APIKEYを入れる'
cronitor.read_config('./cronitor.yaml')
cronitor.validate_config()

実行結果です。意図した通り「存在しない通知先」とエラーが出ています。

$ python3 cronitor_config_validate.py
XXXXXX:
  notify:
  - Invalid notification list template "discord".

Dry-Runと設定適用ができたら何ができるでしょうか。CI/CDができます。
PRやマージに応じてバリデーションや設定が自動で適用されるよう、CI/CDの仕組みを組んでみましょう。

GitHub Actionsによるパイプライン化の例

APIKEYはGitHub Secretsに格納することします。YAMLは一つしか存在しないこととし、デプロイのコードに関しては必要最低限のみの実装とします。

  • 実行コード
import cronitor
import os
import sys

cronitor.api_key =  os.getenv('CRONITOR_SECRET')
cronitor.read_config('./cronitor.yaml')
if cronitor.validate_config():
    cronitor.apply_config()
else:
    # Validateがうまくいかなかった場合に異常終了させる
    sys.exit(1)
  • GitHub ActionsのYAML
name: cronitor config apply

on:
  pull_request:
    branches:
      - main
    types: [closed]

jobs:
  check:
    name: cronitor config apply
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true
    permissions:
      id-token: write
      contents: read

    defaults:
      run:
        shell: bash
        working-directory: ./

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Python 3.8
        uses: actions/setup-python@v4
        with:
          python-version: 3.8
          cache: 'pip'

      - name: pip install
        run: pip install cronitor

      - name: validate_and_apply
        id : validate_and_apply
        env:
          CRONITOR_SECRET: ${{ secrets.CRONITOR_SECRET }}
        run: |
          python cronitor_config_apply.py

先ほどの誤った設定をGitHub Actions上で適用しようとすると想定通り失敗しました。

ハマりどころ

Monitorの作成はGUIからかAPIからの2種類あり、Cronitor cliを使用する場合は、以下のようになります。

  • GUIからの場合は、Cronitor側で自動的にIDが採番されるので、cronitor execするときにCronitor cliのAPIKEY設定が不要です。
  • API経由の場合は自動採番されない(自分で決める必要がある)ので、cronitor execするときにCronitor cliのAPIKEY設定が必要です。

この仕様はAnonymous Eventsという形でドキュメントに記載されています。
API経由でMonitorを作成した場合は、Cronitor cliのAPIKEY設定をしていないとモニタリングが始まらないので要注意です。

終わりに

実際にこれらを用いて運用をする場合、例えば監視したい環境ごとにEnvironmentやGroupで分けると思いますが、その場合YAMLファイルは複数になるので、そのあたりをコード化する必要があると思います。
個人的にはPull Requestを作った時にCIで設定のdiffを出したいですね。今後の課題としたいです。

参考