Amazon EC2 Image Builder を使い倒す

初めまして。
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 }}'
Step Functionsを利用したことがあると、ステップ間の情報の渡し方、情報の参照のや方法はイメージしやすいかも。
 
Systems Manager Automation構成のポイントについては、QA方式でまとめてみます。

表1の項番2

なぜ、実行するたびにイメージレシピを作成しなおしているの?

イメージレシピを作成する際には、元になるAMIのIDを指定する必要があります。
今回のようにSystems ManagerのAutomationを実行するたびに起動中のEC2からAMIを作成している場合には、AMI IDが毎回異なるため、イメージレシピを作成(もしくは更新)してあげる必要があります。                                     

では、何故、毎回イメージレシピを新規作成にしているかというと、イメージレシピを更新する場合はイメージレシピのバージョンを変更しなければならなく、例えば1.0.0→1.0.1のように既存のものとは異なるバージョンにする必要がありますが、バージョン番号を実行するたびに動的に生成したくなかったというのが理由になります。

動的というのは例えばですがバージョン番号を既存のイメージレシピから取得してインクリメントした値を返すようなLambdaの作成が想定されますが、今回はなるべくコード作成などは行わずにしたかったため、バージョンは固定(2.0.0)として作成して、利用が終わったらイメージレシピを表1の項番 8で削除するようにしています。   

 

表1の項番5

 

どうして、AMIが作成されるまで待つ必要があるの?

これは表1の項番4で実行されている「StartImagePipelineExecution」が非同期のAPIとなっているためです。

その後の表1の項番7で作成されたイメージレシピを削除するためにイメージパイプラインから切り離すようにしているのですが、イメージパイプラインが実行中だと切り離しができません。
そのため、イメージが作成されてイメージパイプラインが実行されてない状態になるまで待つようにしています。

 

 

表1の項番6

 

何のためにイメージにタグを付与しているんだろう?

目的としては作成されたイメージに対してライフサイクルポリシーを適用するためです。

今回のように自動的にAMIを作成していくとどんどんイメージがたまっていくため、保存していくイメージ数を管理していく必要がでてきます。
EC2のImage Builderにはイメージのライフサイクルを設定できるライフサイクルポリシーという機能がありますが、その中でポリシーの適用の対象をタグで指定するようにしているためです。

ここでのタグはEC2のImage Builderでのイメージに付与されるタグになります。AMIのタグとは別のものになりますのでご注意ください。

 

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でのパラメータ設定などはドキュメントから読み取れないことも多くトライ&エラーの連続でした。

今回の設定が同じようなことを試そうとしている方に少しでもお役に立てればと思います。

タイトルとURLをコピーしました