AWS Lambda(js)で夜間に開発環境のEC2インスタンスを停止する

AWS Lambda(js)で夜間に開発環境のEC2インスタンスを停止する

みなさまこんにちはゴリさんです。

エンジョイワークスではAWSを利用しており、EC2でサーバーを稼働させています。
ご存知の通りEC2は従量課金となっており起動している時間分費用が発生します。

本番環境であれば24時間稼働が当たり前なのですが、検証や動作確認を行うためのステージング環境や開発環境用に立てているEC2インスタンス、利用者がいない時間に稼働させるのって無駄だと思いませんか?

節約は小さいところからコツコツと。
今日はAWSの節約術の定番、開発系EC2インスタンスを夜間停止をLambdaで行う方法をご紹介します。

公式ドキュメントにpythonを使った例がありますので、今回はjavascriptを使っていきたいと思います。

IAMロールの作成

LambdaからEC2インスタンスの停止/開始が行える様、IAMロールを作ります。
AWSのコンソールからLambdaのIAMロールの作成を行ってください。

権限はAmazonEC2FullAccessを与えます(アクセス権が広すぎると気になる方は公式ドキュメントの様にIAMポリシーを作成しても良いと思います)

名前をつけてロールを作成します、名前はなんでも良いです。
今回は「EC2InstanceControlRole」と名付けました。

「ロールの作成」クリックでLambdaからEC2インスタンスを操作するためのIAMロールが作成されます。

Lambda関数の作成

続いて制御を行うLambda関数を用意します。
名前を「SaveCostEC2」、ランタイムをnodejsにし、先ほど作ったIAMロールを割り当てて作成します。

作成したらコードを書いていきましょう。
公式ドキュメントの方法ではインスタンスID指定で制御していますが、メンテナンスなどでインスタンスが再起動したらインスタンスIDが変わる可能性もあります。
今回はタグを指定して対象のインスタンスを特定する形にします。

Lambda組み込みのAWS-SDKを使います。
ec2.describeInstancesのパラメタのフィルターでタグが指定できますので使っていきましょう!

const AWS = require("aws-sdk");
AWS.config.update({ region: process.env.AWS_REGION });

const describeInstances = async (ec2) => {
  return new Promise((resolve, reject) => {
    ec2.describeInstances(
      {
        Filters: [{ Name: "tag:Economize", Values: ["true"] }],
      },
      (err, data) => {
        if (err) {
          reject(err);
          return;
        }
        const instances = data.Reservations.reduce(
          (acc, current) => acc.concat(current.Instances),
          []
        );
        resolve(instances);
      }
    );
  });
};

exports.handler = async (event) => {
  const ec2 = new AWS.EC2({ apiVersion: "2016-11-15" });
  const instances = await describeInstances(ec2);
  console.log(instances.map(i => i.InstanceId))
};

「Economize」というタグに”true”が設定されているインスタンスを取得し、そのインスタンスIDをコンソール出力しています。

まずは意図通りにインスタンス情報が取れているか確認します。
早速実行したいところですが、Lambdaの実行時間はデフォルトで3秒になっているのでタイムアウトしてしまう可能性があります。
先に「設定」>「基本設定」からタイムアウトの時間を1分に延長します。


続けて適当にEC2インスタンスを何本か起動し、「Economize」を設定してみましょう。

EC2インスタンスの起動が終わったらAWSのコンソールから実行してみます。
「Deploy」をクリック後、「Test」をクリックしてください。
(「Test」クリック後はテストイベントの作成が促されますが、Hello-worldのテンプレートのままで適当に名前をつけてもらってOKです)

処理結果のログに先ほど起動してタグを設定したEC2インスタンスのIDが表示されてれば成功です!

処理対象のインスタンスの情報を取得できることを確認しました。
続けてインスタンス開始の処理を追加します。

AWS-SDKのec2.startInstancesを使ってインスタンスの開始指示を行います。
(開発環境ということもありますので、インスタンス開始の完了を待つコードは入れません)

const startInstances = async (ec2, instances) => {
  return new Promise((resolve, reject) => {
    ec2.startInstances(
      {
        InstanceIds: instances.map((i) => i.InstanceId)
      },
      (err, data) => {
        if (err) {
          reject(err);
          return;
        }
        resolve();
      }
    );
  });
};

続けて同じ要領で停止の処理を追加します。

const stopInstances = async (ec2, instances) => {
  return new Promise((resolve, reject) => {
    ec2.stopInstances(
      {
        InstanceIds: instances.map((i) => i.InstanceId)
      },
      (err, data) => {
        if (err) {
          reject(err);
          return;
        }
        resolve();
      }
    );
  });
};

次にhandlerの現在コンソールに出力している部分を上記処理を呼ぶ形に修正します。

exports.handler = async (event) => {
  const ec2 = new AWS.EC2({ apiVersion: "2016-11-15" });
  const instances = await describeInstances(ec2);
  if (event.action === "stop") {
    await stopInstances(ec2, instances);
  } else {
    await startInstances(ec2, instances);
  }
};

event.actionに設定されている値でどちらの処理をするか呼び分けています。
先ほど「Hello-world」で作ったテンプレートのイベントを以下の様なjsonに修正して動作確認を行ってください。

{
  "action": "stop"
}

実行後、対象のインスタンスが停止し、actionをstartに変えることで対象のインスタンスが開始する様になったと思います。

トリガー登録

次にLambdaを実行するためのトリガーを追加します。
火-土のAM0:00に停止したいので、EventBridge (CloudWatch Events)のトリガーを登録します。

トリガーが無事追加されたと思います。
このままだとevent.actionが指定されませんのでリンクをクリックしてEventBridgeの詳細画面を開きます。

設定項目はいくつかありますが、「ターゲットを選択」セクションの中に「入力の設定」というものがありますので、それを展開してJSONテキストを設定し{“action”:”stop”}と入力してください。

そのまま更新すればOKです。


続けて同じ様に月-金のAM8:00にインスタンスを開始するトリガーを追加します。

以上で設定は終わりです。
Lambdaの実行結果はCloudWatchに出力されます、ちゃんと動いているか気になる方は確認してみてください。

まとめ

いかがだったでしょうか、私はjavascriptが手に馴染んているので採用しましたがコードが若干冗長気味なのは否めませんね。。
ただpythonでもjavascriptでも良いのでサーバーがなくてもこういう形でインフラを操作できる、というのは覚えておいた方が良いと思います!
RDSやElasticSearch Serviceなど起動しているだけで費用がかかるサービスは他にもありますので費用対効果などを鑑みつつコツコツ節約していきましょう。

それではまた。

一覧へ戻る

株式会社 エンジョイワークス

令和4年度 国土交通省PPP協定パートナー
宅地建物取引業 [神奈川県知事(3)第28062号]
一級建築士事務所 [神奈川県知事登録 第16506号]
不動産特定共同事業者 [金融庁長官・国土交通大臣 第114号]
(第1号、2号、3号、4号に掲げる事業を行う)
第二種金融商品取引業 [関東財務局長(金商)第3148号]
住宅宿泊管理業者 [国土交通大臣(01)第F00604号]
(関連会社グッドネイバーズにて取得)

ファンド出資時の注意事項

  • 出資に際しご負担頂くのは出資金額のみで、それ以上の支払は発生しません。出資金から年度毎に管理報酬を、その他銀行手数料等をご負担頂きます。
  • 事業の状況により、利益の分配が行われない可能性及び返還される出資金が元本を割る可能性があります。詳しくはハロー!RENOVATIONの「ファンド情報」から内容をご確認ください。
  • 不動産特定共同事業のファンドにおいて、株式会社エンジョイワークスは、当事者として不動産特定共同事業法第2条第3項第2号の匿名組合型契約を出資者と締結します。