EC2 Image Builder のイメージ更新毎に古いイメージとEBSスナップショットを削除してみた

EC2 Image Builder は、パイプラインを実行する度にイメージを自動で作成してくれます。しかし、古いイメージを削除する機能はありません。今回は、SNSとLambdaを使用して、イメージ更新毎に古いイメージとEBSスナップショットを削除してみました。
2023.05.25

古いイメージとEBSスナップショットを削除したい。

こんにちは!AWS事業本部のおつまみです。

みなさん、EC2 Image Builderで作成したイメージが残り続けて困った経験はありますか?私はあります。
EC2 Image Builderを検証で動かし続け、気づいたら20個以上のイメージを生み出していました。。

EC2 Image Builder では、パイプラインを実行する度にイメージを自動で作成してくれます。
ただし、EC2 Image Builder には古いイメージを削除する機能はありません。
そのため、古いイメージが残り続けることで、AMIに関連付いているEBSスナップショットの料金が発生してしまいます。

2023.11のアップデートで古いイメージを削除できるようになりました!今後はこちらの機能をお使いください。

そこで今回はEC2 Image Builder のイメージ(AMI)更新毎に古いイメージとEBSスナップショットを削除してみたいと思います。

古いイメージが削除されてしまうため、EC2 Auto Scallingの起動テンプレートにイメージを使用している場合は、本実装を適用しないでください。

構成図

今回構築する構成です。

以下の流れで処理を実行します。

  1. EC2 Image Builderパイプライン で AMI作成時に SNSトピックを通知するように設定
  2. このSNSトピック通知をトリガーに Lambda関数を実行
  3. このLambda関数でEC2 Image Builderで作成した最新のイメージ以外の古いイメージとEBSスナップショットを削除

なお、今回試した構成はこちらのブログからヒントを得ました。ありがとうございます。

やってみた

まずはEC2 Image Builder以外の部分を構築します。
今回はLambdaとSNSトピックの作成にAWS SAMを使用しました。

AWS SAM初心者の方は、こちらのブログをご確認ください。

SAMで構築(Lambda+SNSトピック)

新規プロジェクト作成

今回はAWS SAMをインストールするのが手間だったので、CloudShellから実行します。
CloudShell起動後、sam initコマンドを実行し、SAMの実行環境を作成します。(今回ランタイムはpython3.7を使用しました。)

sam init --runtime python3.7 --name delete-old-image-and-ebs-every-imagebuilder-run

以下の内容が対話方式で聞かれるため順に答えていきます。

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location

1 - AWS Quick Start Templatesを選択。

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Multi-step workflow
        3 - Serverless API
        4 - Scheduled task
        5 - Standalone function
        6 - Data processing
        7 - Infrastructure event management
        8 - Lambda EFS example
        9 - Machine Learning

1 - Hello World Exampleを選択。

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]:

Nを選択。

Would you like to enable monitoring using CloudWatch Application Insights?  [y/N]:

Nを選択。

ここまででSAMの実行環境が作成されます。

次に以下のファイルをCloudShellにアップロードします。

  • template.yml
    • SNSトピックの作成
    • Lambdaのランタイム設定、トリガー、アクセス権限など
  • app.py
    • Lambdaのコード

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "delete old image and ebs every imagebuilder run"
Resources:
  # SNS topic
  SnsTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: sns-topic-for-imagebuilder
  # Lambda function
  Function:
    Type: AWS::Serverless::Function
    Properties:
      Description: "delete old image and ebs every imagebuilder run"
      CodeUri: scripts/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        EventBridgeRule:
          Type: SNS
          Properties:
            Topic: !Ref SnsTopic
      Policies:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ec2:DescribeImages
                - ec2:DeregisterImage
                - ec2:DeleteSnapshot
              Resource: '*'

ポイントは、Lambdaの実行に必要なPoliciesの設定です。

  • AWSLambdaBasicExecutionRole (AWS管理ポリシー)
  • 以下の操作権限を許可(インラインポリシー)
    • AMIイメージのリストを取得 (ec2:DescribeImages)
    • AMIイメージを削除 (ec2:DeregisterImage)
    • EBS Snapshotを削除 (ec2:DeleteSnapshot)

app.py

import boto3

# 設定
REGION = 'ap-northeast-1'
OWNER_ID = '123456789012' # 自分のAWSアカウントIDを入力

# AWS SDK (boto3) クライアントの作成
ec2 = boto3.client('ec2', region_name=REGION)

# Image BuilderのAMIイメージのリストを取得
response = ec2.describe_images(
    Owners=[OWNER_ID],
    Filters=[{'Name': 'tag:Name', 'Values': ['my-japanese-windows-image']}]
)  # AMIにタグ付けしたNameをValuesに入力

# 最新イメージ以外を削除
sorted_images = sorted(response['Images'], key=lambda x: x['CreationDate'], reverse=True)
latest_image = sorted_images.pop(0)

print(f"最新イメージ: {latest_image['Name']} (Image ID: {latest_image['ImageId']}, 作成日: {latest_image['CreationDate']})")

for image in sorted_images:
    # AMIイメージを削除
    print(f"削除: {image['Name']} (Image ID: {image['ImageId']}, 作成日: {image['CreationDate']})")
    ec2.deregister_image(ImageId=image['ImageId'])

    # EBSスナップショットを削除
    for bdm in image['BlockDeviceMappings']:
        if 'Ebs' in bdm:
            snapshot_id = bdm['Ebs']['SnapshotId']
            print(f"削除: スナップショット (Snapshot ID: {snapshot_id})")
            ec2.delete_snapshot(SnapshotId=snapshot_id)

EC2 Image Builderで作成されたイメージをタグを条件に取得、作成日順にソートし、最新イメージ以外のイメージとEBSスナップショットを削除しています。

ハイライトがかかっている部分は、自分のAWS環境に合わせて変更してください。

  • OWNER_ID = '123456789012' # 自分のAWSアカウントIDを入力
  • Filters=[{'Name': 'tag:Name', 'Values': ['my-japanese-windows-image']}] # AMIにタグ付けしたNameをValuesに入力

※ EC2 Image Builderで作成されたイメージへのタグ付けの設定方法は記事後半で説明しています。

アップロードしたファイルを下記の場所に格納します。

delete-old-image-and-ebs-every-imagebuilder-run
∟ template.yml
∟ scripts
     ∟ app.py

ビルド・デプロイ

delete-old-image-and-ebs-every-imagebuilder-run配下に移動し、以下のコマンドを実行します。
sam deploy --guidedで、SAM CLI によるプロンプトを用いたガイドを有効になるため、ガイドに従ってYES選択でデプロイできます。

sam build
sam deploy --guided

ここでCloudFormationが実行されます。
CloudShellにSuccessfully created/updated stack - in ap-northeast-1が表示されることを確認します。

リソースの確認

SAMで作成されたリソースを確認します。

Lambdaのコンソールに移動し、作成されたLambda関数を選択します。

Lambdaのトリガー

SNSトピックがトリガーとして、紐づいています。

実行ロール

紐づいている実行ロールを選択します。

許可ポリシーが正しいことを確認します。

次に、EC2 Image Builderの設定を行います。

EC2 Image Builderの設定

パイプラインやイメージレシピの詳細説明は割愛します。
EC2 Image Builderの構築から行いたい方は、こちらのブログをご確認ください。

作成したパイプラインに、以下の設定を追加します。

1.インフラストラクチャ設定

SAMで作成したSNSを紐付けます。
これによりAMI作成時にSNSトピックを通知するよう設定することができます。

2.ディストリビューション設定

AMIタグで任意のNameを付けます。
今回はmy-japanese-windows-imageにしました。
このタグにより、Lambdaの処理でAMIイメージのリストを取得することができます。
任意のNameをつけた場合は、Lambdaのコード修正も併せて行なってください。

いざ検証

1回目

作成したパイプラインを実行して、イメージができあがるのを待ちます。
イメージが使用可能になっていることを確認しました。

1回目であるため、イメージは削除されず、残っています。

2回目以降

再度パイプラインを実行して、イメージができあがるのを待ちます。

2回目のイメージのみ残っており、1回目のイメージは削除されていました!

EBSスナップショットも1つだけ残っており、削除されていそうです!

最後に

今回はEC2 Image Builderのイメージ(AMI)更新毎に古いイメージとEBSスナップショットを削除する方法をご紹介しました。

最新のイメージとEBSスナップショットだけが残るようになったので、コストだけでなく、管理面的にもニッコリです。

最後までお読みいただきありがとうございました!
どなたかのお役に立てれば幸いです。

以上、おつまみ(@AWS11077)でした!

備忘

最初は要件を叶えるために、弊社くらにゃん(ChatGPT)に構成を考えてもらいました。
すると、EventBridgeが使えると教えてもらいました。

教えてもらった通りに実装しようと思いました。
しかし、EventBridgeのイベントソースでは、EC2 Image Builderはサポートされていませんでした。

どうやらEC2 Image Builderをターゲットに指定することはできますが、2023/5時点ではイベントソースとしては使用できないようです。
再度くらにゃんに聞いたところ、やはりImageBuilderはサポートされていないようでした。

くらにゃん(ChatGPT)の言ったことをなんでも鵜呑みにせず、自分の頭で考えることも重要だと感じました。