AWSのロードバランサーにLambdaを使ってWEBサーバーを自動追加


目的:アクセス集中時のみWEBサーバーを追加・負荷分散しコストダウン

 AWSのロードバランサー(ELB)を使っているサイトも多いかと思われます。ELBにはオートスケール機能があり、負荷率の上昇などをトリガーとしてスケールアウト(WEBサーバーの追加)を設定することができます。

 しかし、Auto Scalingを利用して負荷が上がってからWEBサーバーを立ち上げて追加したのでは時間がかかり、手遅れになる可能性が高いです。障害が出る前にサーバーを増設したいと思うでしょう。

 私の会社が開発しているソーシャルゲームでも、イベント開催時や広告プロモーション時には、普段の十数倍のアクセスになります。こういう負荷上昇が見込まれる場合は、あらかじめEC2インスタンスを立ち上げてELBに追加しておくことが有効でしょう。

 Lambdaのスケジュール機能を使い、アクセスの見込まれる必要な時刻にEC2インスタンス(WEBサーバー)をELBに自動的に追加し、ある程度の時間が過ぎたらELBから切り離す。これだけでは負荷分散はできてもコスト削減はできません。コスト削減のためには、EC2インスタンスを停止しなくてはなりません。EC2インスタンスの自動起動とELBに追加、ELBからの切り離しとシャットダウンが必要です。

 下記の記事リンクでLambdaを使ってEC2インスタンスの自動起動のやりかたを説明しましたので、これとあわせて設定することが必要です。
   AWSのEC2サーバーをLambdaを使って自動起動

前提条件

 すでにロードバランサー(ELBv2)を使用して、ターゲットグループにインスタンス(WEBサーバー)を追加済みで、WEBアクセスを分散している前提です。ピークにあわせて、インスタンスをターゲットグループに自動的に追加するというスクリプト関数を設定します。
 インスタンスは既に構築済みというのが、AWSのオートスケール機能と違うところです。つまり、あらかじめインスタンスを作成しておき、このインスタンスをロードバランサーに加えるというスクリプトを作ります。

AWS Lambdaコンソールで関数を作成しロールを付与

 Lambdaコンソールで関数を作成します。以前作成したEC2サーバーの起動とは別の関数名「Add_elb」にします。

アクセス権限をクリックし、ロードバランサーのターゲットグループにEC2インスタンスを追加・削除できる権限(ロール)を付与します。
IAMマネージャーから、追加のサービスポリシーを登録します。サービスを最初に選択しますが、ELBには、初期の「ELB」と、「ELB v2」があるので、「ELB v2」の方を選択します。
 次にアクションを「register」で検索すると、「DeregisterTargets」「RegisterTargets」が表示されます。これはそれぞれ、ロードバランサーのターゲットグループにインスタンスを「削除」「登録」できる権限を与えます。

EC2インスタンスをロードバランサーに追加するスクリプト

ロールの設定が終わったら、Lambdaの関数の表示する画面に戻り、下記のようにindex.jsの関数コードを書き換えます。

index.js
const INSTANCE_ID = ['i-0b1ec8e45xx6293a9','i-0a3393fcxxb68a626','i-0b01xx0783bef752d']

const ARN = "arn:aws:elasticloadbalancing:ap-northeast-1:371633xx9153:targetgroup/web/e6cccc2a33xx2c4"; // ターゲットグループ「web」

var AWS = require('aws-sdk'); 
AWS.config.region = 'ap-northeast-1';

exports.handler = function(event, context) {
    var elbv2 = new AWS.ELBv2();
    var params = {
        TargetGroupArn: ARN, 
        Targets: [ {Id: 'dummy' }]
    };
    console.log('Script Start!');

   for (let i = 0; i < INSTANCE_ID.length; i++) {
       params.Targets[i] = {Id: INSTANCE_ID[i]};
   }

    elbv2.registerTargets(params, function(err, data) {
        if (!!err) {
            console.log(err, err.stack);
        } else {
            console.log(data);
        }
    });
};

 ELBに登録するインスタンスは、下記のように複数指定することが可能ですので、同時に複数のインスタンスの追加ができます。

const INSTANCE_ID = ['i-052838b19d5xxxxx1', 'i-052838b19d5xxxxx2', 'i-052838b19d5xxxxx3']; 

 ターゲットグループの指定は、AWSのEC2>ロードバランシング>ターゲットグループでターゲット名を選択すると、arn:で始まるターゲットグループ名が表示されるので、コピーして転記します。

const ARN = "arn:aws:elasticloadbalancing:ap-northeast-1:371633xx9153:targetgroup/web/e6cccc2a33xx2c4";

設定が終わったら、コード上部の「Save」または右上の「保存」をクリックし、「Test」または「テスト」をクリックします。
 実行結果でエラーが出なければ正常ですので、ターゲットグループに指定したインスタンスが追加されているか確認してみてください。

ロードバランサーからWEBサーバーを自動解除

 逆にロードバランサーからWEBサーバーを切り離すには、違う名前で例えば「Del_elb」という名前の関数スクリプトを作り、上記のスクリプトをコピー。「elbv2.registerTargets」のメゾットの行を次のように書き換えるだけです。パラメータは同じなので、「registerTargets」か「deregisterTargets」の違いだけです。

    elbv2.deregisterTargets(params, function(err, data) {

実際の運用

 先の私の記事リンクで作成した関数とあわせて運用を計画してください。

AWSのEC2サーバーをLambdaを使って自動起動

 実際の流れとしては、イベントやプロモーションの開始時間にあわせて、あらかじめ予備サーバーをスタンバイ(停止)しておき、CloudWatch/Eventで時刻になったら各関数を起動するようにCRONでスケジューリングします。
EC2インスタンスの起動に1分、最新ソースのデプロイ(更新)に最大2分程度を見込んだスケジュールです。

-開始時間5分前に予備サーバーを起動。起動スクリプトで最新ソースプログラムをデプロイ(更新反映)
-開始時間になったらロードバランサーに予備サーバーを追加
-(予備サーバーを含め負荷上昇に対して運用)
-終了時間になったらロードバランサーから予備サーバーを外す
-終了時間1分後に予備サーバーをシャットダウン

これで予定された負荷上昇への対応が自動化でき、予備サーバーは通常はインスタンスを停止しておけるので、コスト削減にも貢献できます。

もちろん負荷対応はWEBサーバーだけでは無いですが。