EC2 Image Builderから生まれたAMIをSlackに通知してみた。
はじめに
皆様こんにちは、あかいけです。
突然ですが、EC2 Image Builder を使っていますでしょうか?
EC2 Image BuilderはAMIおよびコンテナイメージのビルドはもちろん、独自のカスタマイズやデプロイ、はたまたライフサイクルポリシーによる世代管理まで…。
自前で実装したら大変な処理を、パイプラインとして一元化して管理できるとても便利なサービスです。
また先日SSMパラメータストアとの統合がサポートされ、その便利さにますます磨きをかけています。
しかし私は気づいてしまいました、
日々生まれては消えていくAMIたちへの感謝ができていないなと…。
というわけで今回は、
EC2 Image Builderから生まれたAMIをSlackに通知してみました。
なおそもそも「EC2 Image Builderってなんやねん…。」という方は以下の記事をご参照ください。
構成
大まかな構成は以下の通りです。
EC2 Image Builderのインフラストラクチャ設定にて、
通知先のSNS トピックを指定して、その後Lambdaへ連携して加工したメッセージをSlack Webhook URL(Parameters Storeに格納)を使ってSlackへ通知しています。
事前準備
- Slack Webhook URL (Incoming Webhooks)
- Terraform実行環境
- Install Terraform | Terraform | HashiCorp Developer
- 個人的にはmiseでのインストールがおすすめ。
Getting Started | mise-en-place
作成方法
今回はTerraformで用意しました、以下の流れでデプロイできます。
リポジトリをcloneします。
git clone https://github.com/Lamaglama39/ami-birth-notify.git;
クレデンシャル情報を設定するため、terraform.tfvars
を作成します。
slack_webhook_url
は取得したURL、
slack_channel
は送信先のチャネル名に置き換えてください。
cat <<EOF >> ./terraform/terraform.tfvars
slack_webhook_url = "https://hooks.slack.com/services/XXX/XXX/XXX" # 取得したSlack Webhook URL
slack_channel = "#channel-name" # 通知先Slack Channel
app_name = "ami-slack-notification" # アプリ名
aws_region = "ap-northeast-1" # 作成リージョン
EOF
あとはapplyするだけです。
cd ./terraform;
terraform init;
terraform apply;
リソースの作成完了後、
outputに出力される以下のコマンドを実行するか、マネジメントコンソールからパイプラインを手動実行してください。
terraform output;
aws imagebuilder start-image-pipeline-execution --image-pipeline-arn ${aws_imagebuilder_image_pipeline.example.arn}
イメージの作成完了するとSlackに通知されます。
新しいAMIが生まれました、かわいいね。
ポイント
いくつかポイントとなる箇所を説明します。
パイプライン実行タイミング
aws_imagebuilder_image_recipe.parent_image
にて、AWS の提供するイメージを指定しています。
またバージョンの指定をx.x.x
とすることで、パイプライン実行時に最新バージョンのAMIが指定されるようになります。
resource "aws_imagebuilder_image_recipe" "example" {
name = "${var.app_name}-recipe"
version = "1.0.0"
parent_image = "arn:${data.aws_partition.current.partition}:imagebuilder:${data.aws_region.current.name}:aws:image/amazon-linux-2023-x86/x.x.x"
component {
component_arn = aws_imagebuilder_component.example.arn
}
block_device_mapping {
device_name = "/dev/xvda"
ebs {
volume_size = 8
volume_type = "gp3"
}
}
}
次にaws_imagebuilder_image_pipeline.pipeline_execution_start_condition
にて、
EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE
を指定します。
こうすることで、参照しているベースイメージ、およびコンポーネントが更新されている場合のみパイプラインが実行されるようになります。
resource "aws_imagebuilder_image_pipeline" "example" {
name = "${var.app_name}-pipeline"
image_recipe_arn = aws_imagebuilder_image_recipe.example.arn
infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.example.arn
schedule {
schedule_expression = "cron(* * * * ? *)"
pipeline_execution_start_condition = "EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE"
}
image_tests_configuration {
image_tests_enabled = false
}
status = "ENABLED"
}
メッセージの加工
メッセージの加工はLambdaで行っております。
EC2 Image BuilderからSNSトピックに通知されるメッセージ形式は以下の通りですので、
通知メッセージを変更したい場合はこちらをご参照の上カスタマイズしてください。
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');
const https = require('https');
const url = require('url');
exports.handler = async (event) => {
console.log('Event:', JSON.stringify(event, null, 2));
// SNSイベントをパース
let imageBuilderEvent = event;
if (event.Records && event.Records[0] && event.Records[0].Sns) {
try {
imageBuilderEvent = JSON.parse(event.Records[0].Sns.Message);
} catch (error) {
console.error('Error parsing SNS message:', error);
}
}
// パラメータストアからSlackのWebhook URLとチャンネルを取得
const ssmClient = new SSMClient();
const webhookUrlParam = await ssmClient.send(
new GetParameterCommand({
Name: process.env.SLACK_WEBHOOK_PARAM_NAME,
WithDecryption: true
})
);
const slackChannelParam = await ssmClient.send(
new GetParameterCommand({
Name: process.env.SLACK_CHANNEL_PARAM_NAME,
WithDecryption: true
})
);
const webhookUrl = webhookUrlParam.Parameter.Value;
const slackChannel = slackChannelParam.Parameter.Value;
// イベントからイメージの詳細を抽出
const imageArn = imageBuilderEvent.arn || 'N/A';
const imageState = imageBuilderEvent.state?.status || 'N/A';
const imageId = imageBuilderEvent.outputResources?.amis?.[0]?.image || 'N/A';
const imageName = imageBuilderEvent.name || 'N/A';
const recipeVersion = imageBuilderEvent.version || 'N/A';
const buildVersion = imageBuilderEvent.buildVersion || 'N/A';
const osVersion = imageBuilderEvent.osVersion || 'N/A';
const region = imageBuilderEvent.outputResources?.amis?.[0]?.region || 'N/A';
const recipeName = imageBuilderEvent.imageRecipe?.name || 'N/A';
const parentImage = imageBuilderEvent.imageRecipe?.parentImage || 'N/A';
// Slackメッセージを作成
const message = {
channel: slackChannel,
username: 'AWS Image Builder',
icon_emoji: ':aws:',
attachments: [{
color: '#36a64f',
title: '👶 New AMI is Born 👶',
fields: [
{
title: 'Image Name',
value: imageName,
short: true
},
{
title: 'Image Version',
value: `${recipeVersion}/${buildVersion}`,
short: true
},
{
title: 'AMI ID',
value: imageId,
short: true
},
{
title: 'Status',
value: imageState,
short: true
},
{
title: 'OS Version',
value: osVersion,
short: true
},
{
title: 'Region',
value: region,
short: true
},
{
title: 'Recipe',
value: recipeName,
short: true
},
{
title: 'Parent Image',
value: parentImage,
short: true
}
],
footer: 'AWS Image Builder',
ts: Math.floor(Date.now() / 1000)
}]
};
// Slackにメッセージを送信
try {
await sendSlackMessage(webhookUrl, message);
return { statusCode: 200, body: 'Notification sent to Slack' };
} catch (error) {
console.error('Error sending Slack message:', error);
throw error;
}
};
function sendSlackMessage(webhookUrl, message) {
return new Promise((resolve, reject) => {
const parsedUrl = url.parse(webhookUrl);
const options = {
hostname: parsedUrl.hostname,
path: parsedUrl.path,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
const req = https.request(options, (res) => {
let responseBody = '';
res.on('data', (chunk) => {
responseBody += chunk;
});
res.on('end', () => {
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(new Error(`Status Code: ${res.statusCode} ${responseBody}`));
} else {
resolve(responseBody);
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(JSON.stringify(message));
req.end();
});
}
さいごに
以上、EC2 Image Builderから生まれたAMIをSlackに通知する方法でした。
無事に生まれてきてくれたAMIへの感謝を忘れないようにしましょう。