AWS Lambda(Python)でSQS, ParameterStore, DynamoDBを処理してみた

# AWS Lambda(Python)でSQS, ParameterStore, DynamoDBを処理してみた
最近AWS上のサービスを使ったアプリ開発が増え、バッチなどの業務にLambda、SQSなどを使用したので備忘録として残そうと思います。
本番を想定した設定にはなっておりませんので、ご容赦ください。
Lambda関数で各サービスを使うチュートリアル的なものと思ってください。
今回は仕様的な部分に関してはほぼ記述しておりません、機会があれば別途投稿しようと思います。

## 実施したこと
* Lambda：関数の作成、下記のサービスとの統合
* SQS：キューの作成からメッセージを受信する設定
* ParameterStore：値のセット、Lambda関数で取得
  - SDKで取得する方法
  - Extensionsで取得する方法（＋カスタムレイヤーの作り方）
* DyanamoDBの作成、操作


## Lambda：関数の作成、下記のサービスとの統合
・まずはLambdaの【関数の作成】から作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/a8c69902-8243-c65a-1e74-b20d95a1ceba.png)

・関数名、ランタイム、アーキテクチャを選択して、作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/42318961-9473-a829-4de4-de8153950834.png)

・作成完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/8905dbe8-1e52-a4a2-cc5e-5d8afdb70c89.png)


### SQS：キューの作成からメッセージを受信する設定
・SQSで【キューを作成】する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/3417d4ad-051b-2434-8c5c-3562b2cbf38e.png)

・入力する項目ですが、基本的に名前のみで後はデフォルトのままで大丈夫です
　デッドレターキュとは、通常に設定しているキューの処理が失敗した時に退避されるキューです。これによりエラーとなるキューをそのままにせず、原因解消後処理するようなことが可能になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/d9042ee9-4bfc-c91b-56d0-0fe5dbabc567.png)

・下記のように作成できればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/304adcae-e9ff-e922-2935-cd16195ae8a4.png)

・そして、【Lambdaトリガー】のタブから先ほどのLambda関数を設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/f70d6a14-6700-c308-46ed-0461a91661d1.png)

そのまま設定すると以下のエラーになるので、Lambda関数にIAMロールを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/76a161f2-53cf-f549-64e9-81c69469b20b.png)

Lambda関数の【設定】から【ロール名】をクリックして、IAMへ移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/7b1840b4-6ee3-ac3c-6cbb-614d2692c14e.png)

許可ポリシーから追加をします。今回はポリシーのアタッチで大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/1c9e96e6-7f08-18b1-0d6e-9e49a57c9028.png)

検索で[SQS]と入力し、【AmazonSQSFullAccess】にチェックを入れ、追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/e807ba5b-bcee-14f3-ef30-81bcd799753e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/02f2aff1-9b56-6416-36fc-f36e33259fe0.png)


SQSに戻り再度、Lambda関数のトリガーを設定します。
次は正常に設定されました。ステータスがCreating → EnabledなればSQSメッセージを送信すると、Lambda関数が自動で受信するようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/d270bf42-b3fc-9f05-7fa1-dbf3f7db81fe.png)


・上記までできれば一旦確認してみましょう
　まずはLambda側の設定です。
　Lambda関数の[コード]を以下のように書き換えてください。
```python:python
import json

def lambda_handler(event, context):
    for record in event["Records"]:
        # SQSのメッセージをevent["Records"]で受け取る
        # ["body"]にメッセージ本文が格納されている
        body = record["body"]
        print("Received message:", body)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/c60b430b-0c84-e5fb-4f32-e2e7c21cefbd.png)

そして【Deploy】します。
少し経ったら、画面上記にダイアログが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/420a1ef0-6d5a-514d-b862-af50c7c0b11b.png)

次にSQSにメッセージを送信します。
メッセージを送受信から、メッセージ本文に任意の値を入力し、【メッセージを送信】します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/db65cf44-e170-5a43-baf5-b2d80f8b0148.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/68497588-f092-06b2-6110-a62432d912d9.png)

送信後、処理されたかログを確認してみましょう。
CloudWatchを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/e9a22d1a-93fc-3760-5e32-52e049af3b11.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/b9e5bbcb-b0d8-b58c-477e-7480d1b97d21.png)

SQSのメッセージ内容が出力されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/4ca62ee0-89f3-e793-35fa-d69696dc9156.png)



## ParameterStore：値のセット、Lambda関数で取得
次にParameter storeから値を取得してみましょう。
実は可視性のために改行を入れたりすると少し厄介になります。
また取得方法も2つあって、pythonコードからboto3を使って取得する方法、Extensionsのレイヤーを使用して直接取得する方法(http経由)があります。
アップデートによって、Extensionsが使用できるようになったのですが、取得した値をキャッシュできるようになったので、一定の時間2回目の呼び出しが爆速になり、低レイテンシを実現できます。
なので私はExtensionsの使い方をお勧めいたします。しかしEC2などからParameter storeの値を取得する際は使用できないので、SDKの取得方法も紹介させていただきます。

まずはParameter storeの値のセットを行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/764c5cd4-823d-f4cc-cf10-28b26a8cc686.png)

パラメータの作成から、名前と値を入力して、作成をしておきます。
ちなみに名前はスラッシュ（/）で階層化することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/dce8984e-c856-c482-5b77-e50f3de95db7.png)


Parameter storeを使用する際（というか他サービスとの連携）もIAMロールによってアクセス許可しないとエラーになるので追加します。
今回はインラインポリシーの作成で許可してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/73b5305b-68ef-7b82-b06b-330ee1725694.png)

設定画面の【JSON】タブに切り替え、以下のJSONデータを入力します。
Resourceには自分のアカウントIDを設定してください。
```json:json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameter",
                "ssm:GetParameters",
                "ssm:GetParametersByPath"
            ],
            "Resource": "arn:aws:ssm:ap-northeast-1:自分のアカウントID:parameter/sample-func-param"
        }
    ]
}
```
ポリシー名を指定して作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/4ca184eb-5c43-b663-7c2f-ca39a5d74dc5.png)


次はLambda関数から呼び出します。

### SDKで取得する方法
SDKの場合はboto3を使って以下のように呼び出すことができます。
```python:python
import json
import boto3

ssm = boto3.client("ssm") # クライアント初期化
PARAM_NAME = "sample-func-param" # ParameterStoreのパラメータ名

def lambda_handler(event, context):
    # SDKでParameterStoreから値
    # WithDecryption=Trueにすると暗号化されているパラメータを複合化して取得できる
    res = ssm.get_parameter(Name=PARAM_NAME, WithDecryption=True)
    param = res['Parameter']['Value']
    print(param)

    for record in event["Records"]:
        body = record["body"]
        print("Received message:", body)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/7ee090d9-f5e8-f750-d563-c3a3040e41b8.png)

上記を設定して、【Deploy】した後に、上記で行ったSQSからメッセージを投げて、Lambda関数を起動してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/d01487ac-4d5f-818c-c587-7c000dd12a96.png)

実行後、CloudWatchでログを確認します。
ParameterStoreで設定した値が取得できていればOK。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/0635f5c1-e442-3b3c-7764-b9c7bdbb1f50.png)


### Extensionsで取得する方法（＋カスタムレイヤーの作り方）
詳しいこと設定は以下の記事がとても参考になります。
[AWS Parameters and Secrets Lambda Extensionを使用してみた](https://dev.classmethod.jp/articles/aws-parameters-and-secrets-lambda-extension/)

ではまずLambdaからAWSレイヤーを追加します。設定は以下を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/b846c504-8964-8d76-f1da-76d7cbcacda2.png)

追加完了するとレイヤーが設定されているのが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/71149be7-7978-bf93-b572-007c1d78628c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/d56b9925-7396-bcf6-3a61-b409f0ccc1f1.png)

次に実際取得するコードを書きます。
```python:python
import os
import json
import requests

SECRETS_EXTENSION_PORT = os.environ.get('PARAMETERS_SECRETS_EXTENSION_HTTP_PORT')
end_point = f'http://localhost:{SECRETS_EXTENSION_PORT}'
PARAM_NAME = "sample-func-param" # ParameterStoreのパラメータ名
path = f'/systemsmanager/parameters/get/?name={PARAM_NAME}'

def lambda_handler(event, context):
    # SDKでParameterStoreから値
    url = '{}{}'.format(end_point, path)
    headers = {
        "content-type": "application/json",
        'X-Aws-Parameters-Secrets-Token': os.environ.get('AWS_SESSION_TOKEN')
    }
    res = requests.get(url, headers=headers)
    res.encoding = 'utf-8'
    param = res.json()['Parameter']['Value']
    print(param)

    for record in event["Records"]:
        body = record["body"]
        print("Received message:", body)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

```
忘れずに環境変数もセットしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/8b1a8e6e-6ecc-c08a-9896-78a5d4acb3ac.png)

そして、コード内では、```requests```の外部ライブラリを使用しています。
Lambdaではインストールされていないため、外部ライブラリをパッケージ化して、カスタムレイヤーとして追加する必要があります。
パッケージ化は割愛させていただきます。下記の記事をご参考ください。
[【AWS】LambdaにrequestsライブラリをLayerで追加する](https://sebenkyo.com/2021/05/21/post-1979/)


カスタムレイヤーを作成します。
設定は以下でOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/541deb34-ba65-f2f7-ead9-34cbf3d66e5e.png)

そしてLambda関数の設定でレイヤーを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/2537c565-0f0a-4968-ab68-8812fcb53fda.png)


実行して、ログを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/db44ef17-577a-7b8f-238c-356f29e1868f.png)


## DyanamoDBの作成、操作
DyanamoDBからテーブルの作成を選択して、テーブル名とパーティションキー、ソートキーをして作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/9df0421b-1242-3792-0078-bfac6db419f3.png)
作成後↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/4d93476f-bde9-0abe-29ef-3f7d396d7568.png)

次は、Lambda関数でテーブルに対してデータ投入をします。
Lambda関数のコードを書き換えましょう。
```python:python
import os
import json
import uuid
import boto3
from datetime import datetime
import requests

SECRETS_EXTENSION_PORT = os.environ.get('PARAMETERS_SECRETS_EXTENSION_HTTP_PORT')
end_point = f'http://localhost:{SECRETS_EXTENSION_PORT}'
PARAM_NAME = "sample-func-param" # ParameterStoreのパラメータ名
path = f'/systemsmanager/parameters/get/?name={PARAM_NAME}'

# DynamoDBクライアント初期化
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('sample')

def lambda_handler(event, context):
    # SDKでParameterStoreから値
    url = '{}{}'.format(end_point, path)
    headers = {
        "content-type": "application/json",
        'X-Aws-Parameters-Secrets-Token': os.environ.get('AWS_SESSION_TOKEN')
    }
    res = requests.get(url, headers=headers)
    res.encoding = 'utf-8'
    param = res.json()['Parameter']['Value']
    print(param)

    for record in event["Records"]:
        body = record["body"]
        print("Received message:", body)
        
    # DynamoDBにデータ投入
    id = str(uuid.uuid4()) # サロゲートキー
    dt = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
    name = 'Test User'
    message = 'Hello from Lambda!'
    # データ挿入
    res = table.put_item(
        Item = {
            'id': id,
            'dt': dt,
            'name': name,
            'message': message
        }
    )
    print(f'DB登録結果: {res['ResponseMetadata']}')

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

```

忘れずに【depoly】し、そして、IAMロールも忘れずにします。
今回はポリシーのアタッチで下記を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/cadae1cc-0585-dae6-d1ca-f88e9b97cbed.png)

後はいつも通り、SQSからメッセージを送信して、ログを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/c6f00460-5496-acbf-baf0-7208df6a6dae.png)

直接DynamoDBの値も確認します。
追加されていました。（何回か実行したので複数データあります）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3289261/ba3701e9-e513-482b-4111-eacce75b09cb.png)


## まとめ
最近AWSサービスをよく使うけど、この設定どうするんだっけ？となることがあるので、備忘録としてまとめました。特にIAMロールの設定忘れとか初めのころはよくしてました（笑）
他にもVPC、RDS、ECSなど色んなサービスも使うので、まとめようと思っています。