OpenSearchのインデックスサイズの監視をAlerting機能で実施する

# はじめに
OpenSearchのプロビジョンドは、大きなインデックスが作成されると容量が不足して落ちることがあります。
それを検知するために、ドメインのストレージサイズが少なくなったらそれをCloudWatchで検知することはよくやります。

ただ、CloudWatchでは、プロビジョンドのOpenSearch内の個別インデックス単位でサイズを検知することはできません。
今回は、それをOpenSearchのAlerting機能で実現してみます。

# 前提条件
- 東京リージョン
- OpenSearchのバージョンは2.17
- Channelは既に作成済み
    - typeはSNSで、SNSにはメールアドレスをサブスクリプション設定済み

# やってみる

### Monitorの新規作成
OpenSearchのアラートは、Monitorを作成することで実現します。
詳細は、以下のBlackBelt資料にわかりやすく載っているので読んでみてください。

[AWS Black Belt Online Seminar Amazon OpenSearch Service 機能解説 – モニタリング・オブザーバビリティ編](https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_Amazon-OpenSearch-Service-Advanced-Monitoring-Observability_0131_v1.pdf)

まずはMonitorを作成していきます。
Monitorsの画面から、「Create monitor」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/73adebe5-dfc3-cb77-2f09-3bf275d20c05.png)

### Monitor details の設定
Monitorを新規作成したら、以下のように **Per cluster metrics monitor** を選択します。これで、このクラスターのメトリクスに対してクエリができます。
頻度は、要件に従って設定してください。今回は、Dailyにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/07a6cb13-7fde-0c48-8d1b-eb9dd125e32f.png)

### Query の設定
次に、Queryを設定していきます。
Request typeに **List indicies** を設定します。こうすることで、インデックスの情報をクエリできます。
Responseの内容が全インデックスの情報です。
この中の`pri.store.size`がインデックスのサイズなので、この情報を使っていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/1301a60b-c714-b616-d158-aa20033bed8d.png)

### Triggers の設定
ここがポイントです。
Trigger conditionで記載した結果がtrueなら、アラートが飛びます。そのため、指定のサイズ以上のインデックスが存在するかどうかをチェックします。

具体的なコードは以下です。1行目のインデックスサイズの閾値は、適宜修正してください。
ちなみに、ここで記述できるのは**Painless**と呼ばれるOpenSearch（およびElasticsearch）専用のスクリプト言語です。

```java
def threshold = 5; // 閾値5GBに設定。適宜修正する。
def largeIndices = [];

for (int i = 0; i < ctx.results[0].indices.size(); ++i) {
    def index = ctx.results[0].indices[i];
    def sizeStr = index["store.size"];
    if (sizeStr != null) {
        def matcher = /(\d+(?:\.\d+)?)\s*([kmgtKMGT]?[bB])/.matcher(sizeStr);
        if (matcher.matches()) {
            def size = Double.parseDouble(matcher.group(1));
            def unit = matcher.group(2).toLowerCase();
            def sizeInGB = 0.0;
            
            if (unit.startsWith("k")) {
                sizeInGB = size / (1024 * 1024);
            } else if (unit.startsWith("m")) {
                sizeInGB = size / 1024;
            } else if (unit.startsWith("g")) {
                sizeInGB = size;
            } else if (unit.startsWith("t")) {
                sizeInGB = size * 1024;
            } else {
                sizeInGB = size / (1024 * 1024 * 1024);
            }
            
            if (sizeInGB >= threshold) {
                largeIndices.add([
                    "name": index["index"],
                    "size": sizeStr
                ]);
            }
        }
    }
}

ctx.results[0].largeIndices = largeIndices;
ctx.results[0].thresholdGB = threshold;
return !largeIndices.isEmpty();
```

:::note info
returnの直前で、`ctx.results[0]`に変数を新規作成して値を代入しています。
これは、メールの内容に、閾値を超えたインデックス名と閾値を入れるためです。
:::

Trigger conditionにコードを入力したら、「Preview condition response」を押してみてください。閾値以上のインデックスサイズがあれば、右のresponseに`true`が返るはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/0851cd74-4d3c-3a40-52c2-cb13e2091663.png)

### Actions の設定
ここでは、アラート時のメールの内容を記載します。
Messageに以下のように記載します。

```handlebars
{{#ctx.results.0.largeIndices.0}}
以下のインデックスが{{ctx.results.0.thresholdGB}}GB以上のサイズに達しています：

{{/ctx.results.0.largeIndices.0}}
{{#ctx.results.0.largeIndices}}
- インデックス名: {{name}}
- サイズ: {{size}}

{{/ctx.results.0.largeIndices}}
{{^ctx.results.0.largeIndices}}
{{ctx.results.0.thresholdGB}}GB以上のサイズのインデックスは見つかりませんでした。

{{/ctx.results.0.largeIndices}}
詳細な情報はOpenSearch Dashboardsで確認してください。
```

`{{#ctx.results.0.largeIndices}}` から `{{/ctx.results.0.largeIndices}}` と囲うことで、for文のようにループを回すことが出来ます。largeIndiciesは1つ前のTriggerでlistとして定義しているため、閾値以上のサイズのインデックスの名前とサイズがメールに記載されます。

ここの記載方法は以下に詳細が書かれているので、ご興味ある方はご覧ください。

https://mustache.github.io/mustache.5.html

この状態で、Preview messageにチェックを入れると、閾値を超えるインデックスが存在する場合、以下のように表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/51cb4bda-84db-aa63-7c6b-7b0d4a269a1e.png)

あとはMonitorを作成すれば完了です。

### 実際にアラートを飛ばしてみる
以下のようなメールが飛んでいます。
ただ、インデックスサイズは基本減らないので、何も処理をしないとアラートが上がり続けることに注意しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/ae362d18-4429-1309-034f-82c113d8354b.png)

# おわりに
OpenSearchのAlertingに関しては、あまり情報がないですが、やりたい要件を実装できてよかったです。
個人的に、Triggerで変数を新規作成して、それをMessageに使えるというのは知らなかったです。そんな使い方ができるとは…。OpenSearchのAlerting、侮れないですね。

ちなみに、今回は（も?）TriggerやMessageでのコーディング内容は生成AIをふんだんに使いました。なければ実装できなかったと思います。こういう情報があまりない分野では、ハルシネーションをちょくちょく起こすとは言え、特に助かりますね。