AWS LambdaでRDS（MySQL）のmysqldumpを取るようにしました

[ヴァル研究所 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/val)の22日目です。

AWSのRDSインスタンスは毎日自動でスナップショットが取れるようになっていますが、mysqldumpも取りたいと思ってAWS Lambdaでやってみました。

## 使った主なAWSサービス
+ Lambda  
RDSインスタンスをプライベートサブネット内に配置しているため、LambdaをVPC内に配置しました。
+ Key Manager Service  
Lambda内で使用するRDSのパスワードを暗号化・復号するため。
+ S3  
mysqldumpデータを保存するため。  
ライフサイクルを設定して、一定期間を過ぎたら削除するようにしました。

## 実装
LambdaのRuntimeはNode.js 4.3にしました。
認証情報は環境変数で渡しています。

+ index.js

```js
'use strict';

const AWS = require('aws-sdk');
const mysqlDump = require('mysqldump');
const fs = require('fs');
require('date-utils');

var date = new Date();
var timeStamp = date.toFormat('YYYYMMDDHH24MISS');
// dumpファイルのファイル名
const dumpFileName = 'dump_' + timeStamp + '.sql';
const dumpPath = '/tmp/' + dumpFileName;

// メインの処理
exports.handler = (event, context) => {
  const kms = new AWS.KMS({ region: 'ap-northeast-1' });
  // 暗号化したデータベースのパスワードをKMSに渡す形式のJSONにする
  const cipherText = { CiphertextBlob: new Buffer(process.env.PASSWORD, 'base64') };

  // パスワードの復号を実行する
  kms.decrypt(cipherText, (err, data) => {
    if (err) {
      // エラーが起こった場合、ログを出力
      console.log('Decrypt error:', err);
    } else {
      console.log('Decrypt Success.');

      // 復号に成功した場合、パスワードとして変数に代入
      var password = data.Plaintext.toString();
      // 不要な記号（”）が文字列の最初と最後に付くため、切り取る
      password = password.slice(1, -1);

      // mysqldumpを実行
      mysqlDump({
        host: process.env.HOST,
        user: process.env.USER,
        password: password,
        database: process.env.DBNAME,
        dest: dumpPath
      }, (err) => {
        // エラーが起こった場合、ログを出力
        if (err) {
          context.fail(err);
          console.log('mysqldump error:' + err);
        } else {
          console.log('mysqldump saved.');
          // mysqldumpの作成が成功した場合、S3へdumpファイルを送信する
          sendS3();
        }
      });
    }
  });
};

// S3へdumpファイルを送信する
function sendS3() {
  const s3 = new AWS.S3();

  var params = {
    Bucket: （保存先のバケット名を指定）,
    Key: 'mysqldump/' + dumpFileName,
    Body: fs.readFileSync(dumpPath)
  };

  // S3にmysqldumpを配置する
  s3.putObject(params, (err) => {
    if (err) {
      console.log('S3 PutObject error:', err);
    } else {
      console.log('mysqldump is uploaded to S3.');
    }
  });
}
```

+ GitHubリポジトリ  
[[nakano348/mysqlDumpFunction]](https://github.com/nakano348/mysqldumpFunction/)

## 詰まったところ
### VPC内のLambdaからのS3・KMSの操作
Lambda functionに付与するIAM Roleで指定したKMSのキーやS3バケットを扱えるように設定したのに、なぜかエラーが返ってくると思ったら、VPCの設定が足りていませんでした。

VPC内のLambdaでは、VPC内以外のAWSリソースを扱う場合は配置したプライベートサブネットからインターネットアクセスが出来るよう設定しないといけませんでした。

アクセスするにはVPCエンドポイントを設定するか、NATを使う必要がありました。
（下記の参考ページに詳細が書いてありました。有難うございます。）

S3だけであれば、VPCエンドポイントで出来るようなのですが、
KMSを使用ため、今回はNATを使用する方法で解決しました。

#### 参考ページ
+ VPC Lambdaからs3へアクセスする
http://qiita.com/ijin/items/94c0bc4b8f6f5e77a591

+ Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する
「ラムダ関数でのインターネットアクセス」の部分が関連します。
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html

### KMSでの復号
なぜか復号すると `”` という記号が文字列の前後についていました。
パスワードが`hoge`であってほしいところが、`”hoge”`といった状態でした。
応急処置で、最初と最後の文字（`”`）をカットするようにしています。

## 改善できそうなところ
+ コードを役割ごとに分割する  
現状は、KMSで復号・mysqldump取得・S3への送信を1つのコードにしてしまっていますが、先日発表された[Step Functions](https://aws.amazon.com/jp/step-functions/)等を使うと分割できるのかもしれません。  
KMSの復号部分のLambdaを分割して、VPC外に配置した状態で、Step Functionsで複数のLambdaが実行できれば、NATゲートウェイの部分は不要になりそうです。  
[NATゲートウェイも1時間ごとに課金](https://aws.amazon.com/jp/vpc/pricing/)なので、使わなければコストも抑えられそうです。

## まとめ
簡単なコードで済むもので定期実行したい場合は、Lambdaがぴったりな印象です。
Lambdaはアップデートが多いので今後も実現できることが増えていきそうですね。

この記事がみなさまの何かのお役に立ちましたら幸いです。