RDS Proxy 経由で MySQL にアクセスするLambdaを、VPC内で実行

# はじめに
RDS Proxy 経由で MySQL にアクセスするLambdaをVPC内で実行させましたので、手順をまとめます。

![スクリーンショット 2022-06-23 22.00.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6391a70c-3d26-f29d-2a5c-a8db47bbb6c3.png)


# RDS Proxyの作成
RDS Proxyは、以前作成した記事がありますので、こちらを参考にしてください。

https://qiita.com/hirai-11/items/07b25f81f1a983e0191c

# private subnetにLambdaを作成

## 設定

- IAMロール
    - `AWSLambdaENIManagementAccess`を付与します.これによってVPC上でLambdaが実行できます。
- タイムアウト時間
    - 30秒にします。実行に数秒かかります。
- 環境変数
    - RDS_HOSTNAME：proxyのライターエンドポイント
    - RDS_DB_NAME：データベース名
    - RDS_USERNAME：ユーザー名
    - RDS_PASSWORD：パスワード

- VPC
    - private subnetに設置
    - sgの設定は以下の通り

|設定  |タイプ|ポート|ソース|
|---|---|---|---|
| Lambda |All | All|0.0.0.0/0|
| Proxy | MySQL/Aurora |3306|LambdaのSG|
| Aurora | MySQL/Aurora |3306|ProxyのSG|

## mysqlモジュール
Lambdaは、Nodejsを使用します。

`mysql`モジュールを使用するため、ローカルで`mysql`モジュールをインストールします。

### LambdaLayerでアップロード
```terminal
$ mkdir nodejs
$ cd nodejs
$ tree
.nodejs
├── package-lock.json
└── package.json

// package name以外はデフォルトにしました
$  npm init -y

$ npm install mysql

$ tree
.nodejs
├── node_modules
├── package-lock.json
└── package.json

$ cd ..

$ zip -r nodejs.zip nodejs
```
nodejsフォルダごとzip化すること。

zip化したnodejs.zipをLambdaLayerにアップしましょう。

具体的なアップロード方法は、以前作成した記事を参考にしてください

https://qiita.com/hirai-11/items/58a968cc940051bd2fbd#lambda-layer%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89pythonnodejs%E5%85%B1%E9%80%9A

### Lambdaに直接アップロード
LambdaLayerを使用しない場合、以下のように作成します。
```terminal
$ mkdir nodejs
$ cd nodejs
$ tree
.nodejs
├── package-lock.json
└── package.json

// package name以外はデフォルトにしました
$  npm init -y

$ npm install mysql

$ touch index.js

$ tree
.nodejs
├── index.js
├── node_modules
├── package-lock.json
└── package.json

$ zip -r nodejs.zip *
```
nodejs.zipをLambdaにアップロードします
![スクリーンショット 2022-11-04 12.56.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/cfaef075-973c-a608-2eb7-d106e5bd6f0f.png)

## Lambdaのコード

```js
const hostname = process.env.RDS_HOSTNAME;
const databaseName = process.env.RDS_DB_NAME;
const password = process.env.RDS_PASSWORD;
const user_name= process.env.RDS_USERNAME;

const mysql = require('mysql');

const beginTransaction = (connection) => {
  return new Promise((resolve, reject) => {
    connection.beginTransaction((err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
};
const query = (connection, statement, params) => {
  return new Promise((resolve, reject) => {
    connection.query(statement, params, (err, results, fields) => {
      if (err) {
        reject(err);
      } else {
        resolve(results, fields);
      }
    });
  });
};
const commit = (connection) => {
  return new Promise((resolve, reject) => {
    connection.commit((err) => {
      if (err) {
        reject(err);
      } else {
        resolve(err);
      }
    });
  });
};
const rollback = (connection, err) => {
  return new Promise((resolve, reject) => {
    connection.rollback(() => {
      reject(err);
    });
  });
};

exports.handler = async (event) => {
  // console.log('event:', JSON.stringify(event, null, 2));

  const connection = mysql.createConnection({
    host: hostname,
    user: user_name,
    password: password,
    database: databaseName,
  });

  const now = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
  const sql = 'INSERT INTO database_table (title, created_at) VALUES (?, ?);';
  const inserts = ['test', now];

  try {
    await beginTransaction(connection);

    const results = await query(connection, sql, inserts);
    console.log(results);

    await commit(connection);
  } catch (err) {

    await rollback(connection);
    throw err;
  }
};
```
今回は、`database_table`というテーブルに、タイトルと作成日時を登録してみました。
`'INSERT INTO databaseTable (title, created_at) VALUES (?, ?);'`

### mysqlの使用メソッド
- `createConnection`
    - mysqlと接続します
- `beginTransaction`
    - トランザクションを開始します
- `query`
    - クエリを実行します
- `commit`
    - トランザクション中のデータ処理を確定します
- `rollback`
    - トランザクション中のデータ処理を取り消され、トランザクション実行前の状態に戻りトランザクションを終了します。

# モジュールを作成し、LambdaLayerで共通ライブラリ化
`beginTransaction`、`query`、`commit`、`rollback`の4つは、モジュールにして、LambdaLayerで共通ライブラリ化してもよいです。

```beginTransaction.js
module.exports = (connection) => {
  return new Promise((resolve, reject) => {
    connection.beginTransaction((err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
};
```
```query.js
module.exports = (connection, statement, params) => {
  return new Promise((resolve, reject) => {
    connection.query(statement, params, (err, results, fields) => {
      if (err) {
        reject(err);
      } else {
        resolve(results, fields);
      }
    });
  });
};
```
```commit.js
module.exports = (connection) => {
  return new Promise((resolve, reject) => {
    connection.commit((err) => {
      if (err) {
        reject(err);
      } else {
        resolve(err);
      }
    });
  });
};
```
```rollback.js
module.exports = (connection, err) => {
  return new Promise((resolve, reject) => {
    connection.rollback(() => {
      reject(err);
    });
  });
};
```



こちらの記事を参考にしました。

https://tech.chakapoko.com/nodejs/mysql/promise.html

```terminal
$ tree
.
└── node_modules
    ├── beginTransaction.js
    ├── commit.js
    ├── query.js
    ├── rollback.js
    ├── package-lock.json
    └── package.json
```
先程と同様に、nodejsをzip化し、アップロードします。

## Lambdaのコード
```js
const hostname = process.env.RDS_HOSTNAME;
const databaseName = process.env.RDS_DB_NAME;
const password = process.env.RDS_PASSWORD;
const user_name= process.env.RDS_USERNAME;

const mysql = require('mysql');
const beginTransaction = require('beginTransaction');
const commit = require('commit');
const query = require('query');
const rollback = require('rollback');

exports.handler = async (event) => {
  // console.log('event:', JSON.stringify(event, null, 2));

  const connection = mysql.createConnection({
    host: hostname,
    user: user_name,
    password: password,
    database: databaseName,
  });

  const now = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
  const sql = 'INSERT INTO databaseTable (title, created_at) VALUES (?, ?);';
  const inserts = ['test', now];

  try {
    await beginTransaction(connection);

    const results = await query(connection, sql, inserts);
    console.log(results);

    await commit(connection);
  } catch (err) {

    await rollback(connection);
    throw err;
  }
};
```

# mysql2
`2022.07.23 更新`
`mysql2`モジュールの方がコード量が少ないため、こちらをおすすめします

https://qiita.com/hirai-11/items/b4fa016bb5db1af2cb1d

# 参考

https://www.npmjs.com/package/mysql

https://qiita.com/aya_akatsuki/items/ea9a8beba93367ef7209

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html?icmpid=docs_lambda_help

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create-deploy-nodejs.rds.html