CloudFormationを使用して、OpenSearch Serverlessを作成する

CloudFormationを使用して、OpenSearch Serverlessを作成する

CloudFormationを使用して、OpenSearch Serverlessを作成する

本ページでは、OpenSearch Serverlessを取り上げます。

Amazon OpenSearch Serverless は、Amazon OpenSearch Service 用のオンデマンドサーバーレス設定です。サーバーレスにより、OpenSearch クラスターのプロビジョニング、設定、チューニングの運用上の複雑さがなくなります。

Amazon OpenSearch Serverless とは

今回はCloudFormationを使用して、OpenSearch Serverlessを作成し、IAM UserおよびLambda関数から同リソースにアクセスします。

構築する環境

Diagram of ceating OpenSearch Serverless using CloudFormation.

OpenSearch Serverlessを作成します。

IAMユーザを作成し、OpenSearch Serverlessのダッシュボードにアクセスします。

OpenSearch ServerlessにアクセスするLambda関数を2つ作成します。
一方の関数の働きは、OpenSearch Serverlessにインデックスを作成することです。
もう一方の関数の働きは、OpenSearch Serverlessにドキュメントをインデクシングすることです。

なおLambda関数のランタイム環境はPython3.8です。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/147

テンプレートファイルのポイント解説

OpenSearch Serverless

以下のAWS公式サイトを参考にして、CloudFormationテンプレートを作成します。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-cfn.html

OpenSearch Serverlessに関して、以下の4つのリソースを作成します。

  • コレクション
  • 暗号化ポリシー
  • データアクセスコントールポリシー
  • ネットワークアクセスポリシー

コレクション

コレクションとは、OpenSearch Serverlessのメインリソースです。

Amazon OpenSearch Serverless のコレクションは、分析ワークロードを表す 1 つまたは複数のインデックスを論理的にグループ化したものです。

Amazon OpenSearch Serverless コレクションの作成、一覧表示、および削除
Resources:
  Collection:
    Type: AWS::OpenSearchServerless::Collection
    DependsOn:
      - EncryptionSecurityPolicy
    Properties:
      Name: !Ref CollectionName
      StandbyReplicas: DISABLED
      Type: TIMESERIES
Code language: YAML (yaml)

ポイントは2点あります。

1点目ははTypeプロパティです。
以下の3つの中から選択できます。

[Time series] – 運用、セキュリティ、ユーザー行動、およびビジネスに関するインサイトを得るために、マシン生成による大量の半構造化されたデータをリアルタイムで分析することに重点を置いたログ分析セグメント。

[Search] – 社内ネットワーク内のアプリケーション (コンテンツ管理システム、法的文書) や、e コマースウェブサイト検索やコンテンツ検索などのインターネット向けアプリケーションを強化する全文検索。

[ベクトル検索] – ベクトルデータ管理を簡素化し、機械学習 (ML) によって拡張された検索エクスペリエンスと、チャットボット、パーソナルアシスタント、不正検出などの生成系 AI アプリケーションを強化する、ベクトル埋め込みのセマンティック検索。

コレクションタイプを選択する

時系列と検索の大きな違いはデータのキャッシュのされ方です。

[Search] – 社内ネットワークのアプリケーションやインターネットに接続するアプリケーションに使用される全文検索。すべての検索データはホットストレージに保存され、クエリの応答時間を短縮できます。

[Time series] – マシン生成の大量の半構造化データの分析に焦点を当てたログ分析セグメント。少なくとも 24 時間のデータはホットストレージにキャッシュされ、残りはウォームストレージに残ります。

コレクションの作成

今回は「TIMESERIES」を指定して、時系列タイプのコレクションを作成します。

2点目はリソースの作成順序です。
後述する暗号化ポリシーを作成した後でないと、OpenSearch Serverlessコレクションは作成できません。
この順番を守らない場合、以下のようなエラーが発生します。

No matching security policy of encryption type found for collection name: [collection-name]. Please create security policy of encryption type for this collection.

この仕様に対応するため、DependsOnプロパティを使用して、暗号化ポリシーが作成された後に、本リソースが作成されるように設定します。

暗号化ポリシー

暗号化ポリシーを作成することで、OpenSearch Serverless内のデータの暗号化に関する設定を行います。

ユーザーが作成した各 Amazon OpenSearch Serverless コレクションでは、データへの不正アクセスを防止するための、暗号化を使用したセキュリティ機能により、保管中のデータが保護されます。保管時の暗号化では、AWS Key Management Service (AWS KMS) を使用して、暗号化キーの保存と管理を行います。

Amazon OpenSearch Serverless の暗号化
Resources:
  EncryptionSecurityPolicy:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties:
      Name: !Sub "${Prefix}-encryption-policy"
      Policy: !Sub >-
        {"Rules":[{"ResourceType":"collection","Resource":["collection/${CollectionName}"]}],"AWSOwnedKey":true}
      Type: encryption
Code language: YAML (yaml)

Policyプロパティで暗号化ポリシーの内容を定義します。

記法については以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-encryption.html

今回のポリシーを確認します。
Resourceに先述のOpenSearch Serverlessコレクションを指定し、本ポリシーの対象を同リソースとします。
そしてAWSOwnedKeyにtrueを指定することで、暗号化にAWSが所有するKMSキーを使用するように設定します。

データアクセスポリシー

データアクセスポリシーを作成することによって、IAMユーザ等に対して、OpenSearch Serverlessへのアクセス権限をきめ細やかに設定することができます。

Amazon OpenSearch Serverless のデータアクセスコントロールを使用すると、アクセスメカニズムやネットワークソースに関係なく、ユーザーがコレクションやインデックスにアクセスできるようにすることができます。

Amazon OpenSearch Serverless のデータアクセスコントロール
Resources:
  DataAccessPolicy1:
    Type: AWS::OpenSearchServerless::AccessPolicy
    Properties:
      Name: !Sub "${Prefix}-data-policy-01"
      Policy: !Sub >-
        [{"Description":"Access for cfn user","Rules":[{"ResourceType":"index","Resource":["index/*/*"],"Permission":["aoss:*"]},
        {"ResourceType":"collection","Resource":["collection/${CollectionName}"],"Permission":["aoss:*"]}],
        "Principal":["${User1Arn}"]}]
      Type: data
Code language: YAML (yaml)

Policyプロパティでデータアクセスポリシーの内容を定義します。

記法については以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-data-access.html

今回のポリシーを確認します。
OpenSearch Serverlessインデックスへの全アクションを許可します。
またコレクションへの全アクションも許可します。
Principalに後述するIAMユーザ1を指定します。

ネットワークアクセスポリシー

ネットワークアクセスポリシーについて、AWS公式では以下の通りに説明しています。

Amazon OpenSearch Serverless コレクションでは、ネットワーク設定によって、そのコレクションに対しインターネット経由でパブリックネットワークからのアクセスが可能か、OpenSearch Serverless が管理する VPC エンドポイントからアクセスする必要があるかが決まります。

Amazon OpenSearch Serverless でのネットワークアクセス
Resources:
  NetworkSecurityPolicy:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties:
      Name: !Sub "${Prefix}-network-policy"
      Policy: !Sub >-
        [{"Rules":[{"ResourceType":"collection","Resource":["collection/${CollectionName}"]},
        {"ResourceType":"dashboard","Resource":["collection/${CollectionName}"]}],"AllowFromPublic":true}]
      Type: network
Code language: YAML (yaml)

Policyプロパティでネットワークアクセスポリシーの内容を定義します。

記法については以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-network.html

今回のポリシーを確認します。
AllowFromPublicに「true」を指定することによって、本コレクションやダッシュボードに対して、インターネットから直接アクセスできるようになります。

IAMユーザ

Resources:
  User1:
    Type: AWS::IAM::User
    Properties:
      LoginProfile:
        Password: !Ref Password
      Policies:
        - PolicyName: AllAllowPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - aoss:*
                Resource: "*"
      UserName: !Sub "${Prefix}-user-01"

  User2:
    Type: AWS::IAM::User
    Properties:
      LoginProfile:
        Password: !Ref Password
      Policies:
        - PolicyName: AllAllowPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - aoss:*
                Resource: "*"
      UserName: !Sub "${Prefix}-user-02"
Code language: YAML (yaml)

2ユーザ作成します。
設定は両ユーザとも同一です。

先述の通り、データアクセスポリシーによって、IAMユーザ1に対してのみコレクションやダッシュボードへのアクセスを許可しています。

Lambda関数

作成したOpenSearch ServerlessにLambda関数からアクセスします。
関数で実行するコードは以下のページを参考にしています。

https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-clients.html#serverless-python

https://opensearch.org/docs/latest/clients/python-low-level/

関数1

Resources:
  Function2:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          COLLECTION_ENDPOINT: !Sub "${Collection}.${AWS::Region}.aoss.amazonaws.com"
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          from opensearchpy import OpenSearch, RequestsHttpConnection, AWSV4SignerAuth
          import boto3
          import os

          host = os.environ['COLLECTION_ENDPOINT']
          region = os.environ['REGION']

          service = 'aoss'
          credentials = boto3.Session().get_credentials()
          auth = AWSV4SignerAuth(credentials, region, service)

          client = OpenSearch(
            hosts=[{'host': host, 'port': 443}],
            http_auth=auth,
            use_ssl=True,
            verify_certs=True,
            connection_class=RequestsHttpConnection,
            pool_maxsize=20,
            )

          def lambda_handler(event, context):
            index_name = "python-test-index"
            create_response = client.indices.create(
              index_name
            )
            print(create_response)

            document = {
              'title': 'Moneyball',
              'director': 'Bennett Miller',
              'year': '2011'
              }

            index_response = client.index(
              index=index_name,
              body=document
              )
            print(index_response)
      FunctionName: !Sub "${Prefix}-function-02"
      Handler: !Ref Handler
      Layers:
        - !Ref LambdaLayer
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole2.Arn
Code language: YAML (yaml)

関数内で実行するコードをインライン形式で記載します。

CloudFormationを使用して、Lambda関数を作成する方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

この関数はOpenSearch Serverlessにデータを保存します。
具体的には、OpenSearch Serverless用のクライアントオブジェクトを作成後、以下の2つのアクションを実行します。

  • クライアントのindices.createメソッドを使用して、インデックス作成
  • クライアントのindexメソッドを使用して、ドキュメントのインデクシング

ドキュメントは参考サイトに記載されていたサンプルデータです。

関数2

Resources:
  Function3:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          COLLECTION_ENDPOINT: !Sub "${Collection}.${AWS::Region}.aoss.amazonaws.com"
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          from opensearchpy import OpenSearch, RequestsHttpConnection, AWSV4SignerAuth
          import boto3
          import os

          host = os.environ['COLLECTION_ENDPOINT']
          region = os.environ['REGION']

          service = 'aoss'
          credentials = boto3.Session().get_credentials()
          auth = AWSV4SignerAuth(credentials, region, service)

          client = OpenSearch(
            hosts=[{'host': host, 'port': 443}],
            http_auth=auth,
            use_ssl=True,
            verify_certs=True,
            connection_class=RequestsHttpConnection,
            pool_maxsize=20,
            )

          def lambda_handler(event, context):
            index_name = "python-test-index"
            q = 'miller'
            query = {
              'size': 5,
              'query': {
                'multi_match': {
                  'query': q,
                  'fields': ['title^2', 'director']
                }
              }
            }

            search_response = client.search(
              body=query,
              index=index_name
              )
            print(search_response)
      FunctionName: !Sub "${Prefix}-function-03"
      Handler: !Ref Handler
      Layers:
        - !Ref LambdaLayer
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole3.Arn
Code language: YAML (yaml)

この関数は、OpenSearch Serverlessのデータを検索します。
具体的には、クライアントのindices.searchメソッドを使用します。
こちらも参考サイトに記載されていたサンプルデータを検索します。

IAMロール

Resources:
  FunctionRole2:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: FunctionRole2Policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - aoss:APIAccessAll
                Resource:
                  - !Sub "arn:aws:aoss:${AWS::Region}:${AWS::AccountId}:collection/${Collection}"
Code language: YAML (yaml)

両関数用のIAMロールです。
上記は関数2用ですが、関数3用のIAMロールもポリシーは全く同じです。

ポイントはインラインポリシーで指定している「aoss:APIAccessAll」です。

2023 年 5 月 10 日以降、OpenSearch Serverless では、コレクションリソースにこれら 2 つの新しい IAM 許可が必要になります。aoss:APIAccessAll のアクセス許可はデータプレーンアクセスを許可し、aoss:DashboardsAccessAll アクセス許可はブラウザから OpenSearch Dashboards を許可します。2 つの新しい IAM アクセス権限を追加しなかった場合、403 エラーが表示されます。

Amazon OpenSearch Serverless 向けの アイデンティティとアクセス管理

両関数はインデックス作成や検索を行いますから、前者を許可する必要があります。

データアクセスポリシー

Resources:
  DataAccessPolicy2:
    Type: AWS::OpenSearchServerless::AccessPolicy
    DependsOn:
      - Function2
    Properties:
      Name: !Sub "${Prefix}-data-policy-02"
      Policy: !Sub
        - >-
          [{"Description":"Access for Function2","Rules":[{"ResourceType":"index","Resource":["index/*/*"],"Permission":["aoss:CreateIndex","aoss:WriteDocument","aoss:UpdateIndex"]}],
          "Principal":["${FunctionRole2Arn}"]}]
        - FunctionRole2Arn: !GetAtt FunctionRole2.Arn
      Type: data

  DataAccessPolicy3:
    Type: AWS::OpenSearchServerless::AccessPolicy
    DependsOn:
      - Function3
    Properties:
      Name: !Sub "${Prefix}-data-policy-03"
      Policy: !Sub
        - >-
          [{"Description":"Access for Function3","Rules":[{"ResourceType":"index","Resource":["index/*/*"],"Permission":["aoss:ReadDocument"]}],
          "Principal":["${FunctionRole3Arn}"]}]
        - FunctionRole3Arn: !GetAtt FunctionRole3.Arn
      Type: data
Code language: YAML (yaml)

IAMユーザーと同様に、Lambda関数用にもデータアクセスポリシーを作成します。

関数2はOpenSearch Serverlessにデータを投入しますから、書き込み系の権限を与えます。
関数3はデータを検索しますから、読み込み用の権限を与えます。

(参考)CloudFormationカスタムリソースを使用して、Lambdaレイヤーを自動的に作成する

PythonからOpenSearch Serverlessにアクセスするために、AWS公式が提供しているパッケージ(opensearch-py)を使用します。
このパッケージはデフォルトのLambda関数のランタイム環境には含まれていません。
今回はLambdaレイヤーを作成し、これを含めることによって、同パッケージを使用できるようにします。

今回はCloudFormationカスタムリソースを使用して、自動的にLambdaレイヤーを作成します。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CFNカスタムリソースでLambdaレイヤーパッケージを準備する – Python版 【CloudFormationカスタムリソースを使って、Python用のLambdaレイヤーパッケージを自動的に作成・配置する】 以下のページでLambdaレイヤーの作成方法について取り上げ...

上記のページでは、pipでインストールするパッケージをSSM Parameter Storeで指定します。
今回は同リソースを以下のように指定します。

Resources:
  RequirementsParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Ref Prefix
      Type: String
      Value: |
        urllib3==1.26.6
        opensearch-py
Code language: YAML (yaml)

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

今回は名前をつけるIAMリソース(IAMユーザ)を作成しますから、以下の通りにオプションを設定します。

$ aws cloudformation create-stack \
--stack-name [stack-name] \
--template-url https://[bucket-name].s3.[region].amazonaws.com/[folder-name]/fa-147.yaml \
--capabilities CAPABILITY_NAMED_IAM
Code language: Bash (bash)

AWS Management ConsoleからOpenSearch Serverlessコレクションを確認します。

Detail of OpenSearch Serverless 1.

コレクションが正常に作成されています。

ネットワークアクセスポリシーを確認します。

Detail of OpenSearch Serverless 2.

このコレクションがパブリックに公開されていることがわかります。

データアクセスポリシーを確認します。
3つありますので順番に確認します。

Detail of OpenSearch Serverless 3.

1つ目のポリシーはIAMユーザ1に対して全アクションを許可する内容です。

Detail of OpenSearch Serverless 4.

2つ目のポリシーは関数2にテストデータを書き込むための権限を与える内容です。

Detail of OpenSearch Serverless 5.

3つ目のポリシーは関数3にデータを検索するための権限を与える内容です。

動作確認

準備が整いましたので、実際の動作を確認します。

データを書き込む

関数2を実行してテストデータを書き込みます。

Detail of Lambda 1.

ログを見ると、確かにログが書き込まれていることがわかります。
正常にインデックス作成と追加ができました。

データを検索する

続いて書き込んだデータを検索します。

Detail of Lambda 2.

ログを見ると、確かに書き込んだデータが返ってきました。
正常に検索が実行できました。

ダッシュボード

IAMユーザからOpenSearch Serverlessダッシュボードにアクセスします。

まずIAMユーザ2でアクセスを試みます。

Detail of OpenSearch Serverless 6.

アクセスできませんでした。
これはIAMユーザ2が使用しているIAMロール的には問題ありませんが、データアクセスポリシーが設定されていないためです。
このようにOpenSearch Serverlessを使用するユーザは、IAMロール等のIAMポリシーだけでなく、データアクセスポリシーも適切に設定する必要があります。

続いてIAMユーザ1でアクセスします。

Detail of OpenSearch Serverless 7.

正常にアクセスできました。
IAMユーザ1はデータアクセスポリシーが適切に設定されているため、ダッシュボードにアクセスすることができました。

最後にダッシュボードから検索を行います。

Detail of OpenSearch Serverless 8.

書き込んだテストデータが返されました。
このようにOpenSearch Serverlessでは、ダッシュボードも使用することができます。

まとめ

CloudFormationを使用して、OpenSearch Serverlessを作成し、IAM UserおよびLambda関数から同リソースにアクセスしました。