はじめに
EC2を自動起動停止させるLambdaをたぶんNode.js v6かv8のころから使ってきました。
Node.js v14まではコードを修正せずに動いたのですが、v18に上げたらそのままでは動かなかったので、やったことを残しておきます。
Node.js v14まで
ソースコード
元々使っていたコードは下記です。
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タグの値がこれだったら自動起動させる
- TAG_TITLE・・・
- トリガー
- 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" }
-
- スケジュール・・・
- EventBridge・・・
使い方
自動起動停止させたい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を同じディレクトリに配置してください。
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;
}
};
{
"type": "module"
}
設定
設定はそのまま流用できます。
使い方
使い方も同じです。
テスト手順
Lambda関数の設定画面上、コードの右にある「テスト」タブで即時実行させられます。
※殆どの人には関係ない話ですが、zscaler経由だとテストが実行できません。
- テストに使うEC2のタグを
AutoRun:autostop-test
に書き換える - Lambda関数 > テスト
- 自動停止テスト
- イベントアクションをテスト・・・新しいイベントを作成
- イベント名・・・
autostop-test
- イベント共有の設定・・・プライベート
- テンプレート - オプション・・・何でも良い。一般>hello-worldとかで可
- イベント JSON
-
{ "type": "stop", "tagvalue": "autostop-test" }
-
- 「保存」ボタン押下
- 「テスト」ボタン押下
- 自動起動テスト
- イベントアクションをテスト・・・新しいイベントを作成
- イベント名・・・
autostart-test
- イベント共有の設定・・・プライベート
- テンプレート - オプション・・・
autostop-test
。何でも良い - イベント JSON
-
{ "type": "start", "tagvalue": "autostop-test" }
-
- 「保存」ボタン押下
- 「テスト」ボタン押下
保存したautostop-test
、autostart-test
は繰り返し使えます。
- イベントアクションをテスト・・・保存されたイベントを編集
- イベント名・・・
autostop-test
orautostart-test
- 「テスト」ボタン押下
テストが終わったら、テストに使ったEC2のAutoRunタグを元に戻してください。