EC2を停止してからAMIを取得する自動パイプラインを【AWS Systems Manager Automation】で実装してみた

AWS

2024.6.11

Topics

はじめに

こんにちは、YUJIです。

最近、「EC2のバックアップ取得を自動化したいが、静止点確保のためにEC2を停止が必要で、どうすれば良いかわからない。」
という旨のお問い合わせを、複数同時にいただいているのを見ました。

上記のご要望を叶えるために

  • AWS Systems Manager Automationで一貫して処理できないか
  • cronで日時指定して自動実行できるように、Amazon EventBridge RulesでAutomationを自動実行できないか

という所を目標に、実験してみます。

記事のターゲット

タイトルでEC2を停止してからAMIを取得しようとしているところから、お察しの通り
「簡単に静止点を確保した上で、AMIを取得したい。しかも自動で。」
という要望を持っている方向けの記事です。

静止点の確保だけで言えば
例えば、Windowsなら専用コンポーネントをEC2にインストールした上で
AWS BackupでVSSスナップショットを有効にする等、様々なアプローチがあります。

が、この記事ではもっと分かりやすく簡単に
EC2を止める→AMIを取得する→止めたEC2を開始する
というフローで実現したいと思います。

引用するjsonやyamlが長すぎて、この記事自体やたらボリュームがあって複雑そうに見えてしまうかもしれませんが
コピペして各リソースを作ってしまえば、実装は簡単です!

※あくまでも概念実証での実験なので、実際の挙動は検証環境等を使って確かめてください。

前提知識

・AWS Systems Manager Automationの概要を理解していること。
この記事のハンズオンを経験いただくと、スムーズに内容が入ってくると思います。

関連記事
AWS Systems Manager Automation ランブックをGUIで直感的に作成する #OpsJAWS

今回は下記のようなAutomationを作って、EventBridge Schedulerで自動実行させます。

ステップ1 EC2を停止する
ステップ2 EC2を強制停止する(ステップ1の失敗時のみ実行)
ステップ3 AMIを取得する
ステップ4 停止したEC2のNameタグを取得する
ステップ5 AMIにNameタグを付与する
ステップ6 停止したインスタンスを開始する

実装するだけなら、ステップ4とステップ5は必要無いです。
…が、NameタグがついてないAMIって気持ち悪いと思いませんか?僕は思います。

事前準備

EC2

対象のEC2に、AmazonSSMManagedInstanceCoreのポリシーが付いたIAMロールがアタッチされていること。

もしEC2をプライベートサブネットに設置する場合は
NATゲートウェイやVPCエンドポイントなどを使い、外部と443ポートでのアウトバウンド通信が可能であること。

Automation用のロール

信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ssm.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

許可ポリシー ※内容を一部修正しました(2024/6/24)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeInstanceStatus",
                "ec2:StartInstances",
                "ec2:StopInstances",
                "ec2:DescribeImages",
                "ec2:CreateImage",
                "ec2:DescribeTags",
                "ec2:CreateTags"
            ],
            "Resource": "*"
        }
    ]
}

Eventbridge用のロール

信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "events.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

許可ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:StartAutomationExecution",
                "ssm:DescribeAutomationExecutions"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:StopInstances",
                "ec2:StartInstances",
                "ec2:CreateImage",
                "ec2:CreateTags"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEqualsIfExists": {
                    "iam:PassedToService": "ssm.amazonaws.com"
                }
            }
        }
    ]

紹介用に、権限はある程度広めでとっています。
気になる方は、対象リソースを*(ワイルドカード)とするのではなく、明示的に指定するなどカスタマイズしてみてください。

実装

Automation ランブックの作成

ランブックの作成ページに移動します。

次に、左上の鉛筆マークを押して、ランブックをお好きな名前に編集したら
[{ }コード] タブを選択して、表示された赤枠の編集部分に下記のyamlをペーストします。

description: |-
  # 概要

  ステップ1 EC2を停止する
  ステップ2 EC2を強制停止する(ステップ1の失敗時のみ実行)
  ステップ3 AMIを取得する
  ステップ4 停止したEC2のNameタグを取得する
  ステップ5 AMIにNameタグを付与する
  ステップ6 停止したインスタンスを開始する
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  InstanceId:
    type: String
    description: (Required) EC2 Instance to stop
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: ''
mainSteps:
  - name: stopInstance
    action: aws:changeInstanceState
    nextStep: CreateAMI
    isEnd: false
    onFailure: step:forceStopInstance
    inputs:
      InstanceIds:
        - '{{ InstanceId }}'
      DesiredState: stopped
  - name: forceStopInstance
    action: aws:changeInstanceState
    nextStep: CreateAMI
    isEnd: false
    inputs:
      InstanceIds:
        - '{{ InstanceId }}'
      CheckStateOnly: false
      DesiredState: stopped
      Force: true
  - name: CreateAMI
    action: aws:createImage
    nextStep: getInstanceName
    isEnd: false
    inputs:
      InstanceId: '{{ InstanceId }}'
      ImageName: '{{ InstanceId }}-{{ global:DATE_TIME }}'
    outputs:
      - Name: CreatedImageId
        Selector: $.ImageId
        Type: String
  - name: getInstanceName
    action: aws:executeScript
    nextStep: taggingAMI
    isEnd: false
    inputs:
      Runtime: python3.8
      Handler: handler
      Script: |
        import boto3
        def handler(event, context):
          ec2 = boto3.client('ec2')
          response = ec2.describe_instances(InstanceIds=[event['InstanceId']])
          tags = response['Reservations'][0]['Instances'][0]['Tags']
          for tag in tags:
            if tag['Key'] == 'Name':
              return {'InstanceName': tag['Value']}
      InputPayload:
        InstanceId: '{{ InstanceId }}'
    outputs:
      - Name: InstanceName
        Selector: $.Payload.InstanceName
        Type: String
  - name: taggingAMI
    action: aws:createTags
    nextStep: startInstance
    isEnd: false
    inputs:
      ResourceIds:
        - '{{ CreateAMI.CreatedImageId }}'
      Tags:
        - Key: Name
          Value: '{{ getInstanceName.InstanceName }}-{{ global:DATE_TIME }}'
  - name: startInstance
    action: aws:changeInstanceState
    isEnd: true
    inputs:
      InstanceIds:
        - '{{ InstanceId }}'
      DesiredState: running

EventBridge ルールの作成

まずはルールの作成画面へ移動します。

ステップ1
ルール名:お好きなルール名
ルールタイプ:スケジュール
を設定し、続行してルールを作成するをクリック

ステップ2
お好みのスケジュールパターンで、Automationを動かしたい時間を設定します。

Amazon EventBridge cron 式のリファレンス
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-cron-expressions.html

画像では、特定の時刻の実行を選び
毎日12:20(UTC) = 21:20(JST)でAutomationを動かすように、cron式を埋めています。

ステップ3
下記のように設定値を埋めていきます。

項目 設定値
ターゲットタイプ AWSのサービス
ドキュメント [Automation ランブックの作成]で作成済みのドキュメント
InstanceID 対象のインスタンスID(i-xxxxxxxxxxx)
AutomationAssumeRole 事前準備で作成したAutomation用のロール
実行ロール 既存のロールを使用
ロール名 事前準備で作成したEventbridge用のロール

ステップ4
ルールにタグを付与できます。
スキップしても問題ありません。

ステップ5
ここまでで設定した内容が表示されるので、各項目が合っているかどうかを確認しましょう。

動作確認

※インスタンスの停止が伴うので、ターゲットのインスンタンスIDは間違っていないかどうかは確認しましょう

試しに、直近の時刻に走るよう、ルールのスケジュールを編集してみましょう。
今回は日本時間の毎日15:05にスケジュールしてみます。

UTC表記で紛らわしいですが、15:05(JST)にAutomationが発火していることが確認できます。
呼び出しのラグがあるので、秒単位では少し誤差がありますね。

実行されたAutomationの詳細を確認すると
・EC2の停止のリクエストを行い、ステータスが[停止中]から、[停止済み]になる
・AMIの取得を開始、AMIが利用可能になる
・AMIへNameタグをつける
・EC2を再度開始する
までの一貫した処理が、正常に行われていることが確認できます。

※停止が成功しているので、強制停止のステップは実行されません。

実際にAMIを見に行ってみると、ちゃんと居ました。

ただし、変数:global:DATE_TIMEがUTCを見に行ってるため、UTCの時刻でタグ付けされてしまってますね。
スクリプトとステップが複雑化してしまうので、NameタグにおいてのJSTへの変換は今回は行いません。

Automationが実行結果で「Success」と言っているので
疑う余地はあまり無いですが、EC2がちゃんと起動し直されていることも確認できます。

これで、「EC2の停止→停止済みのEC2からAMIを取得→EC2の開始」のフローを一貫して行うAutomationを
事前のスケジュール通りに、自動で行えるようになりました。

まとめ

以上、AWS Systems Manager AutomationとEventBridgeで
静止点の確保+AMI取得の自動化までを紹介してみました。

Automationのステップを編集すれば
「StartBackupJobのAPIを叩いてバックアップ処理自体はAWS Backup経由にする」など
カスタマイズは効くはずなので、是非要件に合わせて色々と試してみてください!

今回の記事が参考になれば幸いです。
最後まで読んでいただき、ありがとうございました。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

YUJI

2023年9月に入社 邦ロックとVtuber好き

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら