
EC2 Image Builder で ASG のインスタンスをビルドの土台に AMI を作ってみた
はじめに
2026 年 10 月 19 日、EC2 Image Builder のアップデートがあり。Auto Scaling グループ(ASG)で稼働中の 1 台のインスタンスをビルドの土台にし、インスタンスへ加えたカスタマイズとレシピで指定した変更を反映した AMI を作成できるようになりました。
検証の構成
初期 AMI は、AL2023(2023.12.20260930)の EC2 にマーカーファイル image1.png を置いて作りました。この AMI を起動テンプレートに指定した ASG(min=1、max=2、desired=1)の 1 台目には、image2.png を置きました。マーカーファイルの有無で、AMI の元になったディスクを見分けます。
ASG は、起動テンプレートを $Default で参照して作りました。起動テンプレートのインスタンスプロファイルには、AmazonSSMManagedInstanceCore と EC2InstanceProfileForImageBuilder をアタッチしたロールを使っています。
aws ec2 create-launch-template --launch-template-name <launch-template-name> --version-description "v1 template-ami" --launch-template-data file://lt-v1-data.json
aws autoscaling create-auto-scaling-group --auto-scaling-group-name <asg-name> --launch-template 'LaunchTemplateName=<launch-template-name>,Version=$Default' --min-size 1 --max-size 2 --desired-capacity 1 --vpc-zone-identifier <subnet-id> --health-check-type EC2 --health-check-grace-period 60
{
"ImageId": "<initial-ami-id>",
"InstanceType": "t3.micro",
"IamInstanceProfile": {"Name": "<instance-profile-name>"},
"SecurityGroupIds": ["<security-group-id>"],
"MetadataOptions": {"HttpTokens": "required"}
}
親インスタンスの条件は、InService であること、SSM Agent がオンラインであること、プラットフォームとアーキテクチャがレシピと一致していることです。親インスタンスは条件を満たすインスタンスの中からランダムに選ばれ、指定はできません。
ビルド前にそのインスタンスへタグが付き、ASG からデタッチされます。親インスタンスがビルドインスタンスになり、ASG の起動テンプレートで指定した設定がビルドに使われます。
実行ロールに必要な権限は次のとおりです。
- autoscaling:DescribeAutoScalingGroups
- autoscaling:DetachInstances
- ec2:CreateTags
- ec2:DeleteTags
実行ロールは、IAM のロールテンプレートから作りました。
aws iam acquire-role --template-arn arn:aws:iam::aws:role-template/imagebuilder.amazonaws.com/EC2ImageBuilderParentInstanceASGRoleTemplate:3 --replacement-values 'AutoScalingGroupName={Values=[<asg-name>]},RoleNameSuffix={Values=[<asg-name>]}'
親インスタンスの取得元になる ASG は、インフラ設定の parentInstance で指定します。インスタンスプロファイルは、インフラ設定と起動テンプレートで同じものを指定しました。
{
"name": "<infra-config-name>",
"instanceProfileName": "<instance-profile-name>",
"terminateInstanceOnFailure": true,
"parentInstance": {
"autoScalingGroup": {
"name": "<asg-name>",
"capacityWaitTimeoutSeconds": 600
}
},
"resourceTags": {"Project": "ib-asg-verify-1020"},
"tags": {"Project": "ib-asg-verify-1020"}
}
aws imagebuilder create-infrastructure-configuration --cli-input-json file://infra-config.json
配布設定には、起動テンプレートの新バージョンを作る launchTemplateConfigurations を指定しました。
{
"name": "<distribution-config-name>",
"distributions": [
{
"region": "ap-northeast-1",
"amiDistributionConfiguration": {
"name": "<ami-name>-{{ imagebuilder:buildDate }}",
"amiTags": {"Project": "ib-asg-verify-1020"}
},
"launchTemplateConfigurations": [
{"launchTemplateId": "lt-0aaaaaaaaaaaaaaa1", "setDefaultVersion": true}
]
}
],
"tags": {"Project": "ib-asg-verify-1020"}
}
aws imagebuilder create-distribution-configuration --cli-input-json file://distribution-config.json
レシピはコンポーネントなしで作り、パイプラインの作成時に実行ロールを --execution-role で渡しました。
aws imagebuilder create-image-recipe --name <recipe-name> --semantic-version 1.0.0 --parent-image <al2023-ami-id>
aws imagebuilder create-image-pipeline --name <pipeline-name> --image-recipe-arn <recipe-arn> --infrastructure-configuration-arn <infra-config-arn> --distribution-configuration-arn <distribution-config-arn> --execution-role <execution-role-arn> --image-tests-configuration imageTestsEnabled=false
aws imagebuilder start-image-pipeline-execution --image-pipeline-arn <pipeline-arn>
ASG に何が起きるか
親インスタンスは ASG からデタッチされ、約 10 秒後に desired capacity を保つ補充インスタンスの起動が始まりました。親インスタンスの Name はビルド用の値に上書きされました。
インフラ設定の terminateInstanceOnCompletion を true にしたビルドでは、親インスタンスはビルド後に終了しました。false にしたビルドでは、ASG の外で停止状態のまま残りました。
AMI に何が入るか
マーカーファイル
ASG の 1 台目に置いた image2.png が、出力 AMI と補充分のどちらに入るかを調べました。更新後インスタンスは、後述の instance refresh で出力 AMI から起動したものです。
| インスタンス | 起動元の AMI | image1.png | image2.png |
|---|---|---|---|
| 親インスタンス(ASG の 1 台目) | 初期 AMI | あり | あり |
| 補充インスタンス | 初期 AMI | あり | なし |
| 更新後インスタンス | Image Builder の出力 AMI | あり | あり |
クリーンアップ
クリーンアップでは、cloud-init の記録、SSH ホストキー、ログなどを削除します。元インスタンス固有の情報を出力 AMI に残したくない場合に実行します。
コンポーネントなしのレシピと、マーカーファイルを書く ExecuteBash コンポーネントを 1 つ入れたレシピで、親インスタンスのビルドを比べました。表の最右列は、ビルドで作った AMI から起動したインスタンスの /var/lib/cloud/instances にあるインスタンス ID の数です。
| コンポーネント | RunSanitizeScript の出力の先頭 | cloud-init の記録にあるインスタンス ID の数 |
|---|---|---|
| なし | Skipping cleanup | 3 |
| あり | Cleaning up cloud init files | 1 |
コンポーネントなしのレシピでは、RunSanitizeScript は Skipping cleanup を出力して COMPLETED で終わりました。作った AMI から起動したインスタンスの cloud-init の記録は、初期 AMI の元にした EC2、親インスタンス、自分自身のものでした。machine-id は初期 AMI の元にした EC2 と同じ値でした。
ExecuteBash コンポーネントを入れたレシピでは、クリーンアップが実行されました。作った AMI から起動したインスタンスでは、machine-id は新しい値になり、cloud-init の記録は自分自身のものだけでした。
起動テンプレートと ASG への反映
配布設定に launchTemplateConfigurations を指定すると、元の設定を引き継いで AMI ID だけを新しくした起動テンプレートの新バージョンが作られます。setDefaultVersion は省略時に true となり、新バージョンが既定になります。
ビルド後の起動テンプレートには v2 が作られ、既定になっていました。
この ASG は起動テンプレートを $Default で参照しています。補充インスタンスが起動したのはデタッチの直後で、v2 が作られる前だったので、その時点の既定である v1(初期 AMI)から起動しました。
ASG が $Default を参照していても、起動済みのインスタンスは入れ替わりませんでした。v2 から起動したインスタンスへの入れ替えには instance refresh を実行しました。
aws autoscaling start-instance-refresh --auto-scaling-group-name <asg-name> --preferences MinHealthyPercentage=100,MaxHealthyPercentage=110,InstanceWarmup=60
instance refresh 後、更新後インスタンスは v2 から起動しました。
まとめ
ASG で稼働中のインスタンスを土台に、レシピで指定した変更を反映した AMI を作成し、ASG のインスタンスを、新しい AMI から起動したインスタンスに入れ替えられることを確認しました。
稼働中のインスタンスで済ませた設定を AMI に含められるため、初期セットアップを省略できます。一方、コンポーネントなしのレシピではクリーンアップが実行されず、元のインスタンス固有の情報が AMI に残りました。不要なログや古いデータを AMI に持ち込まないよう、クリーンアップが実行されることを確認してください。
今回は Amazon Linux で試しました。Windows の ASG でも、Windows Update を含むレシピと、sysprep によるクリーンアップを組み合わせたメンテナンスの自動化などに使えそうです。
AMI を更新して入れ替える前提でメンテナンスしたい場合に、今回のアップデートをお試しください。








