初めまして。
SCSKの多田といいます。
今まではAWSでサーバレス環境の構築等を行うことが多かったのですが、今期よりEC2を使ったサービスを提供している部署に異動となり、最初の仕事としてEC2の運用周りを見直す機会に携わりました。
その中で、サービス提供中のEC2のバックアップとしてAMI作成を手動で作成しているのを人手を介さずに自動化できないかなーとお題をいただいたので、EC2周りの勉強がてら実施してみることにしました。
最初は「EC2のマネジメントコンソールのアクションボタンからイメージ作成を選択するのを自動化するだけの簡単なお仕事」と思っていたのですが、思いのほか、はまったり、悩んだりといったことがありましたので、皆さんに共有できればと思い記事にしてみました。
なお、簡単な(当初は)お仕事の概要としては以下のポイントとなります。
- 運用中のEC2からゴールデンイメージ(AMI)を作成
- ゴールデンイメージにはEC2のログやテンポラリのファイルなどを含めないようにしたい
- 完全自動化(上記のEC2内部のクリーンアップや作成したイメージの管理まで)
構成について
利用した主なAWSサービス
今回、利用した主なAWSのサービスは以下の2つとなります。
EC2 Image Builder
Systems Manager Automation
全体の構成について
いきなりですが、今回の機能の全体の構成図は以下の通りとなります。
図1 全体イメージ図
まず、このSystems Manager AutomationとEC2 Image Builderの2つを組み合わせた構成になった背景について説明します。
EC2 Image Builder自体はAMI(図1 ①)を起点にして最終的なAMI(図1 ⑨)を作成するサービスとなります。
そのため、稼働中のEC2から最終的なAMIを作成するためには、EC2 Image Builderだけでは実現できなく、Systems Manager Automationを使って起点となる一時的なAMI(図1 ①)を作成しています。
その後、EC2 Image Builderの設定、起動(図1 ②~⑦)などもSystems Manager Automationを使って実行しています。
そして最後に一時的に作成したAMIは不要なものになるため、最後に削除(図1 ⑧)しています。
AWS Systems Manager Automationの構成について
次にSystems Manager AutomationとEC2 Imager Builderの詳細について説明していきます。
まず、実際に検証で使用したSystems Manager Automationの構成は以下の通りとなります。
図2 Systems Manager Automationの構成図
Systems Manager Automation内のワークフローの詳細は以下の表の通りとなります。
表1 Systems Manager Automationのステップ一覧
項番 | アクション | Service | API | 概要 |
---|---|---|---|---|
1 | aws:createImage | – | – | EC2から一時的なAMIを作成 |
2 | aws:executeAwsApi | imagebuilder | CreateImageRecipe | EC2 Image Builderのレシピを作成 |
3 | aws:executeAwsApi | imagebuilder | UpdateImagePipeline | EC2 Image Builderのパイプラインを更新
(項番2で作成したレシピをパイプラインに組み込む) |
4 | aws:executeAwsApi | imagebuilder | StartImagePipelineExecution | EC2 Image Builderのパイプラインを起動 |
5 | aws:waitForAwsResourceProperty | – | – | AMIが作成されるまで待つ |
6 | aws:executeAwsApi | imagebuilder | TagResource | 作成されたAMIにタグを付与 |
7 | aws:executeAwsApi | imagebuilder | UpdateImagePipeline | EC2 Image Builderのパイプラインを更新
(項番3で組み込んだレシピをパイプラインから取り外す) |
8 | aws:executeAwsApi | imagebuilder | DeleteImageRecipe | EC2 Image Builderのレシピを削除
(項番7で取り外したレシピを削除する) |
9 | aws:deleteImage | – | – | 項番1で作成した一時的なAMIを削除 |
実際の定義は以下になります。なお、各パラメータ等はご要件に併せて変更していただくようお願いします。
schemaVersion: '0.3'
assumeRole: arn:aws:iam::XXXXXXXXXXXX:role/XXXXXXXXXX
mainSteps:
- name: CreateAMI
action: aws:createImage
nextStep: CreateImageRecipe
isEnd: false
inputs:
ImageName: XXXXXXXXXX
NoReboot: true
InstanceId: XXXXXXXXXX
- name: CreateImageRecipe
action: aws:executeAwsApi
nextStep: UpdateImagePipeline
isEnd: false
inputs:
Service: imagebuilder
Api: CreateImageRecipe
parentImage: '{{ CreateAMI.ImageId }}'
components:
- componentArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:component/XXXXXXXXXX/1.0.0/1
semanticVersion: 2.0.0
name: XXXXXXXXXX
outputs:
- Type: String
Name: ImageRecipeArn
Selector: $.imageRecipeArn
- name: UpdateImagePipeline
action: aws:executeAwsApi
nextStep: StartImagePipelineExecution
isEnd: false
inputs:
Service: imagebuilder
Api: UpdateImagePipeline
imageRecipeArn: '{{ CreateImageRecipe.ImageRecipeArn }}'
distributionConfigurationArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:distribution-configuration/XXXXXXXXXX
workflows:
- workflowArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:workflow/build/XXXXXXXXXX/1.0.0/1
executionRole: arn:aws:iam::XXXXXXXXXXXX:role/XXXXXXXXXX
imagePipelineArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:image-pipeline/XXXXXXXXXX
infrastructureConfigurationArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:infrastructure-configuration/XXXXXXXXXX
- name: StartImagePipelineExecution
action: aws:executeAwsApi
nextStep: WaitOnAWSResourceProperty
isEnd: false
inputs:
Service: imagebuilder
Api: StartImagePipelineExecution
imagePipelineArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:image-pipeline/XXXXXXXXXX
outputs:
- Type: String
Name: ImageBuildVersionArn
Selector: $.imageBuildVersionArn
- name: WaitOnAWSResourceProperty
action: aws:waitForAwsResourceProperty
timeoutSeconds: 3600
nextStep: TagResource
isEnd: false
inputs:
Service: imagebuilder
Api: GetImage
PropertySelector: $.image.state.status
DesiredValues:
- AVAILABLE
imageBuildVersionArn: '{{ StartImagePipelineExecution.ImageBuildVersionArn }}'
- name: TagResource
action: aws:executeAwsApi
nextStep: UpdateImagePipeline_default
isEnd: false
inputs:
Service: imagebuilder
Api: TagResource
tags:
AMIType: XXXXXXXXXX
resourceArn: '{{ StartImagePipelineExecution.ImageBuildVersionArn }}'
- name: UpdateImagePipeline_default
action: aws:executeAwsApi
nextStep: DeleteImageRecipe
isEnd: false
inputs:
Service: imagebuilder
Api: UpdateImagePipeline
imageRecipeArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:image-recipe/default/1.0.0
distributionConfigurationArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:distribution-configuration/XXXXXXXXXX
imagePipelineArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:image-pipeline/XXXXXXXXXX
infrastructureConfigurationArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:infrastructure-configuration/XXXXXXXXXX
- name: DeleteImageRecipe
action: aws:executeAwsApi
nextStep: DeleteAMI
isEnd: false
inputs:
Service: imagebuilder
Api: DeleteImageRecipe
imageRecipeArn: '{{ CreateImageRecipe.ImageRecipeArn }}'
- name: DeleteAMI
action: aws:deleteImage
isEnd: true
inputs:
ImageId: '{{ CreateAMI.ImageId }}'
Amazon EC2 Image Builder の構成について
Image Builder の構成のポイントとなる機能について説明します。
イメージパイプライン
最初にイメージパイプラインについて説明します。
「イメージパイプラインの作成」ボタンを押下して作成しますが、今回の目的におけるポイントは次の3つになります。
- 「ビルドスケジュール」の選択について
図3 EC2 Image Builder設定例-1
「ビルドスケジュール」については、今回はSystems ManagerのAutomationからEC2のImage Builderを実行することになるため、「手動」で設定します。
また、Systems ManagerのAutomationを定期的に自動で実行したい場合には、別途EventBridgeでスケジュール設定を行います。
- 「レシピの詳細」の選択について
図4 EC2 Image Builder設定例-2
Systems ManagerのAutomationの箇所でも触れましたが実際に使用されるイメージレシピは表1の項番2で作成された後、項番3でイメージパイプラインに設定、項番7でイメージパイプラインから取り外されます。
そのためここの「レシピの詳細」で設定されるレシピは項番8から項番2までの状態のイメージパイプラインに設定されているイメージレシピとなります。
なお、このイメージレシピは実行されることのないものになりますので、今回は「Amazon管理」で提供されている適当なコンポーネントを選んで作成したイメージレシピを選択するようにしています。
上記の図4では、事前に「Default」という名前のレシピを作成済みのため、それを選択しています。
- 「タイプ」で選択するワークフローの選択について
図5 EC2 Image Builder設定例-3
今回は図5のようにオリジナルのイメージワークフローの「CreateAMI-WorkFlow」を設定しています。
詳細については次の「イメージワークフロー」にて説明します。
イメージワークフロー
「タイプ」で選択するワークフローについて説明します。
簡単に言うと、EC2 Image BuilderでAMIを作成する際の手順が定義されたものになります。
参考までにAWSが提供しているbuild-imageのイメージワークフローの内容を以下に貼付します。
name: build-image
description: Workflow to build an AMI
schemaVersion: 1.0
steps:
- name: LaunchBuildInstance
action: LaunchInstance
onFailure: Abort
inputs:
waitFor: "ssmAgent"
- name: ApplyBuildComponents
action: ExecuteComponents
onFailure: Abort
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
- name: InventoryCollection
action: CollectImageMetadata
onFailure: Abort
if:
and:
- stringEquals: "AMI"
value: "$.imagebuilder.imageType"
- booleanEquals: true
value: "$.imagebuilder.collectImageMetadata"
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
- name: RunSanitizeScript
action: SanitizeInstance
onFailure: Abort
if:
and:
- stringEquals: "AMI"
value: "$.imagebuilder.imageType"
- not:
stringEquals: "Windows"
value: "$.imagebuilder.platform"
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
- name: RunSysPrepScript
action: RunSysPrep
onFailure: Abort
if:
and:
- stringEquals: "AMI"
value: "$.imagebuilder.imageType"
- stringEquals: "Windows"
value: "$.imagebuilder.platform"
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
- name: CreateOutputAMI
action: CreateImage
onFailure: Abort
if:
stringEquals: "AMI"
value: "$.imagebuilder.imageType"
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
- name: TerminateBuildInstance
action: TerminateInstance
onFailure: Continue
inputs:
instanceId.$: "$.stepOutputs.LaunchBuildInstance.instanceId"
outputs:
- name: "ImageId"
value: "$.stepOutputs.CreateOutputAMI.imageId"
内容をご覧いただくと分かりますが、上の方でも記載しているSystems ManagerのAutomationの定義のようなフローが記述された設定となっています。
ちなみにこの中で実際に行われている処理の内容は以下のリンクに記載があります。
特に「SanitizeInstance」の処理は、AWSが想定しているインスタンスのサニタイズ処理がされるため、作成するイメージとして問題がないかは確認をしたほうがよいです。
もし、イメージワークフローで実施してほしくない処理などがあれば、既存のものを参考にして「イメージワークフローを作成」ボタンから作成して、イメージパイプラインに組み込むことが可能です。
今回は「SanitaizeInstance」の処理の一部が不要なものであったので、オリジナルのイメージワークフロー「CreateAMI-WorkFlow」を作成しています。
コンポーネント
最後にコンポーネントについて説明します。
今回はインスタンスに対して独自の処理(ログファイルや一時作成のファイルの削除など)を行いたかったため、独自のコンポーネントを作成しています。
作成にあたっては以下のリンクを参考にしました。
なお、イメージレシピにはこのコンポーネントを組み込む形になります。(上記のSystems ManagerのAutomation定義の一部抜粋)
- name: CreateImageRecipe
action: aws:executeAwsApi
nextStep: UpdateImagePipeline
isEnd: false
inputs:
Service: imagebuilder
Api: CreateImageRecipe
parentImage: '{{ CreateAMI.ImageId }}'
components:
- componentArn: arn:aws:imagebuilder:ap-northeast-1:XXXXXXXXXXXX:component/XXXXX/1.0.0/1
semanticVersion: 2.0.0
name: XXXXXX
outputs:
- Type: String
Name: ImageRecipeArn
Selector: $.imageRecipeArn
以上で、設定に悩んだ、もしくははまった個所については触れることができました。
上記以外の機能(インフラストラクチャ設定、ディストリビューション設定、ライフサイクルポリシー)については、大きく悩む箇所はなかったため割愛させていただきます。
最後に
以上、ざっとですが今回のお題をクリアするための構成ができたことになります。
最後までお読みいただき、ありがとうございました。
最初の想定に比べると倍以上は複雑になってしまったのと、加えてSystems ManagerのAutomationでのパラメータ設定などはドキュメントから読み取れないことも多くトライ&エラーの連続でした。
今回の設定が同じようなことを試そうとしている方に少しでもお役に立てればと思います。
今回のようにSystems ManagerのAutomationを実行するたびに起動中のEC2からAMIを作成している場合には、AMI IDが毎回異なるため、イメージレシピを作成(もしくは更新)してあげる必要があります。
では、何故、毎回イメージレシピを新規作成にしているかというと、イメージレシピを更新する場合はイメージレシピのバージョンを変更しなければならなく、例えば1.0.0→1.0.1のように既存のものとは異なるバージョンにする必要がありますが、バージョン番号を実行するたびに動的に生成したくなかったというのが理由になります。
動的というのは例えばですがバージョン番号を既存のイメージレシピから取得してインクリメントした値を返すようなLambdaの作成が想定されますが、今回はなるべくコード作成などは行わずにしたかったため、バージョンは固定(2.0.0)として作成して、利用が終わったらイメージレシピを表1の項番 8で削除するようにしています。