はじめに
こんにちは、会員システムグループの上原です。
先日、社内ISUCONを開催し、参加者から好評の声をいただくことができました。
当日どんな感じだったのか気になる方は以下のブログ記事をご覧ください。https://engineering.nifty.co.jp/blog/21057
本イベントでは参加者にハンズオンで使用するサーバーを配布しました。参加者の人数とクォータ制限の観点から、数百台のサーバーをマルチアカウント・マルチリージョンで展開する必要があったのですが、手動でやるのは面倒だったので、可能な限り自動化してみました。
目標
- 数百台の同じ設定を入れ込んだサーバーを複数のAWSアカウント・複数のリージョンで簡単に立ち上げたい
従来の方法
同じ設定のサーバーを大量に立ち上げるためにまずはAMIを作成し、そのAMIを元にサーバーを起動する方法が思いつきます。
具体的には以下の手順になります。
- UbuntuやAmazon LinuxなどのOSが入っているAMIからEC2インスタンスを立ち上げる
- sshやセッションマネージャなどでサーバにログインし、EC2インスタンスに必要なソフトウェア・設定を入れ込む
- EC2インスタンスをAMI化する
- 作成したカスタムAMIを使って複数アカウント・複数リージョンにサーバーを立てる
従来の方法の問題点とその解決策
従来の方法の問題は、AMIを作成するためだけにEC2インスタンスを起動しコマンドを打ちこみイメージを作成する必要があるところです。
ですが、手作業だと抜け漏れやミスが発生しやすく、何しろ面倒です。
そこで、AMIを自動的に作成する方法はないかどうか探してみたところ見つけたのが、Amazon EC2 Image Builderというサービスです!
Amazon EC2 Image Builderとは?
Amazon EC2 Image BuilderとはAWSのフルマネージドサービスで、AMIを自動で生成・検証・配布してくれるサービスです。
手順をYAMLファイルに書いておけばその手順をもとにAMIを作成・検証し、AMIを他リージョン・他アカウントにも共有・コピーして使えるようにしてくれます。
より詳しく知りたい方は以下のBlack Beltのpdfがわかりやすいので参考にしてください。
https://pages.awscloud.com/rs/112-TZM-766/images/20200825_BlackBelt_EC2imagebuilder.pdf
ハンズオン
- 今回はお試しとしてslコマンドを入れたサーバーを5台同時に立ててみます
- 使用するOSはUbuntu 22.04
- 公式のAMI(ami-0d52744d6551d851e)を使用する
- AMIは自アカウントのap-northeast-1とus-east-1に展開します
- ちなみに、今回のterraformのコードをいじると別のアカウントにも展開できるようにすることもできます
- 作成したAMIを使ってEC2インスタンスをap-northeastに5台起動します
- 使用するOSはUbuntu 22.04
- 今回使うコードはこちらのレポジトリに配置しています。
Amazon EC2 Image Builderでパイプラインを作成する
ここではAWS EC2 Image Builderをterraformを使って立ててみます
- レポジトリをcloneします
1 |
git clone https://github.com/Penpen7/ec2-image-builder-test.git |
- terraform実行に必要なS3バケットや認証情報を入れてください
1 2 3 |
cd ec2-image-builder-test vim ec2/providers.tf vim image-builder/providers.tf |
- terraform initで初期化、terraform applyでAWS環境に反映します
1 2 3 |
cd image-builder terraform init terraform apply |
- 実行が終わればEC2 Image Builderのパイプラインが作られた状態になります
コンソールに入って設定を見てみる
- AWS コンソールにログインし、EC2 Image Builderでイメージパイプラインを表示します
- 以下のように作成したパイプラインが表示されるはずです
- パイプラインの中を見ると、パイプラインで出力したイメージ(AMI)・作成手順・配布設定・トリガーの設定を見ることができます
- レシピはどのようにAMIを構築するか指定します
- ベースとなるイメージをUbuntuに指定し、slコマンドをインストールする手順をコンポーネントに記述します
- ディストリビューション設定をクリックし、image-builder-testの中身を見ると以下の通りになっています
- ap-northeast-1とus-east-1に作成したAMIを展開するように設定されています
パイプラインを実行し、AMIを作成する
- 手動でパイプラインを実行します。イメージパイプラインからimage-builder-testにチェックを入れてパイプラインを実行するをクリックします
- パイプラインの出力イメージに作成中のイメージが追加されます
- 出力イメージをクリックすると作成状況がわかります
- 作成中はEC2 Image BuilderがEC2を起動して作業をしています
- 完了になればAMIがap-northeast-1とus-east-1に作成されているはずです
EC2を起動する
- terraformコードで今回作成したAMIを参照してEC2を5台起動できるようにしているので、ec2ディレクトリに移動しterraform applyで反映させます
1 2 3 |
cd ../ec2 terraform init terraform apply |
- EC2コンソールでサーバーが5台起動していることがわかります
- セッションマネージャでログインするとslコマンドがインストールされてることがわかります
- このようにAMIの作成手順や設定を記述しパイプラインを実行することで、AMIの作成・配布を自動化できます
- 設定を変更し、もう一度パイプラインを実行すればAMIを更新してくれます
お片付け
- terraform destroyでEC2 Image BuilderとEC2を破棄します
1 2 3 4 5 |
cd ../ec2 terraform destroy cd ../image-builder terraform destroy |
- コンソール画面でバージニア北部と東京リージョンに作成されたAMIの登録解除を行い、スナップショットを削除します
EC2 Image Builderのterraformのコード説明
- もし気になる方向けにterraformのコードの中身を簡単に説明します
Image Pipeline
- AMIを生成・配布するための自動実行されるパイプラインです
- CodePipelineのPipelineと同じやつです
- 1つのパイプラインから1つのAMIができます
- terraformの設定の中身はレシピとインフラ設定、配布設定のarnを指定しているだけです
- その他スケジュールで定期的にパイプラインを実行することもできます
1 2 3 4 5 6 7 8 9 10 |
resource "aws_imagebuilder_image_pipeline" "this" { name = var.name # AMIを作成するためのレシピ image_recipe_arn = aws_imagebuilder_image_recipe.this.arn # AMIを作成するためのインフラ構成 infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.this.arn # AMIを作成した後の配布設定 distribution_configuration_arn = aws_imagebuilder_distribution_configuration.this.arn } |
Recipes
- レシピはAMIをどのように作っていくか記述していきます
- ここでslをインストールするように指示する自作のコンポーネントを指定します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
resource "aws_imagebuilder_image_recipe" "this" { name = var.name # ベースとなるAMI parent_image = var.parent_image working_directory = "/tmp" version = "1.0.0" block_device_mapping { device_name = "/dev/sda1" no_device = false ebs { delete_on_termination = true encrypted = false volume_size = 16 volume_type = "gp2" } } # slをインストールするよう指示するコンポーネントのarnを指定 component { component_arn = aws_imagebuilder_component.this.arn } # SSMを配布するAMIから削除するかどうか # defaultだとtrueのはず systems_manager_agent { uninstall_after_build = false } } |
Components
- コンポーネントはAMIを作成するためにどのようなコマンドを実行するかを記述します
1 2 3 4 5 6 7 8 9 |
resource "aws_imagebuilder_component" "this" { name = var.name platform = "Linux" version = "1.0.0" data = file( var.component_build_path ) } |
- AWSが公式に出しているコンポーネントもあります
- 他のレシピからも参照できる再利用可能なコンポーネントです
- 中身はYAMLで記述します
- ビルドフェーズでaptを使ってslをインストールしますテストフェーズで正常動作するか検証することができ、今回はwhichを使ってslコマンドが存在するか検証します
- yamlに記述するnameにスペースがあるとエラーになるので注意してください
- 引数を受け取って処理させることも可能です(今回は引数を受け取りません)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
name: install_sl description: install sl schemaVersion: 1.0 phases: - name: build steps: - name: install_sl action: ExecuteBash inputs: commands: - sudo apt update - sudo apt install sl - name: test steps: - name: run_sl action: ExecuteBash inputs: commands: - which sl |
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/imagebuilder_component
infrastructure Configuration
- Image BuilderがAMIを作成するために立ち上げるEC2の設定を記述します
- Image Builderが起動するEC2には以下のポリシーが必要です
- arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- SSM agentとSSMが疎通できる環境でないとImage Builderは使えないです
- セキュリティグループでインターネットへの外向きのアクセスを全開放にして、サブネットをパブリックに、起動時にパブリックIPが自動的につくように設定すると簡単に疎通できると思います
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
resource "aws_imagebuilder_infrastructure_configuration" "this" { name = var.name # AMIを作成するためのEC2につけるIAMロール instance_profile_name = aws_iam_instance_profile.image_builder.name # AMIを作成するためのEC2のインスタンスタイプ instance_types = [var.instance_type] # AMIを作成するためのEC2につけるセキュリティグループ security_group_ids = [aws_security_group.this.id] # AMIを作成するためのEC2につけるサブネット subnet_id = aws_subnet.public.id # 異常終了時にインスタンスを削除するかどうか # パイプラインが失敗しデバッグしたい時はfalseにしてインスタンスを残すようにした方が良い terminate_instance_on_failure = false lifecycle { ignore_changes = [instance_metadata_options, logging] } } |
Distribution Configuration
- 作成したAMIをどのアカウント・どのリージョンに展開するか記述します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
resource "aws_imagebuilder_distribution_configuration" "sample" { name = var.name dynamic "distribution" { for_each = var.regions content { ami_distribution_configuration { name = "${var.name}{{imagebuilder:buildDate}}" ami_tags = { Name = "${var.name}" } # AMIのコピー先 target_account_ids = [ data.aws_caller_identity.current.account_id, ] # AMIの共有先(コピーせず、自アカウントのAMIを他アカウントから読めるようにする) launch_permission { user_ids = var.ami_share_ids } } # リージョン region = distribution.value } } } data "aws_caller_identity" "current" {} |
- 1リージョンごとに設定を入れます
- terraformだとdynamicという構文を使うとfor文で一気に設定を入れることができます
- ami_tagsでamiのtagを指定できます
- target_account_idにAMIをコピーして配置したいIDを指定します
- 他のAWSアカウントでも可能ですが、ここで指定したアカウントにクロスアカウントアクセス用のロールを作成する必要があります
- 逆にコピーせず共有したい時にはlaunch_permissionでIDを指定しましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
resource "aws_iam_role" "ec2_image_builder_distribution_cross_account_role" { name = "EC2ImageBuilderDistributionCrossAccountRole" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Sid = "" Principal = { AWS = "arn:aws:iam::${var.origin_aws_id}:root" } }, ] }) managed_policy_arns = [ "arn:aws:iam::aws:policy/Ec2ImageBuilderCrossAccountDistributionAccess", ] } |
https://docs.aws.amazon.com/imagebuilder/latest/userguide/cross-account-dist.html
最後に
今回は社内ISUCONで参加者にサーバーを配布するために使用したAmazon EC2 Image Builderをご紹介しました。
このサービスを使うと、AMIの作成を自動化することができ非常に便利です。
もしよろしければ使ってみてください。