0
1

EC2を自動起動停止させるLambdaをNode.js18に移植した

Last updated at Posted at 2023-12-11

はじめに

EC2を自動起動停止させるLambdaをたぶんNode.js v6かv8のころから使ってきました。
Node.js v14まではコードを修正せずに動いたのですが、v18に上げたらそのままでは動かなかったので、やったことを残しておきます。

Node.js v14まで

ソースコード

元々使っていたコードは下記です。

index.js
var aws = require('aws-sdk');
var ec2 = new aws.EC2();

exports.handler = (event, context, callback) => {
    'use strict';

    let filter_status;
    switch (event.type) {
        case 'start':
            filter_status = 'stopped';
            break;
        case 'stop':
            filter_status = 'running';
            break;
        default:
            callback({ 'exception': 'unexpected_argument' });
            return;
    }

    const tagValue = event.tagvalue || process.env.TAG_VALUE;

    ec2.describeInstances({
        'Filters': [
            {
                Name: 'tag:' + process.env.TAG_TITLE,
                Values: [tagValue]
            },
            {
                Name: 'instance-state-name',
                Values: [filter_status]
            }
        ]
    }, (err, data) => {
        if (err) {
            callback(err);
            return;
        }

        if (data.Reservations.length === 0) {
            callback(null, { 'result': 'There is no instance to be operated.' });
            return;
        }

        let target_instance_ids = [];
        data.Reservations.forEach(reservation => {
            reservation.Instances.forEach(instance => {
                target_instance_ids.push(instance.InstanceId);
            });
        });

        if (event.type === 'start') {
            ec2.startInstances({ InstanceIds: target_instance_ids }, instance_operate_callback(callback));
        } else {
            ec2.stopInstances({ InstanceIds: target_instance_ids }, instance_operate_callback(callback));
        }
    });
};

function instance_operate_callback(lambda_callback) {
    return (err, data) => {
        if (err) {
            lambda_callback(err);
            return;
        }
        let operated_instances = data.StartingInstances || data.StoppingInstances;

        let message = '\n' + operated_instances.length + 'instance(s)\n\n';
        operated_instances.forEach(instance => {
            message += '* ' + instance.InstanceId + '\n';
        });
    };
}

設定

必要な設定は下記です。

  • アクセス権限・・・AmazonEC2FullAccess
  • 環境変数
    • TAG_TITLE・・・AutoRun
      • このタグを持つEC2を自動起動停止の対象とする
    • TAG_VALUE・・・weekday
      • AutoRunタグの値がこれだったら自動起動させる
  • トリガー
    • EventBridge・・・stop_ec2
      • スケジュール・・・cron(05 12 ? * MON-FRI *)
      • ターゲット1・・・入力定数:
        • {
            "type": "stop",
            "tagvalue": "autostop"
          }
          
      • ターゲット2・・・入力定数:
        • {
            "type": "stop",
            "tagvalue": "weekday"
          }
          
    • EventBridge・・・start_ec2
      • スケジュール・・・cron(00 01 ? * MON-FRI *)
      • ターゲット1・・・入力定数:
        • {
            "type": "start"
          }
          

使い方

自動起動停止させたいEC2にAutoRunというタグを設定してください。
値による挙動の違いは下記の通りです。
weekday=自動起動停止, autostop=自動停止のみ, none=何もしない

上記は手順通りに設定した場合で、EventBridgeの入力定数を変えることで、任意のタグを利用できます。
例えば「このEC2だけ午前4時に起動させたい」という場合は、

{
  "type": "start"
  "tagvalue": "start0400"
}

という入力定数を持つトリガーと、このトリガーを持ち午前4時に起動するEventBridgeを作成し、対象のEC2にAutoRun:start0400というタグを付与することで実現可能です。

「none=何もしない」に関しては特にそのように実装しているわけではなく、トリガーに使われていない値なら何でも良いし、AutoRunタグ自体を削除するでも良いです。明示的に何もして欲しくないという意思表示のためにnoneを採用しただけです。

Node.js v18

v18で変わったこと

AWS SDK for JavaScript v2からAWS SDK for JavaScript v3に変わったらしいです。
importの仕方が変わりました。

元々importではなくrequire使ってましたが、これもrequire is not defined in ES module scope, you can use import insteadと言われてしまうので書き換えました。

主な変更点は以下の通りです。

  • 必須
    • requireをimportに置き換え、AWS SDK for JavaScript v3を使用しました。
    • AWS SDKのメソッド名をv3に対応したものに変更しました。
  • オプション
    • 非同期操作の処理をasync/awaitを利用して変更しました。(v14以降サポート)
    • カスタムのinstance_operate_callback関数を削除し、AWS SDKのメソッドとasync/awaitを直接利用するようにしました。

ソースコード

修正後のソースコードは下記です。
index.mjsとpackage.jsonを同じディレクトリに配置してください。

index.mjs
import { EC2Client, DescribeInstancesCommand, StartInstancesCommand, StopInstancesCommand } from '@aws-sdk/client-ec2';
const ec2 = new EC2Client();

export const handler = async (event) => {
    'use strict';

    let filter_status;
    switch (event.type) {
        case 'start':
            filter_status = 'stopped';
            break;
        case 'stop':
            filter_status = 'running';
            break;
        default:
            throw new Error('unexpected_argument');
    }


    const tagValue = event.tagvalue || process.env.TAG_VALUE;

    try {
        const data = await ec2.send(new DescribeInstancesCommand({
            Filters: [
                {
                    Name: 'tag:' + process.env.TAG_TITLE,
                    Values: [tagValue]
                },
                {
                    Name: 'instance-state-name',
                    Values: [filter_status]
                }
            ]
        }));

        if (data.Reservations.length === 0) {
            return { result: '操作対象のインスタンスが見つかりません。' };
        }

        let target_instance_ids = [];
        data.Reservations.forEach(reservation => {
            reservation.Instances.forEach(instance => {
                target_instance_ids.push(instance.InstanceId);
            });
        });

        if (event.type === 'start') {
            await ec2.send(new StartInstancesCommand({ InstanceIds: target_instance_ids }));
        } else {
            await ec2.send(new StopInstancesCommand({ InstanceIds: target_instance_ids }));
        }

        let message = '\n' + target_instance_ids.length + '台のインスタンスが操作されました。\n\n';
        target_instance_ids.forEach(instanceId => {
            message += '* ' + instanceId + '\n';
        });

        return { message };
    } catch (err) {
        throw err;
    }
};
package.json
{
  "type": "module"
}

設定

設定はそのまま流用できます。

使い方

使い方も同じです。

テスト手順

Lambda関数の設定画面上、コードの右にある「テスト」タブで即時実行させられます。
※殆どの人には関係ない話ですが、zscaler経由だとテストが実行できません。

  1. テストに使うEC2のタグをAutoRun:autostop-testに書き換える
  2. Lambda関数 > テスト
  3. 自動停止テスト
    1. イベントアクションをテスト・・・新しいイベントを作成
    2. イベント名・・・autostop-test
    3. イベント共有の設定・・・プライベート
    4. テンプレート - オプション・・・何でも良い。一般>hello-worldとかで可
    5. イベント JSON
      1. {
          "type": "stop",
          "tagvalue": "autostop-test"
        }
        
    6. 「保存」ボタン押下
    7. 「テスト」ボタン押下
  4. 自動起動テスト
    1. イベントアクションをテスト・・・新しいイベントを作成
    2. イベント名・・・autostart-test
    3. イベント共有の設定・・・プライベート
    4. テンプレート - オプション・・・autostop-test。何でも良い
    5. イベント JSON
      1. {
          "type": "start",
          "tagvalue": "autostop-test"
        }
        
    6. 「保存」ボタン押下
    7. 「テスト」ボタン押下

保存したautostop-testautostart-testは繰り返し使えます。

  1. イベントアクションをテスト・・・保存されたイベントを編集
  2. イベント名・・・autostop-test or autostart-test
  3. 「テスト」ボタン押下

テストが終わったら、テストに使ったEC2のAutoRunタグを元に戻してください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1