Opensearchのjob-Schedulerについて

とある件で、Opensearchネイティブ機能のみでクエリを発行や、それ以外の機能を利用することができるかを検討した際、opensearch-job-schedulerプラグインがあることを知り得たのでこちらについて調査・検証した結果を記載します。

# 前提
OS：AmazonLinux2023
Opensearch: 2.17.0
opensearch-job-scheduler： 2.17.0
Docker：27.3.1, build ce12230



# 1. ドキュメントの調査
公式ページを確認したところ、以下記載があることからopensearch-job-schedulerプラグインをつかってJOBスケジュール機能が利用できるのでは？と考えました。

:::note info
ジョブスケジューラ
・OpenSearchジョブスケジューラプラグインは、クラスターで実行される一般的なタスクのスケジュールを作成するために使用できるフレームワークを提供。
・ジョブスケジューラのサービスプロバイダーインターフェイス (SPI) を使用して、スナップショットの作成、データのライフサイクルの管理、定期的なジョブの実行などのクラスター管理タスクのスケジュールを定義可能
・標準のOpenSearchプラグインのインストールプロセスに従って、ジョブスケジューラプラグインをインストール可能
・間隔を指定してジョブをスケジュールすることも、0 12 * * ?毎日正午に実行される などの Unix cron 式を使用して、より柔軟なスケジュールを定義することも可能
:::

引用元

https://opensearch.org/docs/latest/monitoring-your-cluster/job-scheduler/index/

パッケージからインストールさえすれば、すぐにでもAPIエンドポイントが利用可能で自由にジョブ定義が可能だとばかり思っていました。

# 2. エンドポイントの調査
マニュアルを見てもopensearch-job-schedulerプラグインで利用可能なエンドポイントの記載がないのでSourceから調査してみました。

```bash
[root@amznlinux2023 job-scheduler]# grep -R "/_plugins/" ./*
./sample-extension-plugin/src/main/java/org/opensearch/jobscheduler/sampleextension/SampleExtensionRestHandler.java: * POST /_plugins/scheduler_sample/watch?id=dashboards-job-id&job_name=watch dashboards index&index=.opensearch_dashboards_1&interval=1
./sample-extension-plugin/src/main/java/org/opensearch/jobscheduler/sampleextension/SampleExtensionRestHandler.java: * {@code DELETE /_plugins/scheduler_sample/watch?id=dashboards-job-id}
./sample-extension-plugin/src/main/java/org/opensearch/jobscheduler/sampleextension/SampleExtensionRestHandler.java:    public static final String WATCH_INDEX_URI = "/_plugins/scheduler_sample/watch";
./src/test/java/org/opensearch/jobscheduler/TestHelpers.java:    public static final String GET_JOB_DETAILS_BASE_URI = "/_plugins/_job_scheduler/_job_details";
./src/test/java/org/opensearch/jobscheduler/TestHelpers.java:    public static final String GET_LOCK_BASE_URI = "/_plugins/_job_scheduler/_lock";
./src/test/java/org/opensearch/jobscheduler/TestHelpers.java:    public static final String RELEASE_LOCK_BASE_URI = "/_plugins/_job_scheduler/_release_lock";
./src/main/java/org/opensearch/jobscheduler/JobSchedulerPlugin.java:    public static final String JS_BASE_URI = "/_plugins/_job_scheduler";
[root@amznlinux2023 job-scheduler]#
```

sampleでもなく、Testでもない唯一利用可能そうなエンドポイント(/_plugins/_job_scheduler)をDevToolsで確認してみましたが、結果はNGでした。

```json
{
  "error": "no handler found for uri [/_plugins/_job_scheduler] and method [GET]"
}
```
「OpenSearchに登録されているエンドポイントの一覧に /_plugins/_job_scheduler が存在しない、または、対応するメソッド（今回の場合はGET）が定義されていない。」とメッセージが返却されます。


# 3. 改めて

opensearch-job-schedulerは上記に記載のあるサービスプロバイダーインターフェイス (SPI) を提供しているのみで、opensearch-job-scheduler単体だけではActionを含めたジョブを実行できず、「ジョブのスケジュール管理」だけを提供するためのプラグインなのでは？という疑問が生まれ始め、改めて調査しました。


***調査結果***

:::note info
**ジョブスケジューラのセキュリティの現状**
JobSchedulerはシンプルなプラグインで、**他のプラグイン**が**ジョブ**を**スケジューラーに登録**します。ジョブスケジューラーは、登録されたすべてのジョブを定期的にチェックし、実行のタイミングが来ているかどうかを判断します。実行のタイミングが来ている場合、JobSchedulerは該当するプラグイン内でそのジョブを実行します。また、OpenSearchは分散システムであるため、JobSchedulerにはロック機能も備わっています。これにより、不要な場合に複数のノードで同じジョブが実行されるのを防ぐことができます。
:::

https://github.com/opensearch-project/security/issues/2905

やっぱりそのようです。（いや確かにサンプルプログラムとかドキュメントに記載されていましたが、作成しないとそもそも機能しないとか書いてないしなと思いつつ、かなりの時間を消費しました。）


一応、これがほんとにそうなのか？という部分を同じschedule実行されるindex-managementのSourceから確認し、もしjobscheduler.spiがimportされていれば、上記説明も納得できます。

```java:indexmanagement/indexstatemanagement/model/Transition.kt
// 抜粋
import org.opensearch.jobscheduler.spi.schedule.CronSchedule
import org.opensearch.jobscheduler.spi.schedule.ScheduleParser
// 抜粋
```
ものの見事にimportされています。

:::note info
**Alertプラグインについて**
同じくschedule機能があり実行できる機能としてAlertがありますが、こちらはSourceを見る限り、opensearch-job-schedulerプラグインを利用していないようです。※「アラートプラグインはジョブスケジューラを使用してモニターを実行します」と記載があるものの、jobscheduler.spi はimportされていませんでした。
:::

という事で、脆くも「Opensearchネイティブ機能のみでクエリを発行や、それ以外の機能を利用すること」という思惑は見事に崩れ落ちたわけで、OpenSearchにクエリ実行を定期的に実行したいなら、javaを使って自分専用のカスタムプラグインを作成するか、クライアント側のAPIを利用した方法が必要となるようです。（非常に残念です）

ただ、これだけで終わるのもopensearch-job-schedulerの面白さを全然語れていないとおもいますし、せっかくなのでサンプルプログラムを利用するところまで実施してみたいと思います。

:::note warn
**インストールバージョンについて**
yumでインストールできる最新は2.19(2025/2/25日現在)になっていますが、opensearch-job-schedulerのlatestが2.17であるためこれに合わせてOpensearch側も2.17に合わせて実行しています。
:::

# 4. ビルド環境の準備
@gsh-kzさんの記事を参考にさせていただきました。

https://qiita.com/gsh-kz/items/fc17e547a1a0b7b55b17

以下、ビルド用のコンテナ環境を準備します。

```dockerfile:Dockerfile
ARG AM2_VER=2023.0.20230315.0

FROM amazonlinux:$AM2_VER

ARG SDKMAN_PKG="tar zip unzip findutils wget git vim"
ARG DEV_PKG="which man make less"

RUN yum update -y && \
  yum install -y $SDKMAN_PKG $DEV_PKG && \
```

準備ができたら、ビルドしてコンテナにログインします。
```bash
docker build -t amazonlinux:2023 .
docker run --rm -it -v $PWD:/app amazonlinux:2023 bash
```

一般ユーザでないとこの後の作業が失敗するため、opensearchユーザを作成します。
```bash
useradd -m opensearch
su - opensearch
```

ビルドに必要となるpackageを入れます。
```
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 21.0.6-tem
java -version
```

任意のディレクトリでjob-scheduler（2.17）をダウンロードして展開しておきます。
```bash
wget https://github.com/opensearch-project/job-scheduler/archive/refs/tags/2.17.0.0.tar.gz
tar zxvf 2.17.0.0.tar.gz
```


# 5. job-schedulerサンプルプログラムビルド実行

./gradlewコマンドでbuildします。しばらくするとpackage(zipファイル)が生成されます。
```bash
cd job-scheduler-2.17.0.0
./gradlew build
```

無事成功したら、以下のコマンドでコピーしておきましょう
```bash
cp -pr /home/opensearch/job-scheduler-2.17.0.0/sample-extension-plugin/build/distributions/opensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zip /tmp/
```

次にHostOSからローカルにdocker cpコマンドでopensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zipファイルをコピーします。
```bash
docker cp [コンテナID]:/tmp/opensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zip ./
```

copyしたzipファイルはOpensearchが起動しているノードの/tmpなどに配置しましょう。


# 6. Opensearchへ拡張プラグインインストールおよび実行

OpensearchにSSHログインしたのち、以下コマンドを実行します。

```bash
/usr/share/opensearch/bin/opensearch-plugin install file:///tmp/opensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zip
```

***実行結果***
```
-> Installing file:///tmp/opensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zip
-> Downloading file:///tmp/opensearch-job-scheduler-sample-extension-2.17.0.0-SNAPSHOT.zip
[=================================================] 100%  
-> Installed opensearch-job-scheduler-sample-extension with folder name opensearch-job-scheduler-sample-extension
```

エラーが出ていないことを確認したのち、opensearchをリスタートします。

```bash
systemctl restart opensearch
```

続いて、DevTools上で、「GET _cat/plugins?v」を実行し、「opensearch-job-scheduler-sample-extension 2.17.0.0-SNAPSHOT」が表示されていれば準備完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/796b8e21-30e4-49be-aa91-35535aaebf6e.png)


最後にサンプルプラグインのエンドポイント向けにPOSTリクエストを発行します。このサンプルプラグインは指定されたタイミングで、指定されたIndexをモニタリングし、OpenSearchクラスタ内のシャード情報を取得・ログ出力するプラグインの様です。

```
POST /_plugins/scheduler_sample/watch?id=test-monitor-job&job_name=Monitor%20test%20index&index=test-monitor-index&interval=1
```

***パラメータ・意味***
|項目|値|意味|
|---|---|---|
|id|test-monitor-job|重複しないユニークなID|
|job_name|Monitor test index|実行JOB名|
|index|test-monitor-index|監視対象のIndex|
|interval|1|実行間隔(分)|


以下のコマンドでJOB状況を確認できます。

```bash
GET _cat/tasks?v=true&s=timestamp
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/7f14be7a-d94e-409b-a37a-d4d4f88af868.png)


また、GETエンドポイントは用意されていませんが、以下のようにコマンド実行することで、確認が取れます。

```bash
GET /.scheduler_sample_extension/_search
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/45a16a22-0375-441c-b8cf-b7fd67452d30.png)





作成したJOBは以下コマンドで削除可能です。

```bash
DELETE /_plugins/scheduler_sample/watch?id=test-monitor-job
```

# 7. その他

opensearch-job-schedulerは柔軟にカスタマイズできる反面、作りこみが必要となるためまずjavaに精通している必要があります。また、サンプルプログラムはPOSTとDELETEしかありませんしActionもログ出力しかないため、カスタムプラグイン実装の道はそれなりに険しそうです。（すでに開発してGitに挙げている人がいれば教えていただきたいです。。。。）