AWS

Amazon Elasticsearch Serviceでインデックスを作成するまで

実現したいこと

WEB アプリケーション内で全文検索をセキュアに行いたいという案件がありました。DB として使用している DynamoDB はクエリが弱いため Alglia を使うか Elasticsearch を使うか検討し、結果 Amazon Elasticsearch Service の導入に決定しました。

この記事のゴール

Amazon Elasticsearch Service(AES)にインデックスを作成するところまでです。開発メモの側面もありますので読みづらい記事になっているかと思いますがご容赦いただければと思います。

この記事に書いてあること

  • AWS ドメインの登録
  • Lambda 関数の作成
  • IAM ロール、ポリシーの作成
  • セキュリティグループの作成

書いていないこと

  • 数々のデバッグ…

流れ

以降 Amazon Elasticsearch Service を AES と略します。

AES ドメインを登録してみる

基本的には流れに沿えばドメインは作成できると思います。

  • インスタンスは必要最小限に、ミニマムスタート。
  • アベイラビリティーゾーンは 2-AZ。
  • マスターインスタンスはデフォルトのまま作成。

どこでインデックスの登録テストを行うか?

テスト登録のためにどこで HTTP リクエストを叩いて検証するか迷いました。 AES は VPC 内 Lambda 関数から呼ぶ形になりますので、外部からアクセスするのは少々面倒です。

https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-indexing.html

最初は Postman を使って試そうとしましたが、SignerV4 の認証設定が面倒だったため Lambda 関数としました。 では Lambda で実装するにあたり、まずロールの検討が必要です。 今回最低限必要なのは AES へのアクセス権限と CloudWatch への権限です。 つまり、以下のようなポリシーを作成しそのポリシーを包含したロールを作成することになります。 下記例では AES の各種権限を与えていますが、本番環境では必要な権限のみ付与するようにしましょう。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": ["*"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "es:ESHttpPost",
        "es:ESHttpGet",
        "es:ESHttpPut",
        "es:ESHttpDelete"
      ],
      "Resource": "arn:aws:es:ap-northeast-1:{ID}:domain/{DOMAIN}/*"
    }
  ]
}

このロールを Lambda 関数に与え、関数の初期設定を終えます。

Lambda 関数は以下 URL を参考に Lambda 関数として手直しします(機会があれば Lambda 関数を Github で公開します)。 https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-node

ここでインデックスを実際に登録してみますが中々うまくいきません。AWS サポートにも問い合わせを行い、以下のアドバイスをいただきました。

  • AES のエンドポイント設定を再確認(VPC 内に AES ドメインを配置した場合、vpc から始まる)。
  • AES に登録した VPC に HTTPS(443)のインバウンドが設定されていない。
  • Lambda 関数は VPC 内に配置すること。
  • それでも駄目なら AES ドメインのアクセスポリシーを一時的にフルアクセスにして問題の切り分け。

AES のエンドポイント

ドキュメントを見ると、以下のように書いてあります。 まずここで引っかかりました。

正解は以下のように AES の VPC エンドポイント名が正しいです。search-も不要です。ただ、どこにも記載がなくハマりました。

{VPCドメイン名}.ap-northeast-1.es.amazonaws.com

AES ドメインのアクセスポリシー

アクセスポリシー(フルアクセス)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:{ID}:domain/{DOMAIN}/*"
    }
  ]
}

最終形態はこちら 指定ロール以外からのアクセスを遮断するというポリシーです。即ち、このロールを当てている Lambda 関数からのみ AES ドメインへアクセスができるということになります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::{ID}:role/{Lambdaに当てたロール名}"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:{ID}:domain/{DOMAIN}/*"
    }
  ]
}

VPC

結論、以下のように Lambda 関数で設定しました。

セキュリティグループは、HTTPS のみインバウンドを有効にしたものを作成します。 この設定内容については、AES 側の設定も合わせておく必要があると思います。

ここまでで、何とかインデックス登録に成功しましたのでデバッグ用の処理を戻していきましょう。

  • ポリシーを厳格に(”“から”ロール”に。(ただ、EC2 インスタンスからデバッグ用の curl を叩きたかったので、開発時は”“としていました。VPC 内からしかアクセスできないようにしているので、EC2 インスタンスに SSH 接続して弄るのは検証フェーズにおいては楽ですね。)

本番稼働に向けて

実際のデータの投入テストを行います。 とりあえず以下のようなデータを入れてみましょう。

{
  "user": {
    "first_name": "テスト"
  }
}

しかしここでエラーが発生します。エラー内容が認証のことを言っています。

The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

動的マッピングではなく手動マッピングを試してみるなど、その他考えられる範囲でデバッグしましたが結果は変わらずです。ちなみに、一度 INDEX を作ると動的にマッピングを作るため、データ形式を変えるとデータが登録できなくなります。そのため、以下のコマンドでインデックスを削除しながら作業を行うのが確実です。

curl -XDELETE https://{ドメイン名}.ap-northeast-1.es.amazonaws.com/orders/?pretty=true

ちなみにマッピングを確認する方法は以下の通りです。 ?pretty=trueは返却される json を整形してくれます。

curl --XGET https://{ドメイン名}.ap-northeast-1.es.amazonaws.com/orders/_mapping?pretty=true

ここで、試しに以下のようにすると登録をパスすることが判明しました。全角だと通らないのでしょうか?最初に全角でトライしたのは間違いでした。

{
  "user": {
    "first_name": "aaa"
  }
}

結論としては、全角文字を含むリクエストの中に Content-Length があるとエラーになります。原因は Lambda の aws-sdk v2 の SignerV4 に問題があるためでした。英語以外の言語をリクエストに含む場合に署名バージョン 4 の計算に問題が発生します。

回避策としては AWS SDK 3.0(開発者プレビュー)にするか、サードパーティライブラリを使用するかのいずれかとなります。

終わりに

ここまでで、テストデータをインデックスに登録するところまでは成功しました。 本番環境に向けては、Lambda 関数に AppSync を通してデータを受け渡すようにするなど、各アプリに合わせて組み込みを進めていきましょう。

関連記事