👻

OpenSearch Serviceのクラスターを複製する方法

2023/03/06に公開

はじめに

みなさんAWS OpenSearch Service(Elasticsearch)は使ってますか?
先日OpenSearchのクラスターを複製してコピー環境を作ったのですが、この際に若干ハマってしまい色々調べたので、手順整理の意味も込めて対応方法を書いておきたいと思います。

OpenSearchのクラスタ複製について

OpenSearchには自動スナップショット取得機能があります。1時間おきに自動でクラスタのバックアップを取得してくれるので非常に便利なのですが、こちらは同じクラスタ内でしか使えないので、基本的な用途としては「何かあった時にデータを復元する」といった形になるかと思います。
今回やりたかったことは別のクラスタにデータを移す(同じデータを持ったクラスタを複製する)ことですが、これをやるためには手動でスナップショットを取得しなければなりません。
最初は「まあRDSと同じようなもんだろ。スナップショット取って復元するだけだから楽勝」と考えていたのですが、そう甘い話でもなかったので以下詳細について記載しておきたいと思います。
なお今回の作業内容は概ね以下のAWSドキュメントをベースにしていますので、興味のある方はご覧ください。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/managedomains-snapshots.html#es-managedomains-snapshot-registerdirectory

今回の構成

OpenSearchの手動スナップショットは基本的にS3バケットに保存する必要があります。S3を中心にして、コピー元でスナップショットを作成し、コピー先へリストアするイメージです。ざっくりした構成はこのような形です。

いくらか端折っていますが、大筋の流れはのような感じです。

項番 大筋の流れ
EC2インスタンス上でPythonスクリプトを実行し、手動スナップショット保存用のリポジトリ(宛先はS3)を登録する。ちなみに必ずしもEC2を使う必要はなく、ローカルPCから直接スクリプトを実行してもOK。ただし、今回の記事はEC2を前提とした手順になっているのでご注意を。
コピー元のOpenSearchクラスタ上でスナップショット取得用のクエリ(API)を実行し、S3へ保存する。
コピー先のクラスタ上でリストア用のクエリ(API)を実行し、スナップショットからのリストアを行う。

作業手順

それでは早速やっていきましょう。

S3バケットの作成

まずはスナップショットの保存先(リポジトリ)として使用するS3バケットを作成します。
作り方に関しては色んなところに記事があるので割愛しますが、ここでのバケット名は「bucket-es-repo-snapshot」としました。

権限設定①:スナップショット取得用のロール作成

S3へスナップショットを保存するにあたり、IAMロールやポリシーの設定など各種権限設定が必要になります。
まずは新規のロールを1つ作成します。今回は名前を「role-es-repo-snapshot」としました。
スナップショット取得時にはOpenSearchからS3へアクセスする必要があるのですが、この際に必要な権限をポリシーとして記述し、今回作成するロールに割り当てます。具体的なポリシーの内容は以下になります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::bucket-es-repo-snapshot"    ※前段のバケット名を指定
            ]
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::bucket-es-repo-snapshot/*"    ※前段のバケット名を指定
            ]
        }
    ]
}

インラインポリシーとして直接設定するか、または任意の名称で管理ポリシーを作成してロールにアタッチするか、どちらかで対応してください。
なお本ロールを作成する際のユースケースは「Amazon OpenSearch Service 」を選択してください。もしEC2など別のものを選択してしまった場合、ロールを作成した後に「信頼関係」を編集して以下の通り設定すれば大丈夫です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "es.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

権限設定②:スクリプト実行用のポリシー作成とアタッチ

前述の通り、今回はEC2インスタンス上でPythonスクリプトを実行することで、OpenSearchに対してスナップショット保存用のリポジトリ(S3)を登録する流れになります。
このスクリプトの実行にあたり、専用のポリシーを1つ作成してスクリプト実行ユーザ(今回でいくとEC2に割り当てたロール)にアタッチする必要があります。ポリシーの内容は以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::[アカウント番号]:role/role-es-repo-snapshot"
        },
        {
            "Effect": "Allow",
            "Action": "es:ESHttpPut",
            "Resource": "[コピー元OpenSearchのARN]/*"
        },
        {
            "Effect": "Allow",
            "Action": "es:ESHttpPut",
            "Resource": "[コピー先OpenSearchのARN]/*"
        }
    ]
}

定義は計3つ記載しています。一番上の定義により、先程作成したロール(role-es-repo-snapshot)をOpenSearchに対して割り当てるための権限(PassRole)を得ることができます。また、下の2つの定義でes:ESHttpPutの実行権限を追加しています。
ポリシー作成後、EC2のロールにアタッチしましょう。または先程と同じく、インラインポリシーで直接設定してもOKです。

スナップショット用のリポジトリ登録(Pythonスクリプト実行)

続いてリポジトリ登録用のPythonスクリプトを実行します。スクリプトの中身は以下の通りです。カッコつきの数字部分は環境依存なので適宜変更ください。

import boto3
import requests
from requests_aws4auth import AWS4Auth

region = 'ap-northeast-1'
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

# ESドメインのエンドポイント(各自の環境に従い見直すこと)・・・(1)
host = 'https://xxxxxx.ap-northeast-1.es.amazonaws.com/'

# スナップショットリポジトリの名前(スラッシュから右側には任意の名前を指定)・・・(2)
path = '_snapshot/index-repo-s3'
url = host + path

# リポジトリ取得先のバケット名(各自の環境に従い見直すこと)・・・(3)
backetName = 'bucket-es-repo-snapshot'

# ロールARN(各自の環境に従い見直すこと)・・・(4)
roleArn = 'arn:aws:iam::[アカウント番号]:role/role-es-repo-snapshot'

# リポジトリ登録処理
payload = {
    "type": "s3",
    "settings": {
        "bucket": backetName,
        "region": region,
        "role_arn": roleArn
    }
}
headers = {"Content-Type": "application/json"}
res = requests.put(url, auth=awsauth, json=payload, headers=headers)

print(res.status_code)
print(res.text)

これをEC2上の任意のディレクトリに保存して実行します。Pythonの実行に必要なモジュール等は事前にインストールしておいてください。
実行結果として以下がコンソールに出力されればOKです。

200
{"acknowledged":true}

コピー元、コピー先それぞれのOpenSearchに対して上記スクリプトを実行してください。
※具体的には、上記(1)のエンドポイント名を変更して、計2回スクリプトを実行する感じです

コピー元クラスタでのスナップショット取得

前準備が長くなりましたが、これからようやくスナップショットの取得を行います。作業はコピー元のOpenSearchクラスタで実施します。まずはリポジトリがちゃんと登録されているか確認してみましょう。確認用のクエリは以下の通りです。

GET /_cat/repositories?v

※実行結果
id                      type
cs-automated-enc          s3
index-repo-s3             s3

OpenSearchは基本的にREST API(https)で操作するのでcurlコマンドなんかでもいけるのですが、今回はOpenSearch Dashboard(Kibana)のDev Toolsを使いました。OpenSearch Dashboardへアクセスし、左上のハンバーガーメニューから「Dev Tools」を選択すればクエリ実行画面が開くので、上記のクエリをそのままコピペすれば実行できます。
ちなみに上の実行結果部分に表示されている「cs-automated-enc」は自動スナップショット用のリポジトリです。結果の2番目に今回のリポジトリ名(index-repo-s3)が表示されているので、無事登録が行われてるのが分かります。

では続いてスナップショットを作成してみましょう。以下のクエリを実行します。

PUT /_snapshot/index-repo-s3/[任意のスナップショット名]

※実行結果
{
  "accepted" : true
}

スナップショットの取得にはしばらく時間がかかるので待ちましょう。結果の確認は以下のクエリから行えます。実行結果のstateがSUCCESSになっていれば無事取得完了です。

GET /_snapshot/index-repo-s3/_all

※実行結果
{
  "snapshots" : [ {
    "snapshot" : "スナップショット名",
    "uuid" : "XXXXXXXXXXX",
    "version_id" : 1111111,
    "version" : "7.10.2",
    "indices" : [ スナップショットに含まれるインデックスの一覧 ],
    "data_streams" : [ ],
    "include_global_state" : true,
    "state" : "SUCCESS",
    ~~以下略~~

コピー先クラスタへのリストア

コピー先クラスタでも同様、OpenSearch Dashboard(Kibana)のDev Toolsを開きます。
まずは以下のクエリを実行し、先ほど取得したスナップショットが参照できるか確認しましょう。

GET /_snapshot/index-repo-s3/_all

※実行結果
{
  "snapshots" : [ {
    "snapshot" : "スナップショット名",
    "uuid" : "XXXXXXXXXXX",
    "version_id" : 1111111,
    "version" : "7.10.2",
    ~~以下略~~

上記の通り、見えていますね。では続けてリストアを実行します。クエリは以下の通りです。

POST /_snapshot/index-repo-s3/[スナップショット名]/_restore

※実行結果
{
  "error" : {
    "root_cause" : [
      {
        "type" : "snapshot_restore_exception",
        "reason" : "[*****] cannot restore index [.kibana_1] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name"
      }
    ],
    "type" : "snapshot_restore_exception",
    "reason" : "["[*****]] cannot restore index [.kibana_1] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name"
  },
  "status" : 500
}

が、エラーになってしまいました。。
調べてみたところ、Elasticsearchのバージョン5.1以降では.kibanaインデックスを常時モニタリングしているようで、リストアの際に必ずエラーになってしまうようです。(OpenSearchはElasticsearch7.10からフォークされているので同じ現象が発生します)
対応方法はいくつかあると思いますが、今回はエラーの原因となる.kibana関連のインデックスをリストア対象から除外することにしました。クエリは以下の通りで、「indices: "-.kibana*"」の構文を追加しています。

POST /_snapshot/index-repo-s3/[スナップショット名]/_restore
{
    indices: "-.kibana*"
}

※実行結果
{
  "accepted" : true
}

今度はうまく行きました。これでリストア作業は完了です。お疲れ様でした。

さいごに

今回はOpenSearchでクラスタをコピー(複製)する方法をご紹介しました。
RDSなんかだと割と簡単に対応できるのですが、見て頂いた通りOpenSearchではリポジトリの登録や権限周りの設定が必要なので色々大変な印象です。RDSと同じくマウスポチポチだけで対応できるよう、今後の機能追加に期待したいと思います。
この記事が誰かのお役に立てると幸いです。

Discussion