OpenSearch ServerlessをCDKで構築してみた

OpenSearch ServerlessコレクションをCDKを用いて構築してみたので、そのコードを紹介します。 その過程でいくつかつまずいたポイントがあったので、それらについても説明します。
2024.01.02

こんにちは!CX事業本部のたにもんです。

先日、Amazon Bedrockの Knowledge baseAgents がGAになりました。 これらはナレッジベースのデータストアとして OpenSearch Serverless との統合を提供しています。 そのため、今後生成AIアプリケーションを作成する際にOpenSearch Serverlessを利用する機会が増えることが予想されます。

そこで今回はOpenSearch ServerlessコレクションをCDKを用いて構築してみたので、そのコードを紹介します。 また、その過程でいくつかつまずいたポイントがあったため、それらについても説明します。

環境

  • node: 20.0.0
  • typescript: 5.3.3
  • aws-cdk-lib: 2.117.0
  • constructs: 10.3.0

CDKコード

今回作成したCDKコードの全体像は以下のとおりです。 執筆時点(2024-01-02現在)ではOpenSearch ServerlessのL2コンストラクトは公開されていなかったので、L1コンストラクトを利用しています。

import {
  Duration,
  Stack,
  StackProps,
  aws_opensearchserverless,
} from 'aws-cdk-lib';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Architecture, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Construct } from 'constructs';

export class MainStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const knowledgeBaseCollection = new aws_opensearchserverless.CfnCollection(
      this,
      'KnowledgeBaseCollection',
      {
        name: 'knowledge-base',
        type: 'VECTORSEARCH',
        standbyReplicas: 'DISABLED',
      }
    );

    const knowledgeBaseCollectionEncryptionPolicy =
      new aws_opensearchserverless.CfnSecurityPolicy(
        this,
        'KnowledgeBaseCollectionEncryptionPolicy',
        {
          name: 'knowledge-base-encryption-policy',
          type: 'encryption',
          policy: JSON.stringify({
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
            ],
            AWSOwnedKey: true,
          }),
        }
      );

    // NOTE: コレクションを作成する前に、コレクションの名前と一致するリソースパターンを含む暗号化ポリシーを作成しておく必要がある
    // 暗号化ポリシー作成後にコレクションが作成されるように依存関係を設定する
    // @see https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-manage.html#serverless-create
    // @see https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-encryption.html
    knowledgeBaseCollection.addDependency(
      knowledgeBaseCollectionEncryptionPolicy
    );

    new aws_opensearchserverless.CfnSecurityPolicy(
      this,
      'KnowledgeBaseCollectionNetworkPolicy',
      {
        name: 'knowledge-base-network-policy',
        type: 'network',
        policy: JSON.stringify([
          {
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
              {
                ResourceType: 'dashboard',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
            ],
            AllowFromPublic: true,
          },
        ]),
      }
    );

    new aws_opensearchserverless.CfnAccessPolicy(
      this,
      'KnowledgeBaseCollectionAccessPolicy',
      {
        name: 'knowledge-base-access-policy',
        type: 'data',
        policy: JSON.stringify([
          {
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
                Permission: ['aoss:*'],
              },
              {
                ResourceType: 'index',
                Resource: [`index/${knowledgeBaseCollection.name}/*`],
                Permission: ['aoss:*'],
              },
            ],
            Principal: [
              'arn:aws:iam::xxxxxxxxxxxx:role/xxxxx', // コレクションにアクセスする際に利用するIAMユーザーやIAMロールのARNを指定する
            ],
          },
        ]),
      }
    );
  }
}

次のセクションからここで作成している各リソースについて説明していきます。

コレクション

    const knowledgeBaseCollection = new aws_opensearchserverless.CfnCollection(
      this,
      'KnowledgeBaseCollection',
      {
        name: 'knowledge-base',
        type: 'VECTORSEARCH',
        standbyReplicas: 'DISABLED',
      }
    );

OpenSearch Serverlessコレクションを作成します。 コレクションとは、OpenSearchインデックスのグループのことです1

コレクションには、検索( SEARCH)、時系列(TIMESERIES)、ベクトル検索(VECTORSEARCH)の3つのタイプがあります。 今回はベクトル検索のコレクションを作成しました。

standbyReplicas プロパティではスタンバイレプリカを有効化するかどうかを設定できます。 マネジメントコンソールでコレクションを作成する際の「開発/テストモードを有効化」チェックボックスに相当する設定です。 上記のコードでは、standbyReplicas: 'DISABLED' と設定することで、スタンバイレプリカを無効化しています(開発/テストモードを有効化しています)。 本番環境で利用する場合は standbyReplicas: 'ENABLED' と設定してスタンバイレプリカを有効化することを推奨します。

暗号化ポリシー

    const knowledgeBaseCollectionEncryptionPolicy =
      new aws_opensearchserverless.CfnSecurityPolicy(
        this,
        'KnowledgeBaseCollectionEncryptionPolicy',
        {
          name: 'knowledge-base-encryption-policy',
          type: 'encryption',
          policy: JSON.stringify({
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
            ],
            AWSOwnedKey: true,
          }),
        }
      );

    // NOTE: コレクションを作成する前に、コレクションの名前と一致するリソースパターンを含む暗号化ポリシーを作成しておく必要がある
    // 暗号化ポリシー作成後にコレクションが作成されるように依存関係を設定する
    // @see https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-manage.html#serverless-create
    // @see https://docs.aws.amazon.com/ja_jp/opensearch-service/latest/developerguide/serverless-encryption.html
    knowledgeBaseCollection.addDependency(
      knowledgeBaseCollectionEncryptionPolicy
    );

OpenSearch Serverlessコレクションに保存されたデータの暗号化方法を指定するために 暗号化ポリシー を作成する必要があります。

コレクションに保存されたデータはKMSキーによって暗号化されます。 暗号化に利用されるKMSキーには、AWS所有キーとカスタマー管理キーのいずれかを設定できます。 上記のコードでは AWSOwnedKey: true と設定することで、AWS所有キーを利用しています。 カスタマー管理キーを利用するには、AWSOwnedKey: false と設定した上で KmsARN に利用するKMSキーのARNを設定する必要があります。

Resource によって、この暗号化ポリシーの適用先となるコレクションを設定できます。 上記のコードでは、今回作成するコレクションを指定しています。 Resource にはワイルドカード(*)を用いたパターンを設定することもできるため、汎用的な暗号化ポリシーを作成しておいて、それを複数のコレクションに対して適用することもできます。 なお、すべてのコレクションには保管時の暗号化が必須なため2、作成するコレクションにマッチする暗号化ポリシーを事前に作成しておく必要があります。

ネットワークポリシー

    new aws_opensearchserverless.CfnSecurityPolicy(
      this,
      'KnowledgeBaseCollectionNetworkPolicy',
      {
        name: 'knowledge-base-network-policy',
        type: 'network',
        policy: JSON.stringify([
          {
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
              {
                ResourceType: 'dashboard',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
              },
            ],
            AllowFromPublic: true,
          },
        ]),
      }
    );

コレクションに対して、インターネット経由のパブリックアクセスを許可するか、VPCエンドポイント経由でのアクセスのみを許可するかを、ネットワークポリシー によって設定できます。 ネットワークポリシーはコレクションとダッシュボードそれぞれに対して設定できます。

上記のコードでは、コレクションとダッシュボードともにパブリックアクセスが可能となるように設定しました。

本番環境で利用する際は、VPC経由のアクセスのみを許可するように設定することでセキュリティを向上させることができるでしょう。

データアクセスポリシー

    new aws_opensearchserverless.CfnAccessPolicy(
      this,
      'KnowledgeBaseCollectionAccessPolicy',
      {
        name: 'knowledge-base-access-policy',
        type: 'data',
        policy: JSON.stringify([
          {
            Rules: [
              {
                ResourceType: 'collection',
                Resource: [`collection/${knowledgeBaseCollection.name}`],
                Permission: ['aoss:*'],
              },
              {
                ResourceType: 'index',
                Resource: [`index/${knowledgeBaseCollection.name}/*`],
                Permission: ['aoss:*'],
              },
            ],
            Principal: [
              'arn:aws:iam::xxxxxxxxxxxx:role/xxxxx', // コレクションにアクセスする際に利用するIAMユーザーやIAMロールのARNを指定する
            ],
          },
        ]),
      }
    );

誰が(Principal)、どのデータに対して(Resource)、どんな操作を行えるか(Permission)の制限を データアクセスポリシー によって定義できます。

上記のコードでは、今回作成するコレクションおよびそれに含まれるすべてのインデックスに対するすべての操作を、指定したIAMロールに対して許可する設定を行いました。

本番環境で利用する際は、最小限のプリンシパルに対して必要最小限の権限を付与することが推奨されます。

つまずきポイント

今回CDKでコレクションを作成する中でいくつかつまずいた点があったのでそれらについて説明します。

コレクション名の制約

デプロイを行おうとした際に以下のようなエラーが発生しました。

#/Name: failed validation constraint for keyword [pattern]

この原因はコレクション名の制約に違反していたことです。 コレクション名には以下のルールに則ったものを指定する必要があります3

  • 小文字で始まる
  • アカウントとAWSリージョンに固有
  • 3文字以上28文字以下
  • 小文字のa-z、数字の0-9、ハイフン(-)のみを含む

今回は KnowledgeBase という英大文字を含むものを設定していたため、knowledge-base と修正することでエラーが解消しました。

作成するコレクションにマッチする暗号化ポリシーを事前に作成しておく必要がある

デプロイを行おうとした際に以下のようなエラーが発生しました。

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

この原因は、先述したとおり、コレクションにマッチする暗号化ポリシーの作成が必須なところ、暗号化ポリシーを作成していなかったことでした。

上記コードのように、暗号化ポリシーを作成することでこのエラーは解消しました。 なお、コード中にもコメントで補足していますが、暗号化ポリシーの作成が完了してからコレクションが作成されるように、コレクションを暗号化ポリシーに依存させる必要があります。

参考