こしあん
2025-01-18

EC2 Image BuilderでAMIの自動作成を試してみる


51{icon} {views}


TerraformとYAMLファイルで定義したイメージレシピを用い、Amazon Linux 2023にPyTorchをインストールしたカスタムAMIを自動的にビルドする手順を解説。IAMロールやセキュリティグループの設定を整えれば、定期実行や最新AMIへの追従なども効率的に行える。

はじめに

  • SCS勉強してたときに出てきた、AMIを自動でビルドしてくれる「EC2 Image Builder」を使ってみた
  • ビルドのスクリプトをYAMLで書いて、いい感じにビルドしてくれるもの
  • Dockerイメージのビルドにも使えるが今回はAMIのビルドを例に挙げる
  • この例では、Amazon Linux 2023のAMIにPyTorchをインストールしたカスタムAMIを作成してみる

YAMLの書き方(component.yaml)

YAMLで書かれたイメージレシピを定義する。ステップ以下にいろいろ書いていくスタイル

name: InstallPackages
description: Install additional packages
schemaVersion: 1.0

phases:
  - name: build                         # フェーズ名
    steps:
      - name: installPackages          # ステップ名(任意)
        action: ExecuteBash
        inputs:
          commands:
            - dnf update -y
            - dnf install -y python3-pip
            - pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

EC2 Image BuilderのTerraform

EC2 Image Builderを使うためのTerraformは以下の通り

  • ビルドするときに使うロール(この例ではEC2ImageBuilderRole)が必要
    • ポリシーとしてAWSImageBuilderFullAccess付与する必要がある(絞る気ならもう少し絞れるが、これがないとPermission Deniedでビルドが失敗する)。
    • AmazonSSMManagedInstanceCoreほぼ必須とされているようだ
  • イメージレシピの部分がImage Builderで何をビルドするかで、ベースのAMI(この例ではAmazon Linux 2023)を指定し、レシピを外部のyamlファイル(component.yaml)で定義している
  • セキュリティグループ定義したり、インスタンスタイプを指定したりあたかもEC2を立ち上げるような設定をしているが、これはイメージをビルドしたりテストしたりするときに使うEC2インスタンスの設定。ビルド時に使われるもので、終わったら勝手に終了される。
  • 定期実行も可能だが、今回は手動実行の例で示す
# IAM ロールの作成
resource "aws_iam_role" "image_builder_role" {
  name = "EC2ImageBuilderRole"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect = "Allow",
      Principal = {
        Service = "ec2.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "image_builder_role_attach" {
  role       = aws_iam_role.image_builder_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" # 必要に応じて他のポリシーを添付
}
# すでに作成済みの ImageBuilderFullAccess に追加アタッチする例
resource "aws_iam_role_policy_attachment" "image_builder_role_attach_ec2img_full" {
  role       = aws_iam_role.image_builder_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSImageBuilderFullAccess"
}

resource "aws_iam_instance_profile" "image_builder_profile" {
  name = "EC2ImageBuilderInstanceProfile"
  role = aws_iam_role.image_builder_role.name
}

# コンポーネントの作成(外部 YAML を参照)
resource "aws_imagebuilder_component" "example_component" {
  name        = "example-component"
  version     = "1.0.0"
  platform    = "Linux"
  description = "Install packages example"

  data = file("${path.module}/component.yaml")
}

# Amazon Linux 2023 AMI lookup
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["137112412989"]

  filter {
    name   = "name"
    values = ["al2023-ami-2023*-kernel-*-x86_64"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# Security Groups for ec2
resource "aws_security_group" "ec2_sg" {
  name        = "imagebuilder_ec2_sg"
  description = "A security group for EC2 Image Builder"
  vpc_id      = var.vpc_id

  egress {
    description      = "Allow all outbound traffic"
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

# イメージレシピの作成
resource "aws_imagebuilder_image_recipe" "example_recipe" {
  name    = "example-recipe"
  version = "1.0.0"
  # 最新の Amazon Linux 2023 の AMI ID
  parent_image = data.aws_ami.amazon_linux.id

  component {
    component_arn = aws_imagebuilder_component.example_component.arn
  }
}

# インフラ構成の作成
resource "aws_imagebuilder_infrastructure_configuration" "example_infra_config" {
  name                          = "example-infra-config"
  instance_profile_name         = aws_iam_instance_profile.image_builder_profile.name
  security_group_ids            = [aws_security_group.ec2_sg.id]
  subnet_id                     = var.subnet_id # 適切なサブネットIDを指定
  instance_types                = ["t3.medium"]
  terminate_instance_on_failure = true
}

# イメージパイプラインの作成(スケジュールなしで手動実行)
resource "aws_imagebuilder_image_pipeline" "example_pipeline" {
  name                             = "example-image-pipeline"
  image_recipe_arn                 = aws_imagebuilder_image_recipe.example_recipe.arn
  infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.example_infra_config.arn
  status                           = "ENABLED"

  # schedule ブロックを省略 → 自動スケジュールなし
  # schedule {
  #   schedule_expression = "cron(0 0 * * ? *)"
  # }  
}

ビルドしてみる

EC2 Image Builderの「イメージパイプライン」から、「アクション」→「パイプラインを実行」

裏でEC2が立ち上がってビルドが始まる

イメージを見ると途中経過が表示される。まあまあ時間がかかる(15分ぐらい)。特にエラーなければ全部完了済みになっているはず。ロールにポリシーが付与されていないとハマるとここが失敗することがある。

使ってみる

作ったAMIを使いたいときは、EC2の起動画面から「自分のAMI」で表示される。

Session Managerなどで適当にSSHして、Pythonに入りPyTorchのバージョンを表示してみると、最新のがインストールされている。

sh-5.2$ python3
Python 3.9.20 (main, Dec 11 2024, 00:00:00)
[GCC 11.4.1 20230605 (Red Hat 11.4.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__version__
'2.5.1+cpu'

削除時

  • terraform destroyしても、手動実行したジョブによって作られたイメージは自動削除されない。別途手動で削除する必要がある
  • 作ったAMIを消すときは、ロールライフサイクル移行するのに必要な権限を持ったロール(ポリシー:EC2ImageBuilderLifecycleExecutionPolicy)が必要で、これは最初に作ったフルアクセスとは別のロールが必要
  • イメージを削除しようとすると、自動的に作成されたサービスロール(AWSServiceRoleForImageBuilder)があるが、これだと権限不足で消せない
  • 削除用のロールを作成するときは、信頼関係を「EC2 Image Builder」にして、EC2ImageBuilderLifecycleExecutionPolicyをアタッチすればOK。ここが若干面倒くさい

所感

  • まあなるほどねという感じ
  • GPUのAMIを作るときは結構便利そう
  • AMI作るときは便利そうだけど、Terraformでこう作ってしまうと最新といつつベースのAMIのIDが固定されてしまうので、CI/CDとの棲み分けが詰める必要あるかなと思った
  • ただAMIのセキュリティ対策で、どんどん最新のを使いましょうというのには結構使えそう。実際は定期実行でやるのだと思われる


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です