OpenSearch ServerlessとDynamoDBをクロスリージョンで更新してみた

# 背景・目的
異なるリージョンから、DDBとOpenSearchへの更新を試してみます。

# 実践
下記のような構成で試してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/0a4205f8-c13b-cf96-7608-d1da1edca71d.png)


## ① OpenSearch Serverlessの作成
### コレクションの作成
1. AWSにサインインします
1. OpenSearchに移動します
1. ナビゲーションペインの「サーバレス＞ダッシュボード」に移動します
1. 「コレクションを作成」をクリックします
1. 下記を入力し、「次へ」をクリックします
    - コレクション名：任意
    - コレクションタイプ：検索
    - デプロイタイプ：オフ
    - セキュリティ：標準作成
    - 暗号化：AWS 所有キーを使用する
    - ネットワークアクセスの設定
        - 次からコレクションにアクセス：パブリック
        - リソースタイプ
            - OpenSearchエンドポイントへのアクセスを有効にする
            - OpenSearch Dashboardsへのアクセスを有効にする
        
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/14a2e119-fac4-f996-8612-cb36a6ff8206.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/2ab0f0b6-06bb-6169-9dd0-3a6d2fe56dd0.png)

1. データアクセスの設定で、「スキップして後で設定する」をクリックします
1. 確認画面で、「送信」をクリックします

### データアクセス管理
1. OpenSearchに移動します
1. ナビゲーションペインで「セキュリティ＞データアクセスポリシー」をクリックします
1. 「アクセスポリシーを作成」をクリックします
1. 下記を入力し、「作成」をクリックします
    - アクセスポリシー名
    - プリンシパルを選択
    - リソースと許可を付与
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f8a1515b-8367-df49-3fa7-83cdf701579a.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/81b586e7-08b1-699a-3b9e-d3fe0753535f.png)

### 初期データの作成
1. OpenSearchダッシュボードに移動します
1. ナビゲーションペインで「Dev Tools」に移動します
1. インデックスを登録します
    ```json
    PUT test-index3

    ===
    {
      "acknowledged": true,
      "shards_acknowledged": true,
      "index": "test-index3"
    }
    ```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/272ba447-94c4-2b8e-03a3-36474680d647.png)
1. ドキュメントを登録します
    ```json
    PUT test-index3/_doc/1
    {
      "id":1,
      "data":"text1"
    }
    
    ===
    {
      "_index": "test-index3",
      "_id": "1",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
      },
      "_seq_no": 0,
      "_primary_term": 0
    }
    
    ```

1. 内容を確認します。登録されていました
    ```json
    GET test-index3/_search
    {
      "query": {
        "match_all": {}
      }
    }
    ====
    
    {
      "took": 18,
      "timed_out": false,
      "_shards": {
        "total": 0,
        "successful": 0,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": {
          "value": 1,
          "relation": "eq"
        },
        "max_score": 1,
        "hits": [
          {
            "_index": "test-index3",
            "_id": "1",
            "_score": 1,
            "_source": {
              "id": 1,
              "data": "text1"
            }
          }
        ]
      }
    }
    
    ```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/bd7b232b-a19b-1377-67e1-aff645d47005.png)


## ② DynamoDBの作成
1. DynamoDBに移動します
1. ダッシュボードで「テーブルの作成」をクリックします
1. 下記を入力し、「テーブルの作成」をクリックします
    - テーブル名:任意
    - パーティションキー：id
    - 設定をカスタマイズ
    - テーブルアクセス：標準IA
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f8f324c3-622e-5d9f-f9fd-accc168ca103.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e0944632-a7ec-bede-4519-048f118cbf99.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/89792e36-98e9-d8c9-7e58-caff6ca3f608.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e2047989-e708-205a-eb31-e65cc0e72c5e.png)

### 初期データの作成
1. 下記の内容を登録します
    ```bash
    % aws dynamodb put-item \                
    --table-name example_table \
    --item '{"id": {"S": "initial_id"}, "data": {"S": "initial_data"}}' \
    --region ap-northeast-1 \
    % 
    ```
1. 内容を確認します。作成されていました
    ```JSON
    % aws dynamodb get-item \           
        --table-name example_table \
        --key '{"id": {"S": "initial_id"}}' \
        --region ap-northeast-1
    {
        "Item": {
            "id": {
                "S": "initial_id"
            },
            "data": {
                "S": "initial_data"
            }
        }
    }
    %
    ```


## ③ 更新用のLambdaを作成
### 関数の作成
1. Lambdaに移動します
1. リージョンはus-east-2に移動します
1. 「関数を作成」ボタンをクリックします
1. 下記を入力し、「関数の作成」ボタンをクリックします
    - 関数名：任意
    - ランタイム：Python 3.12
    - アーキテクチャ：arm64
1. 下記のコードを貼り付けて、「Deploy」ボタンをクリックします
    ```py
    from opensearchpy import OpenSearch, RequestsHttpConnection
    from requests_aws4auth import AWS4Auth
    import boto3
    
    def update_opensearch_index():
        client = boto3.client('opensearchserverless')
        service = 'aoss'
        region = 'ap-northeast-1'
        credentials = boto3.Session().get_credentials()
        awsauth = AWS4Auth(credentials.access_key, credentials.secret_key,
                        region, service, session_token=credentials.token)
    
        host = 'XXXXX.ap-northeast-1.aoss.amazonaws.com'
    
        client = OpenSearch(
            hosts=[{'host': host, 'port': 443}],
            http_auth=awsauth,
            use_ssl=True,
            verify_certs=True,
            connection_class=RequestsHttpConnection,
            timeout=60
        )
    
        index_name ='test-index3'
        response = client.index(
            index=index_name,
            body={
                'data': 'test data 2'
            },
            id='2',
        )
        print('\nDocument added:')
        print(response)
    
    def put_ddb_item():
        dynamodb = boto3.resource('dynamodb',region_name='ap-northeast-1')
        table = dynamodb.Table('example_table')
        response = table.put_item(
            Item={
                'id': 'id:2',
                'data': 'test data 2'
            }
        )
        print('\nItem added:')
        print(response)
    
    
    def lambda_handler(event, context):
        update_opensearch_index()
        put_ddb_item()

    ```

### レイヤーの作成
1. 「レイヤーの追加」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/551830d0-e6bf-af42-e144-68628e2ab317.png)
1. 下記を入力し、「追加」をクリックします（opensearch-pyが含まれています）
    - レイヤーソース：AWSレイヤー
    - AWSレイヤー：AWSSDKPandas-Python312-Arm64
    - バージョン：15
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3494db8a-0010-e329-8aed-78d82f089053.png)

### LambdaのIAMロールの作成
1. Lambdaの「設定」タブの「アクセス権限」をクリックします
1. ロール名をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/2dec8ae7-3a69-ea65-e717-a2c84f85d3c5.png)


#### OpenSearch Serverless
1. 許可ポリシーで「許可を追加」＞「インラインポリシーを作成」をクリックします
1. 下記を指定して、「次へ」をクリックします
    - すべての OpenSearch Serverless アクション (aoss:*):チェック
    - このアカウント内のいずれか」チェック
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3b0f62e5-35f0-489f-5c83-88f3d724e8ea.png)
1. ポリシー名を入力し、「ポリシーの作成」をクリックします

#### DynamodDB
1. 許可ポリシーで「許可を追加」＞「ポリシーを追加」をクリックします
1. 「AmazonDynamoDBFullAccess」を追加します

### OpenSearch Serverlessのデータアクセスコントロール
1. OpenSearch Serverlessに移動します
1. 該当するコレクションのアクセスポリシーを選択します
1. 上記で作成したIAMロールをプリンシパルに追加します

## ④ 参照用のLambdaを作成
### 関数の作成
1. Lambdaに移動します
1. リージョンはap-northeast-1に移動します
1. 「関数を作成」ボタンをクリックします
1. 下記を入力し、「関数の作成」ボタンをクリックします
    - 関数名：任意
    - ランタイム：Python 3.12
    - アーキテクチャ：arm64
1. 下記のコードを貼り付けて、「Deploy」ボタンをクリックします
    ```python
    from opensearchpy import OpenSearch, RequestsHttpConnection
    from requests_aws4auth import AWS4Auth
    import boto3
    import json
    
    def get_ddb_item():
    
        dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-1')
        table = dynamodb.Table('example_table')
    
        response = table.get_item(Key={'id': 'id:2'})
        dynamo_data = response.get('Item', {})
        print(f"DynamoDB Data: {dynamo_data}")
    
        return dynamo_data
    
    def get_opensearch_index():
        client = boto3.client('opensearchserverless')
        service = 'aoss'
        region = 'ap-northeast-1'
        credentials = boto3.Session().get_credentials()
        awsauth = AWS4Auth(credentials.access_key, credentials.secret_key,
                        region, service, session_token=credentials.token)
    
        host = 'XXXXX.ap-northeast-1.aoss.amazonaws.com'
    
        client = OpenSearch(
            hosts=[{'host': host, 'port': 443}],
            http_auth=awsauth,
            use_ssl=True,
            verify_certs=True,
            connection_class=RequestsHttpConnection,
            timeout=60
        )
    
        index_name = "test-index3"
        response = client.get(
            index=index_name,
            id='2'
            )
    
        print('\nDocument added:')
        print(response)
    
        return response
    
    
    def lambda_handler(event, context):
        ddb_response = get_ddb_item()
        opensearch_response = get_opensearch_index()
    
        return {
            "statusCode": 200,
            "body": json.dumps({
                    "message":"Update successful",
                    "dynamodb":ddb_response,
                    "opensearch-serverless":opensearch_response
    
            })
        }
    ```


### レイヤーの作成
1. 「レイヤーの追加」をクリックします
1. 下記を入力し、「追加」をクリックします（opensearch-pyが含まれています）
    - レイヤーソース：AWSレイヤー
    - AWSレイヤー：AWSSDKPandas-Python312-Arm64
    - バージョン：15

### LambdaのIAMロールの指定
すでに作成したIAMロールを指定します。

1. Lambdaの「設定」タブの「アクセス権限」をクリックします
1. 「編集」をクリックします
1. 実行ロールを「既存のロールを使用する」を選択後、上記で作成済みのものを指定します

## ⑤ API Gatewayの作成

1. API Gatewayに移動します
1. REST APIで構築をクリックします
1. 下記を入力し、「APIを作成」をクリックします
    - 新しいAPIを選択
    - API名：任意
    - API エンドポイントタイプ：リージョン
1. 「メソッドを作成」をクリックします
1. 下記を入力して、「メソッドを作成」をクリックします
    - メソッドタイプ：GET
    - 統合タイプ：Lambda関数
    - Lambda関数
        - リージョン：ap-northeast-1
        - 関数：上記で作成した関数を指定
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/3e7a96cc-c4e2-db64-4333-8493886b24e4.png)

1. 「APIをデプロイ」をクリックします
1. 下記を入力し、「デプロイ」をクリックします
    - ステージ：新しいステージ
    - ステージ名：prd

## ⑥ 登録
1. ③で作成したLambda関数を開きます
1. 「テスト」タブをクリックし、テストをクリックします
1. 成功しました
    ```json
    START RequestId: XXXXX Version: $LATEST
    Document added:
    {'_index': 'test-index3', '_id': '2', '_version': 14, 'result': 'updated', '_shards': {'total': 0, 'successful': 0, 'failed': 0}, '_seq_no': 0, '_primary_term': 0}
    Item added:
    {'ResponseMetadata': {'RequestId': 'XXXXXX', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'Server', 'date': 'Sat, 28 Dec 2024 14:20:00 GMT', 'content-type': 'application/x-amz-json-1.0', 'content-length': '2', 'connection': 'keep-alive', 'x-amzn-requestid': 'XXXXXXX', 'x-amz-crc32': '2745614147'}, 'RetryAttempts': 0}}
    END RequestId: XXXXX
    REPORT RequestId: XXXXX	Duration: 4362.21 ms	Billed Duration: 4363 ms	Memory Size: 128 MB	Max Memory Used: 102 MB	Init Duration: 1226.51 ms	
    ```

## ⑦ 確認
1. ブラウザからAPI Gatewayのエンドポイントにアクセスします
1. 成功しました
    ```json
    {
      "statusCode": 200,
      "body": "{\"message\": \"Update successful\", \"dynamodb\": {\"id\": \"id:2\", \"data\": \"test data 2\"}, \"opensearch-serverless\": {\"_index\": \"test-index3\", \"_id\": \"2\", \"_version\": 14, \"_seq_no\": 13, \"_primary_term\": 1, \"found\": true, \"_source\": {\"data\": \"test data 2\"}}}"
    }
    ```
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/1bbee00c-6433-30a5-9ad3-16024180ea35.png)

# 考察
今回、AWS Lambdaを利用したクロスリージョンアクセスを試してみました。
出来ることは理解しておりましたが、実際に試したことはなかったので良い経験になりました。
次回は、LambdaをVPC内に配置して、Transit Gatewayでリージョン間を接続した構成で試してみようと思います。

# 参考
https://qiita.com/zumax/items/d6553d5521db1537702f

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

https://pypi.org/project/opensearch-py/

https://aws-sdk-pandas.readthedocs.io/en/stable/layers.html