LambdaでVPC内にあるEC2にmysqldumpを実行しS3にアップロードする

# やりたいこと
AWSのLambdaを使って、EC2上のMySQLのdumpを取得後、S3にアップロードする。

## ポイント
VPC内にあるEC2とVPCの外にあるS3のこの両方にアクセスが必要:writing_hand:
というのがハマりポイントでした。
(これまでLambdaを使ったことなかったのであまりわかってなく、簡単にいけるだろうと思って、何も考えずにやってそれ以外の箇所でも結構ハマりました)

## 構成図
最終的にはこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/1eec29ad-d55f-fa2b-21b6-09b114d13484.png)

# 手順

# 1.Lambda Functionを作る

コンソールからLambdaを選択肢、Create Functionで作る。
- Runtimeは、今回Node.jsを選択します。(ここはお好みで)
- Roleは今回新しくLambda用に作成する形にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/216422a3-4a2e-ce56-c86e-756870930265.png)

# 2.Lambda スクリプトを実装する

### 2-1.スクリプト実装

```javascript:index.js
'use strict';

const aws = require('aws-sdk');
const mysqldump = require('mysqldump');
const fs = require('fs');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
require("date-utils");

var date = new Date();
var timeStamp = date.toFormat('YYYYMMDDHH24MISS');
const dumpFileName = 'dump_' + timeStamp + '.sql';
const dumpPath = '/tmp/' + dumpFileName;

exports.handler = async (event, context) => {
    
    console.log("start mysqldump");
    console.log('dumpFileName:' + dumpFileName);
    
    const resultDump = await mysqldump({
    connection: {
        host: process.env.DBHOST,
        user: process.env.DBUSER,
        password: process.env.DBPASS,
        database: process.env.DBNAME,
    },
    dumpToFile: dumpPath,  // Lambda上のサーバにdumpファイルを一時的に置く場所
    });

    console.log("start s3 upload"); 
    var params = {
        Bucket: process.env.BUCKETNAME,
        Key: process.env.BUCKETKEY + dumpFileName,
        Body: fs.readFileSync(dumpPath)
    };

    var resultPutS3 = await s3.putObject(params).promise();
    console.log(resultPutS3);
}
```

### 2-2.環境変数設定

上記スクリプト内で process.env.xxxとなっていた箇所です。
Configuration → Enviroment variablesから設定できます。
※Dateのタイムゾーンは日本にしておきたいため、TZでセットしています。(ここは自身の環境に合わせてTZをセットすると良いと思います)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/a6de7ddc-f207-f587-a9bf-eb64f6bf38dc.png)


#### 実行してみる

Codeから、Deployを実施したのち、Testを選択すると実行できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/bd81e07f-bc98-7a70-2eba-ca69e5b73946.png)


###### 結果
`Runtime.ImportModuleError` が発生します :sweat:

###### 原因
awsのライブラリ以外は、ローカルからアップロードする必要があるため:writing_hand:
(Lambda初心者の私はこれを知らなかったため、最初はなぜエラーなのかわかりませんでした。。)

### 2-3. スクリプトに必要なライブラリをアップロード

#### 2-3-1.ローカルでスクリプトとライブラリをZIPで固めたものを作成する
Lambda上のファイルのコピーをlocalに作成します。

```terminal
-rw-rw-r--@ 1 yukiyoshimura  admin  1090  9 12 14:19 index.js
[yukiyoshimura sample]$ 
```
aws以外のライブラリをnpmでダウンロードします。

```terminal
npm install mysqldump
npm install date-utils
```

zipで固める

```terminal
[yukiyoshimura sample]$ zip -r ../sample.zip ./
```

#### 2-3-2.lambdaへアップロードする
以下からアップロードします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/a9cacabf-212c-dc3e-6fee-dd03c692acfa.png)
アップロード後
node_modulesディレクトリに必要なライブラリが入った状態でlambdaコンソール上で確認できればOK⬇︎
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/e353df47-8936-68f7-70b8-eadf1a8f1e57.png)

#### 再び実行してみる
Time out するけど、 `Runtime.ImportModuleError` が解消されていれば一旦OKです。

## 3.LambdaがEC2にアクセスできるようにする

### 3-1. Lambdaに紐づけているroleに権限を付与する

Configuration → permissions
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/7c3fdf10-e5ed-03bc-1e54-6fc6cfc8e640.png)

以下をセットしておきます。
`AWSLambdaVPCAccessExecutionRole`
`AmazonS3FullAccess`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/2401a2b3-850f-4533-a49f-bf7af67ac2a1.png)


### 3-2. lambdaをVPCに登録する

Configuration → VPCを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/61f9568c-bee5-1ae4-95ea-fea1689c5cd6.png)

- VPCは、アクセスしたいEC2のVPCと同じものをセットします。
- Subnetは、Lambda用にPrivate Subnetを作ったのでそれをセットしました。
- Security groupsは、デフォルトのものをそのままセットします。(インバウンドなし、アウトバウンドAll)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/736be85b-8317-bbd7-a1dd-c053e22afa75.png)

## 4.EC2に対してmysqldumpができるようにする

EC2に関連付けしているSecurity groupのインバウンドに以下を追加する。
Sourceのところは、今回、LambdaでセットしたSecurity Groupと同じものをセットしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/ba8add34-9f1a-e950-2743-123c7f2297a1.png)

## 5.VPCに登録したlambdaからS3にアクセスできるようにする

### S3アクセス用のVPC Endpointの作成

VPC → Endpoints → Create Endpoint
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/6255c2e3-b497-8ef4-004e-df803fbbcc4e.png)

S3のエンドポイントを作成します。今回は、Gatewayで作成しています。
VPCは、EC2と同じもの。
routetablesは、Lambdaでセットしたsubnetが紐づいているものを指定する。
https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/vpce-gateway.html
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/b8788c01-de27-9fa4-1eb9-1c8092dc7b4b.png)

endpointを作成すると、指定したroutetablesにS3へのendpointが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/3b0e8fbe-2a08-7551-89bb-46c2c2fe0d78.png)

## その他

#### lambdaのタイムアウトはデフォルト3秒なので10秒くらいにしておくと良い

ネットワークのエラーでタイムアウトなのか、処理時間でのタイムアウトなのか見分けがつかない時がありました。
今回のdumpしてからS3へのアップロードだと3秒では終わらなかったので10秒にしました。

Configuration → General configuraton から変更できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52335/ccbda83d-b87c-c777-2d78-3be78e840ef1.png)

#### MySQLに外から繋ぐため、外部からの接続を許可しておく必要があります
この辺りを参考に。
自分はlambda接続用のユーザを作成しました。
https://qiita.com/yoshiokaCB/items/df4ae185be7cbc4f03ac

## まとめ
VPC内へのアクセスと外のアクセスが必要だったため、色々設定が必要でしたが、何とかできました。
VPC Endopointを利用する以外にもNATゲートウェイを使う方法もあるようです。

参考にしたページ
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html
https://devlog.arksystems.co.jp/2018/04/04/4807/