📝
Auto Scalingライフサイクルフックを利用して、AMIを取得するLambda関数
Auto Scalingライフサイクルフックを利用して、AMIを取得する方法で、実装の全体像を紹介しましたが、こちらの記事ではLambda関数の中身を紹介します。
関数概要
- ランタイム
- Node.js 14.x
- タイムアウト
- 35秒
- メモリ
- 128MB
- トリガー
- EventBridge
- アクセス権限
AWSLambdaBasicExecutionRole
ec2:DescribeImages
e2:CreateImage
ec2:CreateTags
lambda:UpdateFunctionConfiguration
autoscaling:CompleteLifecycleAction
- 環境変数
- ASGNAME:
現在のAuto Scalingグループ名
- ASGNAME:
コード
index.js
- 現在のAuto Scalingグループ名と、イベントが発生したAuto Scalingグループ名を取得
- 起動イベントなら環境変数を更新する
- 終了イベントの場合、Auto Scalingグループ名を比較
- 現在のグループ名と同じグループ名のイベントならAMI取得
- 現在のグループ名と異なるグループ名の場合、リリースによるグループの置換のため、グループの削除を続行
- 各APIはサービスごとのクラスで呼び出している
index.js
const AWS = require('aws-sdk');
AWS.config.update({ region: 'ap-northeast-1' });
AWS.config.apiVersions = {
ec2: '2016-11-15',
lambda: '2015-03-31',
autoscaling: '2011-01-01',
};
const EC2API = require('./AWS_API/EC2API');
const ec2 = new EC2API(AWS);
const LambdaAPI = require('./AWS_API/LambdaAPI');
const lambda = new LambdaAPI(AWS);
const AutoScalingAPI = require('./AWS_API/AutoScalingAPI');
const autoscaling = new AutoScalingAPI(AWS);
exports.handler = async (event, context) => {
//ログ出力
console.log(JSON.stringify(event, null, 2));
//環境変数から現在のAuto Scalingグループ名を取得
const currentAsgName = process.env.AsgName;
//イベントが発生したAuto Scalingグループ名とインスタンスIDを取得
const eventAsgName = event.detail.AutoScalingGroupName;
const instanceId = event.detail.EC2InstanceId;
if (
event.detail.LifecycleTransition === 'autoscaling:EC2_INSTANCE_LAUNCHING'
) {
//リリースによる新規Auto Scalingグループ起動時に、Lambdaの環境変数を更新
try {
await lambda.updateFunctionConfiguration(
context.functionName,
eventAsgName
);
} catch (err) {
console.log(err);
}
} else {
if (currentAsgName === eventAsgName) {
//予期せぬスケールイン時に障害調査用のAMIを取得
const imageName = `Disability_Image_${new Date().getTime()}`;
try {
const imageId = await ec2.createImage(instanceId, imageName);
const snapshotId = await getSnapshotid(imageId);
if (snapshotId === undefined) {
console.log('スナップショットIDを取得できませんでした。');
return;
}
await ec2.createTags(imageId, snapshotId, imageName);
} catch (err) {
console.log(err);
}
} else {
//リリースによる古いAuto Scalingグループの削除続行
const result = await autoscaling.completeLifecycleAction(
eventAsgName,
event.detail.LifecycleActionToken,
instanceId,
event.detail.LifecycleHookName
);
console.log(result);
}
}
};
//スナップショットのidを取得するメソッド
//AMI作成直後はスナップショットのIDが生成されないので、30秒だけループ
async function getSnapshotid(imageId) {
for (let i = 0; i < 10; i++) {
await new Promise((resolve) => setTimeout(resolve, 3000));
try {
const ebs = await ec2.describeImages(imageId);
if ('SnapshotId' in ebs) {
return ebs.SnapshotId;
}
} catch (err) {
console.log('getSnapshotidでエラーが発生しました。');
throw err;
}
if (i === 9) {
console.log('30秒以内にスナップショットのidを取得できませんでした。');
return undefined;
}
}
}
EC2API.js
EC2関連のAPIを詰め込んだクラスです。
-
createImage
- AMIを作成
-
describeImages
- AMIの詳細を取得
-
createTags
- AMIとスナップショットにタグ付け
EC2API.js
module.exports = class EC2API {
constructor(AWS) {
this.ec2 = new AWS.EC2();
}
//AMIを作成するメソッド
async createImage(InstanceId, Name) {
//Windowsインスタンスの場合
const params = {
InstanceId /* required */,
Name /* required */,
BlockDeviceMappings: [
{
DeviceName: '/dev/sda1',
Ebs: {
DeleteOnTermination: true,
Encrypted: false,
//Iops: 'NUMBER_VALUE',
//KmsKeyId: 'STRING_VALUE',
//SnapshotId: 'STRING_VALUE',
//Throughput: 'NUMBER_VALUE',
VolumeSize: 30,
VolumeType: 'gp2',
},
//NoDevice: 'STRING_VALUE',
//VirtualName: 'STRING_VALUE'
},
/* more items */
],
Description: Name,
DryRun: false,
NoReboot: true,
};
try {
const result = await this.ec2.createImage(params).promise();
return result.ImageId;
} catch (err) {
console.log('createImageでエラーが発生しました。');
throw err;
}
}
//AMIのスナップショットidを取得するメソッド
async describeImages(imageId) {
const params = {
ImageIds: [imageId],
};
try {
const result = await this.ec2.describeImages(params).promise();
return result.Images[0].BlockDeviceMappings[0].Ebs;
} catch (err) {
console.log('describeImagesでエラーが発生しました。');
}
}
//AMIとスナップショットにタグ(名前)を付けるメソッド
async createTags(imageId, snapshotId, Value) {
const params = {
Resources: [imageId, snapshotId],
Tags: [
{
Key: 'Name',
Value,
},
{
Key: 'Name',
Value,
},
],
};
try {
await this.ec2.createTags(params).promise();
} catch (err) {
console.log('createTagsでエラーが発生しました。');
throw err;
}
}
};
LambdaAPI.js
LambdaのupdateFunctionConfiguration
を使用するためのクラス
-
updateFunctionConfiguration
- Lambdaの環境変数を更新
LambdaAPI.js
module.exports = class LambdaAPI {
constructor(AWS) {
this.lambda = new AWS.Lambda();
}
//Lambdaの環境変数を更新するメソッド
async updateFunctionConfiguration(FunctionName, newAsgName) {
const params = {
FunctionName,
Environment: {
Variables: {
AsgName: newAsgName,
/* '<EnvironmentVariableName>': ... */
},
},
};
try {
await this.lambda.updateFunctionConfiguration(params).promise();
} catch (err) {
console.log('updateFunctionConfigurationでエラーが発生しました。');
throw err;
}
}
};
AutoScalingAPI.js
Auto ScalingのcompleteLifecycleAction
を使用するためのクラス
-
completeLifecycleAction
- ライフサイクルフックの一時停止を解除
AutoScalingAPI.js
module.exports = class AutoScalingAPI {
constructor(AWS) {
this.autoscaling = new AWS.AutoScaling();
}
//Auto Scalingグループのライフサイクルアクションを完了させるメソッド
async completeLifecycleAction(
AutoScalingGroupName,
LifecycleActionToken,
InstanceId,
LifecycleHookName
) {
const params = {
AutoScalingGroupName,
LifecycleActionResult: 'CONTINUE',
LifecycleActionToken,
InstanceId,
LifecycleHookName,
};
try {
const result = await this.autoscaling
.completeLifecycleAction(params)
.promise();
return result;
} catch (err) {
console.log('completeLifecycleActionでエラーが発生しました。');
throw err;
}
}
};
その他
EventBridgeのイベントパターンは以下の通りです。
こちらはEventBridgeコンソールで簡単に設定可能です。
{
"source": ["aws.autoscaling"],
"detail-type": ["EC2 Instance-launch Lifecycle Action", "EC2 Instance-terminate Lifecycle Action"]
}
まとめ
今回は、Auto Scalingライフサイクルフックを利用して、AMIを取得するLambda関数を紹介しました。
参考になれば幸いです。
Discussion