RAGでよく使うKendraとS3をCloudFormationで実装してみた

RAGでよく使うKendraとS3をCloudFormationで実装してみた

Clock Icon2023.11.03 12:41

こんにちは、つくぼし(tsukuboshi0755)です!

最近流行りのRetrieval-Augmented Generation(以下RAG)を組む際に、文書を保存するデータソースとしてKendraとS3を組み合わせるパターンは多いと思います。

今回は、RAGでよく使うKendraとS3をCloudFormationで実装し、利用する方法を紹介します!

構成

今回の構成図は以下になります。

また以下の設定はRAGには必須ではありませんが、実際にRAGを運用する際に役に立つので合わせて設定します。

  • CloudWatch Logsの使用料金を減らすため、Kendraログに保持期間を指定
  • S3バケットに保存された文書データが不正にダウンロードされる事を防ぐため、バケットポリシーでKendraデータソースロール以外からのオブジェクト取得を禁止

テンプレート

以下のリポジトリのテンプレートをベースにしています。

amazon-kendra-langchain-extensions/kendra_retriever_samples/kendra-docs-index.yaml

リポジトリのテンプレートはデータソースとしてWebクローラーを使用していますが、今回はS3を使用する形に変更した上で作成しています。

全体のコードは以下の通りです。

CloudFormationコード
AWSTemplateFormatVersion: '2010-09-09'
Description: Kendra and S3

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Parameters:
          - SysName
          - Env
          - KendraEdition
          - KendraLogRetentionDays
          - KendraDSBucketPrefix

Parameters:
  SysName:
    Type: String
    Default: 'cm'
    Description: 'System name for this stack.'
  Env:
    Type: String
    Default: 'prd'
    Description: 'Environment for this stack.'
    AllowedValues:
      - 'prd'
      - 'stg'
      - 'dev'
  KendraEdition:
    Type: String
    Default: 'ENTERPRISE_EDITION'
    Description: 'ENTERPRISE_EDITION is suitable for production environments. DEVELOPER_EDITION is suitable for development environments.'
    AllowedValues:
      - 'ENTERPRISE_EDITION'
      - 'DEVELOPER_EDITION'
  KendraLogRetentionDays:
    Type: Number
    Default: 365
    Description: 'Retention days for Kendra logs.'
    AllowedValues:
      - 1
      - 3
      - 5
      - 7
      - 14
      - 30
      - 60
      - 90
      - 120
      - 150
      - 180
      - 365
      - 400
      - 545
      - 731
      - 1096
      - 1827
      - 2192
      - 2557
      - 2922
      - 3288
      - 3653
  KendraDSBucketPrefix:
    Type: String
    Default: 'awsdoc'
    Description: 'Bucket prefix for search range.'


Resources:
  ##Role for Kendra Index
  KendraIndexRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${SysName}-${Env}-kendra-index-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: kendra.amazonaws.com
            Action: 'sts:AssumeRole'

  ##Policy for Kendra Index
  KendraIndexPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-index-policy'
      Roles:
        - !Ref KendraIndexRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Resource: '*'
            Condition:
              StringEquals:
                'cloudwatch:namespace': 'AWS/Kendra'
            Action:
              - 'cloudwatch:PutMetricData'
          - Effect: Allow
            Resource: '*'
            Action: 'logs:DescribeLogGroups'
          - Effect: Allow
            Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*'
            Action: 'logs:CreateLogGroup'
          - Effect: Allow
            Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*:log-stream:*'
            Action:
              - 'logs:DescribeLogStreams'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'

  ##Kendra Index
  KendraIndex:
    Type: 'AWS::Kendra::Index'
    Properties:
      Name: !Sub '${SysName}-${Env}-kendra-index'
      Edition: !Ref KendraEdition
      RoleArn: !GetAtt KendraIndexRole.Arn

  ##CloudWatch LogGroup for Kendra Index
  KendraIndexLogs:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub
        - '/aws/kendra/${IndexId}'
        -  IndexId: !GetAtt KendraIndex.Id
      RetentionInDays: !Ref KendraLogRetentionDays

  ##Role for Kendra Data Source
  KendraDSRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${SysName}-${Env}-kendra-ds-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: kendra.amazonaws.com
            Action: 'sts:AssumeRole'

  ##Policy for Kendra Data Source
  KendraDSPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-ds-policy'
      Roles:
        - !Ref KendraDSRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Resource:
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref KendraDSBucket
                  - '/*'
            Action:
              - 's3:GetObject'
          - Effect: Allow
            Resource: !GetAtt KendraDSBucket.Arn
            Action:
              - 's3:ListBucket'
          - Effect: Allow
            Resource: !Sub
              - 'arn:aws:kendra:${AWS::Region}:${AWS::AccountId}:index/${IndexId}'
              -  IndexId: !GetAtt KendraIndex.Id
            Action:
              - 'kendra:BatchPutDocument'
              - 'kendra:BatchDeleteDocument'

  ##Kendra Data Source
  KendraDS:
    Type: 'AWS::Kendra::DataSource'
    Properties:
      DataSourceConfiguration:
        S3Configuration:
          BucketName: !Ref KendraDSBucket
          InclusionPrefixes:
            - !Ref KendraDSBucketPrefix
      IndexId: !GetAtt KendraIndex.Id
      LanguageCode: 'ja'
      Name: !Sub '${SysName}-${Env}-kendra-ds'
      RoleArn: !GetAtt KendraDSRole.Arn
      Type: 'S3'

  ##Data Source Bucket
  KendraDSBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub '${SysName}-${Env}-kendra-ds-bucket-${AWS::AccountId}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
            BucketKeyEnabled: true
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced

  ##Data Source Bucket Policy
  KendraDSBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref KendraDSBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Deny
            Action:
              - 's3:GetObject'
            Resource:
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref KendraDSBucket
                  - '/*'
            Principal: '*'
            Condition:
                StringNotEquals:
                  aws:PrincipalArn:
                    - !GetAtt KendraDSRole.Arn

Outputs:
  KendraIndexID:
    Value: !GetAtt KendraIndex.Id
  KendraDSID:
    Value: !GetAtt KendraDS.Id
  KendraDSBucketName:
    Value: !Ref KendraDSBucket

以下より、本テンプレートで作成される各リソースについて説明します。

Kendraインデックスロール

  KendraIndexRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${SysName}-${Env}-kendra-index-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: kendra.amazonaws.com
            Action: 'sts:AssumeRole'

  KendraIndexPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-index-policy'
      Roles:
        - !Ref KendraIndexRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Resource: '*'
            Condition:
              StringEquals:
                'cloudwatch:namespace': 'AWS/Kendra'
            Action:
              - 'cloudwatch:PutMetricData'
          - Effect: Allow
            Resource: '*'
            Action: 'logs:DescribeLogGroups'
          - Effect: Allow
            Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*'
            Action: 'logs:CreateLogGroup'
          - Effect: Allow
            Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*:log-stream:*'
            Action:
              - 'logs:DescribeLogStreams'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'

Kendraインデックス用のIAMロールを作成します。

ここでは以下の公式ドキュメントに基づき、Kendraに関するCloudWatchログ及びメトリクスの出力権限をアタッチします。

Kendraインデックス

  KendraIndex:
    Type: 'AWS::Kendra::Index'
    Properties:
      Name: !Sub '${SysName}-${Env}-kendra-index'
      Edition: !Ref KendraEdition
      RoleArn: !GetAtt KendraIndexRole.Arn

文書データを検索するKendraインデックスを作成します。

Editionでは、KendraのエディションをENTERPRISE_EDITION(本番用)またはDEVELOPER_EDITION(検証用)から選択します。

RoleArnでは、先ほど作成したKendraインデックスロールARNを指定します。

Kendraログ(任意)

  KendraIndexLogs:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub
        - '/aws/kendra/${IndexId}'
        -  IndexId: !GetAtt KendraIndex.Id
      RetentionInDays: !Ref KendraLogRetentionDays

Kendra用のCloudWatch Logsロググループを作成します。

必要に応じて、RetentionInDaysでKendraログの保持期間を指定します。

Kendraデータソースロール

  KendraDSRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${SysName}-${Env}-kendra-ds-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: kendra.amazonaws.com
            Action: 'sts:AssumeRole'

  KendraDSPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-ds-policy'
      Roles:
        - !Ref KendraDSRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Resource:
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref KendraDSBucket
                  - '/*'
            Action:
              - 's3:GetObject'
          - Effect: Allow
            Resource: !GetAtt KendraDSBucket.Arn
            Action:
              - 's3:ListBucket'
          - Effect: Allow
            Resource: !Sub
              - 'arn:aws:kendra:${AWS::Region}:${AWS::AccountId}:index/${IndexId}'
              -  IndexId: !GetAtt KendraIndex.Id
            Action:
              - 'kendra:BatchPutDocument'
              - 'kendra:BatchDeleteDocument'

Kendraデータソース用のIAMロールを作成します。

ここではS3上の文書データへのアクセス権限、及びKendraインデックスへの文書データの登録・削除権限をアタッチします。

Kendraデータソース

  KendraDS:
    Type: 'AWS::Kendra::DataSource'
    Properties:
      DataSourceConfiguration:
        S3Configuration:
          BucketName: !Ref KendraDSBucket
          InclusionPrefixes:
            - !Ref KendraDSBucketPrefix
      IndexId: !GetAtt KendraIndex.Id
      LanguageCode: 'ja'
      Name: !Sub '${SysName}-${Env}-kendra-ds'
      RoleArn: !GetAtt KendraDSRole.Arn
      Type: 'S3'

S3バケットの特定プレフィックス配下の文書データを対象とするKendraデータソースを作成します。

S3Configurationでは、文書データが保存されているバケット名とプレフィックスを指定します。

IndexIdでは、先ほど作成したKendraインデックスのIDを指定します。

LanguageCodeでは、文書データの言語コードを日本語(ja)で指定します。

RoleArnでは、先ほど作成したKendraデータソースロールARNを指定します。

Typeでは、文書データの保存先がS3バケットである事を指定します。


(2024/6/28追記)

S3バケットに接続するKendraデータソース設定には、旧型のS3DataSourceConfigurationと、新型のTemplateConfigurationの2種類が存在します。

2024/6現在、CloudFormationではS3DataSourceConfigurationしか対応していません。

TemplateConfigurationを使用したい場合、現状KendraデータソースをコンソールまたはAPIから設定する必要があるためご注意下さい。

Amazon Kendra now supports an upgraded Amazon S3 connector. You must now use the TemplateConfiguration object instead of the S3DataSourceConfiguration object to configure your connector. Connectors configured using the older console and API architecture will continue to function as configured. However, you won't be able to edit or update them. If you want to edit or update your connector configuration, you must create a new connector. We recommended migrating your connector workflow to the upgraded version. Support for connectors configured using the older architecture is scheduled to end by June 2024.

S3バケット

  KendraDSBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub '${SysName}-${Env}-kendra-ds-bucket-${AWS::AccountId}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
            BucketKeyEnabled: true
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced

文書データを保存するS3バケットを作成します。

今回は基本的なパブリックアクセス/暗号化/ACL無効化を実施しています。

S3バケットポリシー(任意)

  KendraDSBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref KendraDSBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Deny
            Action:
              - 's3:GetObject'
            Resource:
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref KendraDSBucket
                  - '/*'
            Principal: '*'
            Condition:
                StringNotEquals:
                  aws:PrincipalArn:
                    - !GetAtt KendraDSRole.Arn

Kendraデータソースロール以外からのオブジェクト取得を禁止するバケットポリシーを作成します。

このバケットポリシーを設定するで、S3バケットにアップロードした文書データに機密情報が含まれる場合でも、Kendraデータソース以外からの不正アクセスを防ぐ事ができます。

なおもしオブジェクトにアクセス可能なIAMロールを追加したい場合は、以下のような形で該当リソースのARNを追加してください。

            Condition:
                StringNotEquals:
                  aws:PrincipalArn:
                    - !GetAtt KendraDSRole.Arn
                    - !Sub "arn:aws:iam::${AWS::AccountId}:role/<IAMロール名>"
  • 参考

検索の確認

上記のテンプレートをデプロイする事で、Kendraを用いて正常に文書データを検索できるか確認します。

なお今回の確認で使用する文書データのアップロード及び確認手順としては、以下の記事を参照します。

CloudFormationスタックのデプロイ

今回は以下のパラメータで、CloudFormationスタックをデプロイします。

なおKendra関連リソースのデプロイには、およそ20-30分程度かかります。

S3バケットへのデータ投入

次にCloudShell上で以下のコマンドを実施し、PDFファイルを作成したS3バケットにアップロードします。

# S3 バケット名を設定
BUCKET_NAME=<バケット名>

# S3 プレフィックスを設定
BUCKET_PREFIX=awsdoc

# AWS の公式ドキュメントの PDF ファイルをダウンロード
mkdir ${BUCKET_PREFIX}
cd ${BUCKET_PREFIX}
wget https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/dynamodb-dg.pdf -O DynamoDB.pdf
wget https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-dg.pdf -O Lambda.pdf
wget https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-ug.pdf -O VPC.pdf
wget https://docs.aws.amazon.com/ja_jp/kendra/latest/dg/kendra-dg.pdf -O Kendra.pdf
wget https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/route53-dg.pdf -O Route53.pdf
cd ..

# S3 バケットに PDF ファイルをアップロード
aws s3 cp ${BUCKET_PREFIX} s3://${BUCKET_NAME}/${BUCKET_PREFIX}/ --recursive

ファイルのアップロードが成功すると、以下の通り作成したS3バケットのawsdocプレフィックス配下に5つのPDFファイルが作成されます。

Kendraデータソースの同期

続いてKendraのコンソールに移動し、左ペインのIndexesに切り替え、作成したインデックスをクリックします。

Kendraのインデックス設定は以下のようになっています。

左ペインのData Sourcesに切り替え、作成したデータソースをクリックします。

Sync nowボタンを押すと、S3にアップロードしたPDFファイルがKendraに同期されます。

データの同期が成功すると、Sync Historyタブに以下のようなCompleted履歴が表示されます。

Kendraでの検索実行

Kendraで検索が可能か確認するために、左ペインのSearch indexed contentに切り替えます。

右端にある設定ボタンをクリックします。

Default language of source documentsでJapanese(ja)を選択し、saveボタンを押します。

これで検索設定は完了です。

試しに「IP アドレス」と検索すると、「IP」と「アドレス」の両方のキーワードが含まれているドキュメントがヒットしました!

なおもしバケットポリシーでKendraデータソースロール以外のオブジェクト取得を禁止している場合、ドキュメントに表示されているURLをクリックしてもアクセスできない事にご注意ください。

最後に

今回は、RAGでよく使うKendraとS3をCloudFormationで実装し、利用する方法を紹介しました!

今後RAGをAWSで組む機会は増えていくと考えられるので、その際にぜひご活用頂けるとありがたいです。

以上、つくぼし(tsukuboshi0755)でした!

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.