ものづくりIoTブログ

B-EN-GのIoTのスペシャリストが
IoT関連の先端技術及び最新トピックについての
取組みをご紹介します。

ブログのトップ画像

monozukuri-iot's topics

AWS 

RDSにもお休みを

2017/09/21 ・記事をシェアする

B-EN-G つかってみようIoTチーム赤熊です。

久しぶりにAmazon Web Serviceネタです。

RDSサービスも停止ができるようになっています

すでにご存知の方からすれば「何をいまさら」なお話ですが、2017年6月1日付けの「Amazon RDS でデータベースインスタンスの停止と開始をサポート」にてアナウンスされていますが、RDSも停止と起動の操作ができるようになりました!

このリリースまではRDSの停止はRDSインスタンスの削除を伴うもので、「起動」はなく最新のスナップショットを利用してインスタンスを「作成」する操作を要していました。

このスナップショットから作成した場合、セキュリティグループの設定は引き継がれないなど、1アクションでは元の状態に戻せないと若干不便な仕様になっていました。

RDSの利用料金はインスタンスの稼働時間に応じた課金と割り当てしたストレージやスナップショットの容量に応じた課金が主なものですが、稼働時間への課金が費用の割合としては高いものです。 使わない時にはRDSにはお休みしてもらって、AWSへのお支払いを節約してみたいと思います。

EC2の停止・起動とRDSサービスの稼動を連携させてみました

RDSを利用するアプリケーションをデプロイしているEC2の起動・停止にあわせてRDSを起動・停止するようCloud Watch とLambdaを構成してみました。

・Cloud WatchでEC2インスタンスの稼動状態変化を検出し、Lambda実行のイベントを発行

・LambdaでRDS操作のAPI発行

それでは順番は前後しますが、サンプル貼付とともに設定例の説明をしていきます。

注:本稿の手順ではEC2起動後、RDSが利用可能になるまで数分の遅れがあります。アプリケーションによっては起動時にDBとの接続を必要とするものがあります。その場合はRDSの起動完了はEC2起動前に完了するようスケジュールする必要があります。

Amazon RDS

特に設定は要りません。RDSコンソールから操作対象にするRDSインスタンスのRDSインスタンスIDを確認します。

EC2の設定

どのEC2のアプリケーションがどのRDSのインスタンスを利用しているものかを関連付けるためにEC2のタグにRDSのインスタンスIDを登録しました。

・・・・なんだか伏字だらけになってしまいました・・・・「UsingRDS」というキーでRDSのインスタンスIDを持っています・・・・

Lambda関数定義

StartStopRDSという名前でこんな関数を作ってみました。説明は設定貼り付けがひと段落した後にまとめます。

var aws = require('aws-sdk');
var myawsregion = 'ap-northeast-1';
aws.config.update({region: myawsregion});
var ec2 = new aws.EC2();
var rds = new aws.RDS();
var prefix = 'mybackup-';
// main
exports.handler = function(event, context) {
    console.log(JSON.stringify(event));
    process.env.TZ = 'Asia/Tokyo';
    dt = new Date();
    dtstr = dt.getFullYear() + ('0' + (dt.getMonth()+1)).slice(-2) + ('0' + dt.getDate()).slice(-2);

    var instanceid = event.detail['instance-id'];
    var state = event.detail['state'];
    var params = {
        Filters:    [{Name: 'tag-key', Values: ['UsingRDS'] }, ],
        InstanceIds: [instanceid],
    };
    ec2.describeInstances(params,function(err,data){
        if (!err && data.Reservations.length > 0) {
            var target = data.Reservations[0].Instances[0];
            console.log('Target RDS is work with EC2: ' + target);
            target.Tags.forEach(function(tag) {
	            if(tag.Key === 'UsingRDS') targetRDS = tag.Value;
            });
            // RDS STOP
            var dbsnap = prefix + targetRDS + '-asof-' + dtstr;
            if ( state === "stopping" ) {
                console.log('Stop The RDS:' + targetRDS);
                params = {
                    DBInstanceIdentifier: targetRDS,
                    DBSnapshotIdentifier: dbsnap, 
                };
                rds.stopDBInstance(params, function(err, data) {
                    if (err) console.log(err, err.stack); // an error occurred
                    else     console.log(data);           // successful response
                });
            } else if ( state === "running"){
                console.log('Start The RDS:' + targetRDS);
                params = {
                    DBInstanceIdentifier: targetRDS,
                };
                rds.startDBInstance(params, function(err, data) {
                    if (err) console.log(err, err.stack); // an error occurred
                    else     console.log(data);           // successful response
                });
            }
        }
    });
};

Cloud Watchの設定

Cloud Watchコンソールメニューの「ルール」からルールの作成を選択します。

「サービス別のイベントに一致するイベントパターンの構築」

「サービス名」からEC2を、「イベントタイプ」にEC2 Instance State-change Notificationを、「特定の状態」を選択します。状態はリストボックスから選択します。どの状態を選ぶかはEC2の使い方にもよると思いますが、今回は"shutting-down", "running", "stopping" を選びました。

{
  "source": [
    "aws.ec2"
  ],
  "detail-type": [
    "EC2 Instance State-change Notification"
  ],
  "detail": {
    "state": [
      "shutting-down",
      "running",
      "stopping"
    ]
  }
}

EC2イベントの選択が完了したら、コンソール右側のターゲットに登録しておいたLambda関数を指定します。

RDSインスタンスIDの取得

describeInstancesを使い、タグの設定値を取得しています。Cloud Watchから渡されるイベントには以下例のようにEC2のインスタンスIDがあるので、タグをフィルタ指定にはあまり意味はありません。

{
    "version": "0",
    "id": "0fffffff-1234-ffff-ffff-fffffffffff",
    "detail-type": "EC2 Instance State-change Notification",
    "source": "aws.ec2",
    "account": "1234567890",
    "time": "2017-09-18T23:31:39Z",
    "region": "xxxxxxxx",
    "resources": [
        "arn:aws:ec2:xxxxxxxx:1234567890:instance/i-abcdefghij"
    ],
    "detail": {
        "instance-id": "i-abcdefghij",
        "state": "running"

RDS停止時にはスナップショットを取得(お奨め)

stopDBInstance でRDSインスタンスを停止します。DBSnapshotIdentifierオプションを指定すると指定した文字列でスナップショットを取得してくれます。

DBSnapshotIdentifierオプションの指定は強くお奨めします。

RDSの自動スナップショット取得Windowがインスタンスの停止している時刻の場合、自動スナップショットは作成されないようです。DBデータの復旧ポイントを確保するため、このオプションの利用をお奨めします。

DBSnapshotIdentifierオプションはRDSスナップショット名称に使われますので、同じ名前のスナップショットが存在するとスナップショット作成に失敗します。例では年月日を名前重複回避と判別用に使っていますが、1日に複数回動く場合にはもう一工夫必要となります。

ちなみにDBSnapshotIdentifierオプションで作成されるRDSスナップショットは「手動」スナップショットとなりますので、スナップショット保管世代に応じた自動削除の対象になりません。

手動スナップショット削除Lambda

対象のRDSインスタンスは埋め込みですが、手動スナップショットがあまり堆積しないよう自動スナップショットの保管世代を参照し、古い世代の手動スナップショットを消す関数を時刻指定イベントで自動実行してみました。

var myawsregion = 'xxxxxxxxxx';
var instanceid = 'samplerds01';
var prefix = 'mybackup-';

var aws = require('aws-sdk');
aws.config.update({region: myawsregion});
var rds = new aws.RDS({apiVersion: '2014-10-31'});

// main
// Delete Old snapshot
exports.handler = function(event, context) {
    process.env.TZ = 'Asia/Tokyo';
    // Check if target RDS exists
	var params = { DBInstanceIdentifier: instanceid, };
	rds.describeDBInstances(params ,function(err,data){
		var target = data.DBInstances[0];
	    var snapArray = [];

		// calculate holding due date
        duedt = new Date();
        duedt.setDate( duedt.getDate() - target.BackupRetentionPeriod);
        console.log(target.DBInstanceIdentifier ,' : Delete Snapshots older than ' , duedt);
        params = { SnapshotType: 'manual',DBInstanceIdentifier: instanceid,};
        rds.describeDBSnapshots(params,function(err,data){
        	data.DBSnapshots.forEach(function(dbsnap){
        	    if (duedt > dbsnap.SnapshotCreateTime){
        	        if ( dbsnap.DBSnapshotIdentifier.indexOf(prefix) === 0 ){
        	            snapArray.push(dbsnap.DBSnapshotIdentifier);
        	        }
        	    } 
        	});
        	console.log("target: Delete -- " + snapArray);
        	if ( snapArray.length > 0 ){
        	    snapArray.forEach(function(dbsnap){
                    rds.deleteDBSnapshot({DBSnapshotIdentifier: dbsnap}, function(err, data) {
                        if (err)  console.log(err, err.stack);
                    });
                });
            } 
        });
    });
};

IoTに限らない話でしたが

いかがでしたでしょうか?

RDSの停止・起動はマルチAZのインスタンスには使用できないなどサービス上の制約や、本稿では説明省略したアプリケーション起動時にはDB稼動が必要など適合しない今回の例が適合しないケースは多々あると思いますが、AWSをリーズナブルなコストで活用するためのヒントになれば幸いでございます。

蛇足ではありますが・・・・本ページの記述は実際に稼動している設定から掲載のため、編集している箇所があります。記述の設定で動作を保障するものではありません。

・・・ご利用は自己責任でお願いします。

About Me

東洋ビジネスエンジニアリング株式会社の
「ものづくりIoT(Internet of Things)」の
取り組みを発信する公式サイトです。

"すべては変革のために"

ものづくりIoTに取り組むお客様の成功を
支援いたします!