こんにちは、中の人です。
今回は、AWS Lambda Advent Calendar 2014に投稿した内容を紹介します。
昨日のAWS Lambda Advent Calendar 2014は玉川さんの非常に心温まる「ばあちゃんにひ孫の写真をAPIで印刷&郵送する」という記事でした。
今日は11日目としてLambdaからセキュアにRDSに接続してみたいと思います。
○Lambda⇒RDSへ接続するときの課題点
・node.jsにMySQLパッケージを追加が必要
・LambdaがIPアドレス固定でないので、RDSのセキュリティグループをフル開放する必要がある
といった点がポイントとなります。
前者は「AWS LambdaでRDS(MySQL)に接続してみた」で詳しく解説されておりますので、後者に対する対策を考えてみました。
○LambdaのIPアドレス用プログラムの設置
Webサーバに以下のPHPを設置します。
Lambdaから以下のプログラムにアクセスすることでLambdaのグローバルIPを取得します。
設置が難しい場合はIPアドレスを取得できるようなサービスにアクセスして取得しましょう。
1 2 |
<?php echo $_SERVER['REMOTE_ADDR’]; |
○Lambdaのプログラム
Lambdaでは
1. 先ほどのプログラムからLambdaのIPアドレスを取得
2. RDSにLambdaのIPアドレスを追加
3. Lambdaから安全にアクセス
という流れになります。
まず、「AWS LambdaでRDS(MySQL)に接続してみた」を参考にMySQLのパッケージを追加します。
サーバ上で以下を実施します。
1 |
$ npm install mysql |
以下のプログラムをindex.jsとして保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
console.log('Loading event'); var aws = require('aws-sdk'); var ec2 = new aws.EC2({apiVersion: '2014-10-01'}); var mysql = require('mysql'); exports.handler = function(event, context) { var exec = require('child_process').exec; var cmd = "curl http://**************/ip.php;" // 先ほど作成したphpのプログラムでLambdaのIPを取得 var child = exec(cmd, function(error, stdout, stderr) { if (!error) { // IP ⇒ CIDR var ipAddress = stdout.replace(/[\n\r]/g,"") + "/32"; var params = { GroupId: 'sg-********', IpPermissions: [ { FromPort: 3306, ToPort: 3306, IpProtocol: 'tcp', IpRanges: [ { CidrIp: ipAddress }, ] }, ], }; ec2.authorizeSecurityGroupIngress(params, function(err, data) { if (err){ // 既にセキュリティグループが存在すれば、そのまま実行 result = err.toString().search( /already exists|has already been authorized/ ); if ( result > 0 ) { // LambdaからRDSを参照する var connection = mysql.createConnection({ host : 'test.**********.us-east-1.rds.amazonaws.com', //RDSのエンドポイント user : 'user', //MySQLのユーザ名 password : 'password', //MySQLのパスワード database : 'lambda' }); connection.connect(); connection.query('select * from lambda', function(err, rows, fields) { if (err) throw err; console.log(rows); }); connection.end(function(err) { // console.log(err); }); } else { console.log(err.stack); } context.done(); } else{ // LambdaからRDSを参照する var connection = mysql.createConnection({ host : 'test.**********.us-east-1.rds.amazonaws.com', //RDSのエンドポイント user : 'user', //MySQLのユーザ名 password : 'password', //MySQLのパスワード database : 'lambda' }); connection.connect(); connection.query('select * from lambda', function(err, rows, fields) { if (err) throw err; console.log(rows); }); connection.end(function(err) { // console.log(err); }); } }); } else { console.log("error code: " + error.code + ", err: " + error); context.done(error,'lambda'); } }); }; |
続いてindex.jsと先ほどインストールしたMySQLのパッケージのあるnode_modulesをまとめてzip化します。
圧縮ファイルの中身が
1 2 3 |
index.js /node_modules /mysql |
の様になっていればOKです。
lambda上にzip形式でアップして実行してみましょう。
1 2 3 4 5 6 |
START RequestId: a8a42c4f-8147-11e4-b54d-595292db8ce5 2014-12-09T15:09:10.049Z a8a42c4f-8147-11e4-b54d-595292db8ce5 Loading event 2014-12-11T15:09:10.369Z a8a42c4f-8147-11e4-b54d-595292db8ce5 {} 2014-12-11T15:09:10.249Z a8a42c4f-8147-11e4-b54d-595292db8ce5 [ { id: 1, test: 'name' } ] END RequestId: a8a42c4f-8147-11e4-b54d-595292db8ce5 REPORT RequestId: a8a42c4f-8147-11e4-b54d-595292db8ce5 Duration: 2050.61 ms Billed Duration: 2100 ms Memory Size: 128 MB Max Memory Used: 24 MB |
上記のように接続でき、SecurityGroupを確認するとLambdaのIPアドレスが追加されていればOKです。
ちょっと強引な方法ですが、工夫することでLambdaからRDSにもセキュアにアクセスすることが出来ます。
※プログラム内でsleepをかけてからMySQL接続したかった(これが出来ないので若干動作が不安定です…)のとSQL実行後にSecurityGroupを削除を実装したかったのですが、setTimeoutでうまく制御出来なかったので後日挑戦したいと思います。
もし、良い方法を御存知でしたらアドバイスください!
明日は@shot6さんの「Lambdaで地味な何かつくってみますー」です。
お楽しみに!