AuroraServerlessを操作するAPI(GET、PUT、POST)+lambdaを作る。

# 作るもの

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/3f0e6d96-28eb-8fa9-1aac-4da612896558.png)

今回、DBに入れるデータは以下のものとして進める。

|id|name|age|
|:---:|:---:|:---:|
|1|Apigateway|12|
|2|lambda|34|
|3|Dynamodb|56|

_contents_

* Aurora serverless、シークレットマネージャとは
* Aurora serverlessのDBの作り方。
* lambdaからAurora serverlessへアクセスする方法。
* APIでGET、PUT、POSTする方法

## Aurora serverlessとは？

DBのインスタンス部分とストレージ部分が分離した構造を取っており、アクセスがない時にはインスタンス部分は起動しておらず、ストレージ部分のみが存在する。アクセスを受けると、負荷に応じてインスタンスがスケールアウトしクエリを実行する。アクセスがなくなると最終的にはインスタンスはゼロになるため、DBのランニングコストを抑えることが可能である。（RDSは停止させても7日で自動で起動する。）

__クラスタとは？__
まだよくわかっていないがAuroraのインスタンスやストレージの全体のことかと思われる。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.html

__Aurora キャパシティーユニット (ACU) とは？__
処理キャパシティーとメモリキャパシティーの組み合わせ。
最大と最小のACUを指定でき、それぞれDBクラスタをスケールダウン/アップできる最小/最大の値を示している。

## シークレットマネージャとは？
シークレットを一元的に管理するシステム。これまでは認証情報を用いる箇所にそれらをベタ書きしていたが、シークレットマネージャを用いることでセキュリティを高めることができる。

__シークレットとは？__
データベース認証情報、パスワード、サードパーティーのAPIキーなどの任意のテキスト。

__ローテーションとは？___
一定期間がたつと自動的にパスワードを変更する仕組み。ONにすると自動的にlambdaが生成され、対象の認証情報のパスワードを定期的にローテーションさせる。なお、この定期的な変更はユーザ側は気にすることなくアプリケーションを開発することができる。

## 1. Aurora serverlessクラスター・データベースの作成。

RDSコンソールへ移動し、ナビゲーションペインのデータベースを選択→作成へ。

- データベース作成方法を選択 : 標準作成
- エンジンのオプション
 - エンジンのタイプ : Aurora
 - エディション : PostgreSQL
- データベースの機能 : サーバーレス
- 設定
 - `DBクラスター識別子`、`マスターユーザー名`、`マスターパスワード`
- キャパシティーの設定
 - 最小/最大キャパシティーユニット : 任意の値
 - スケーリングの追加設定
 - [ ]  タイムアウトに達すると、容量を指定された値に強制的にスケーリングします
 - [x]  数分間アイドル状態のままの場合コンピューティング性能を一時停止する : 5 min
- 接続
 - 追加の接続設定
 - [x]  Data API
- 追加設定
 - `最初のデータベース名`

→ 作成する。作成されるまでしばらくかかる。

なお、Data APIにチェックを入れていないと3で接続不可となる。
→その場合でもあとからクラスターの設定を変更できる。

## 2. Secret Managerへの登録。

- シークレットの種類を選択 : RDSのデータベース認証情報
- ユーザー名 : 1で設定したマスターユーザー名
- パスワード : 1で設定したマスターパスワード
- 暗号化キーを選択してください : DefaultEncryptionKey (default)
- このシークレットがアクセスするRDSデータベースを選択してください : 1で設定したDBクラスター識別子
- シークレットの名前 : `シークレット名`
- 自動ローテーションを設定する : 自動ローテーションを無効にする

→ 保存

## 3. RDSコンソールからクエリ実行。

コンソールからDBにアクセスして疎通確認を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/2b2bc984-5aed-af23-fc5a-47de42f5b923.png)

対象のクラスターを選択し、[アクション]→[クエリ]

データベースユーザー名は直接入力不可のため、2で作成した「Secret Manager ARNと接続する」を指定する。
Secret Managerコンソールから対象のシークレットのARNを取得しペースト。
データベース名には1で設定した「最初のデータベース名」を指定。

```sql
##### テーブル作成 #####
CREATE TABLE Staff
(id    CHAR(4)    NOT NULL,
name   TEXT       NOT NULL,
age    INTEGER    ,
PRIMARY KEY (id));
# →実行

##### データ挿入 #####
INSERT INTO Staff VALUES ('0001', 'Aurora', 12);
INSERT INTO Staff VALUES ('0002', 'Lambda', 34);
INSERT INTO Staff VALUES ('0003', 'Dynamo', 56);
# →実行

##### データ取得 #####
SELECT * FROM Staff
# → 実行
```

## 4. IAMロールの作成

### 4-1. SecretManagerからRDSにアクセスするIAMポリシー作成

クラメソさんの「[Lambda で Aurora Serverless の Data API 使えました！そう、Lambda Layer があればね](https://dev.classmethod.jp/cloud/aws/aurora-sl-dataapi-with-lambda-layer/)」に従います。
2020年1月現在でも`"arn:aws:secretsmanager:*:*:secret:rds-db-credentials/*"`だけでは不可でした。
公式ドキュメント：https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/data-api.html

```:IAMポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SecretsManagerDbCredentialsAccess",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue",
                "secretsmanager:PutResourcePolicy",
                "secretsmanager:PutSecretValue",
                "secretsmanager:DeleteSecret",
                "secretsmanager:DescribeSecret",
                "secretsmanager:TagResource"
            ],
            "Resource": [
                "arn:aws:secretsmanager:*:*:secret:rds-db-credentials/*",
                " ~~~ここにシークレットのARNを指定~~~ "
            ]
        },
        {
            "Sid": "RDSDataServiceAccess",
            "Effect": "Allow",
            "Action": [
                "dbqms:CreateFavoriteQuery",
                "dbqms:DescribeFavoriteQueries",
                "dbqms:UpdateFavoriteQuery",
                "dbqms:DeleteFavoriteQueries",
                "dbqms:GetQueryString",
                "dbqms:CreateQueryHistory",
                "dbqms:DescribeQueryHistory",
                "dbqms:UpdateQueryHistory",
                "dbqms:DeleteQueryHistory",
                "dbqms:DescribeQueryHistory",
                "rds-data:ExecuteSql",
                "rds-data:ExecuteStatement",
                "rds-data:BatchExecuteStatement",
                "rds-data:BeginTransaction",
                "rds-data:CommitTransaction",
                "rds-data:RollbackTransaction",
                "secretsmanager:CreateSecret",
                "secretsmanager:ListSecrets",
                "secretsmanager:GetRandomPassword",
                "tag:GetResources"
            ],
            "Resource": "*"
        }
    ]
}
```

### 4-2. lambdaに当てるIAMロールの作成

4-1で作成したIAMポリシーと、`AmazonRDSDataFullAccess`を当てたIAMロールを作成する。
そのほかCloudWatchへのログ書き込みなど必要なものは適宜追加。

## 5. Aurora serverlessに接続するlambda関数の作成

テーブル項目一覧取得のコード（同じくクラメソのコード）

```python:lambda_function.py
import json
import boto3

def lambda_handler(event, context):
   
    rdsData = boto3.client('rds-data')
     
    print(boto3.__version__)
   
    # RDSクラスターのARN
    cluster_arn = 'arn:aws:rds:ap-northeast-1:xxxxxxxxxxxx:cluster:test'
    # シークレットのARN
    secret_arn = 'arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:test'
    
    # ★
    response1 = rdsData.execute_statement(
                resourceArn = cluster_arn, 
                secretArn = secret_arn, 
                database = '1で設定したデータベース名', 
                sql = 'select * from table_name') # 3で作成したテーブル名
   
    print (response1['records'])
```

* 初回のDBアクセスではインスタンス起動に時間がかかるためタイムアウトを1minに指定しておくこと。
* IAMロールは上記で作成済みのものを使用。
* RDSクラスターのARNはクラスターの[設定]タブから取得可能
* 参考としたクラメソさんのページではlambda layerを使用してboto3をzipしてレイヤに設定していましたが、layerを設定せずに`import boto3`でもちゃんと取得できた。

## 6. API Gatewayの作成

### 6-1. GET

クエリパラメータに指定したidのレコードを返すAPI+lambdaを作成する。

lambdaを一部変更。

```python
    # ★
    sql_str = "select * from table_name where id = '" + event["id"] + "'" # 3で作成したテーブル名
    response1 = rdsData.execute_statement(
                resourceArn = cluster_arn, 
                secretArn = secret_arn, 
                database = '1で設定したデータベース名', 
                sql = sql_str)
    return response1["records"]
```

__メソッドリクエスト__
1. [アクション]→[メソッドの作成]から`GET`を選択。
2. [メソッドリクエスト]→[URLクエリ文字列パラメータ]→「クエリ文字列を追加」にパラメータ名を入れる。

ここでは名前`id`、必須 `✔︎`

__統合リクエスト__
1. [統合リクエスト]→[マッピングテンプレート]→リクエスト本文のパススルーに「テンプレートが定義されていない場合(推奨)」を選択。
2. Content-Typeには`application/json`を指定。
3. マッピングテンプレートに以下を指定。

```
{
  "id": "$input.params('id')"
}
```

この形式でlambdaのeventに渡される。クエリストリングは`$input.params('パラメータ名')`で取得可能。

最後に[アクション]→[APIのデプロイ]を選択してURIを発行する。

試しにブラウザから`id=0001`を付してアクセスすると次の値が帰る。

```
[
  [
    {
      "stringValue": "0001"
    },
    {
      "stringValue": "Aurora"
    },
    {
      "longValue": 12
    }
  ]
]
```

参考） https://qiita.com/Quantum/items/91ad6b6b788bf4051055

### 6-2. POST

リクエストボディに含めたjsonで新規にレコードを追加するAPI+lambdaを作成する。

lambdaを一部変更。

```python
    # ★
    sql_str = "insert into table_name values ('" + event['id'] + "', '" + event['name'] + "', " + str(event['age']) + ");"
    
    response1 = rdsData.execute_statement(
                resourceArn = cluster_arn,
                secretArn = secret_arn,
                database = '1で設定したデータベース名',
                sql = sql_str,
                )
    
    return response1
```

同じく[アクション]から[メソッドの作成]でPOSTを選択し、[APIをデプロイ]で作成。
postmanから以下のjsonを投げるとレスポンスが得られる。

```json:リクエスト
{
  "id": "0004",
  "name": "Apigateway",
  "age": 78
}
```

```json:レスポンス
{
  "ResponseMetadata": {
    "RequestId": "fdc33f8d-2ace-4bd3-b3d6-3c0796068366",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "x-amzn-requestid": "fdc33f8d-2ace-4bd3-b3d6-3c0796068366",
      "content-type": "application/json",
      "content-length": "49",
      "date": "Mon, 20 Jan 2020 14:48:28 GMT"
    },
    "RetryAttempts": 0
  },
  "generatedFields": [],
  "numberOfRecordsUpdated": 1
}
```

参考） https://qiita.com/edg_aim/items/3989aca53d98e3cafc0f


### 6-3. PUT

指定したパスの`id`の情報を更新するAPI+lambdaを作成する。

lambdaを一部変更。

```python
    # ★
    sql_str = "update table_name set name='" + event["name"] + "' where id = '" + str(event["id"]) + "';"
    
    response1 = rdsData.execute_statement(
                resourceArn = cluster_arn,
                secretArn = secret_arn,
                database = '1で設定したデータベース名',
                sql = sql_str,
                )
    
    return response1
```

まずはid毎のパスを切るため、[アクション]から[リソースmの作成]を選択。
ここで、リソース名に`id`とし、リソースパスに`{id}`とする。
これにより、`{id}`の部分はidの番号で置き換わったパス全てを指すことを意味する。

続いて同じく[アクション]から[メソッドの作成]でPUTを選択し、[APIをデプロイ]で作成。

__メソッドリクエスト__
特に変更梨。リクエストパスを覗くと勝手にリソース名の`id`と書かれている。

__統合リクエスト__
マッピングテンプレートの設定が少しややこしい。それまではGETと同じ手順。

URI： https://〜〜〜/{id}

```マッピングテンプレート
#set($test = '"' + $input.params("id") + '"')
{
    "id": $test,
    "name": $input.json('name')
}
```

`#set()`の中身では式を記述できる？と思われる。
`"id": $input.params("id")`と直接連結してもよいのだが、この場合にidに0001を指定するとクオーテーションで囲われないため以下のエラーが発生。

> Invalid numeric value: Leading zeroes not allowed

正しいテンプレートの下でpostmanから`context-type`を指定してput。

URI: https://〜〜〜/0001

```json:リクエスト
{
    "name": "Aurora"
}
```

200のレスポンスが得られ、テーブルが更新されていれば成功。

なお、マッピングテンプレートの記述は以下のドキュメントに書かれている。
参考） https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#input-variable-reference