SSM AutomationをIaCと組み合わせて本番運用したら、思った以上に沼だった話

パッチ適用漏れで痛い目を見てから1年。Runbook設計・IAM権限・マルチアカウント対応まで、実際に詰まったポイントと乗り越え方を実装コード付きで振り返ります。

SSM Automationに本気で向き合うまで

うちのチームがSSM Automationを「ちゃんと設計して使う」ようになったのは、去年の夏ごろ。それまでは正直、RunCommandでシェルスクリプトを流すだけの使い方しかしてなかった。

転機になったのは、本番でEC2のパッチ適用漏れが発覚したこと。手動で数十台のインスタンスを確認して回る羽目になり、「これはさすがに仕組みを変えないといけない」と痛感した。同時期にSOC2審査でCloudTrail・Config設計が泥沼になった実録を参考にしながら、監査証跡の自動化もあわせて整えることになった。

改めてSSM Automationのドキュメントを読み直したら、自分たちが全然使いこなせてなかった機能の多さに気づいて愕然とした。2026年時点でのSSM Automationは、単なるリモート実行ツールじゃなく、フル機能のワークフローオーケストレーターとして使える。ここにたどり着くまでに1年近くかかったので、その知見をまとめておきたい。


SSM Automationの現在地と使うべきユースケース

2026年時点では、大きく以下の用途で使われることが多い。

ユースケース向いてる理由注意点
EC2パッチ管理・再起動Maintenance Windowと統合しやすいステートフルなフロー管理が弱い
AMI自動作成・ライフサイクル管理公式RunbookがAWS管理で豊富並列実行時の競合に注意
インシデント対応の自動化Systems Manager OpsCenter連携複雑なロジックはStep Functionsの方が向く
マルチアカウントへのオペレーションOrganizations連携でターゲット広げられるIAM設計が複雑になる
IaC展開後のポストプロビジョニングCDK/Terraformからトリガーしやすいタイムアウト設計を慎重に

正直「Step Functionsでよくない?」って思う場面はある。ただSSM AutomationはAWSリソース操作に特化したアクションが豊富で、EC2/RDS/AMI周りは圧倒的に書きやすい。個人的な感覚では、

  • 純粋なAWSリソースオペレーション → SSM Automation
  • 複雑なビジネスロジックを含むワークフロー → Step Functions

この使い分けが今のところしっくりきてる。ここは好みが分かれるかもしれないけど。


設計の核心:Runbook設計とIAM権限モデル

SSM Automationで一番ハマるのは、Runbookの設計IAM権限の組み合わせ。ここを雑にやると、後で権限地獄に陥る。

Runbook設計のパターン

実際に本番で使っているRunbookの構造を示す。以下はEC2のブルーグリーンAMI更新フローだ。

# ssm-automation-ami-rotation.yaml (CDKで管理するRunbook定義)
description: |
  EC2 AMI Rotation Automation
  - 新AMIからLaunch Templateを更新
  - Auto Scaling Groupのインスタンスをローリング更新
  - 完了後にCloudWatchアラームで正常性確認
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  AutomationAssumeRole:
    type: String
    description: The ARN of the role that allows Automation to perform actions
  AmiId:
    type: String
    description: Target AMI ID
  AutoScalingGroupName:
    type: String
    description: Target ASG name
  LaunchTemplateId:
    type: String
    description: Launch Template ID to update
mainSteps:
  - name: ValidateAmi
    action: aws:executeAwsApi
    inputs:
      Service: ec2
      Api: DescribeImages
      ImageIds:
        - '{{ AmiId }}'
    outputs:
      - Name: AmiState
        Selector: $.Images[0].State
        Type: String
    nextStep: CheckAmiAvailable

  - name: CheckAmiAvailable
    action: aws:branch
    inputs:
      Choices:
        - NextStep: UpdateLaunchTemplate
          Variable: '{{ ValidateAmi.AmiState }}'
          StringEquals: available
      Default: FailWithAmiNotAvailable

  - name: UpdateLaunchTemplate
    action: aws:executeAwsApi
    inputs:
      Service: ec2
      Api: CreateLaunchTemplateVersion
      LaunchTemplateId: '{{ LaunchTemplateId }}'
      SourceVersion: '$Latest'
      LaunchTemplateData:
        ImageId: '{{ AmiId }}'
    outputs:
      - Name: NewVersionNumber
        Selector: $.LaunchTemplateVersion.VersionNumber
        Type: Integer
    nextStep: RefreshInstances

  - name: RefreshInstances
    action: aws:executeAwsApi
    inputs:
      Service: autoscaling
      Api: StartInstanceRefresh
      AutoScalingGroupName: '{{ AutoScalingGroupName }}'
      Preferences:
        MinHealthyPercentage: 80
        InstanceWarmup: 300
    outputs:
      - Name: InstanceRefreshId
        Selector: $.InstanceRefreshId
        Type: String
    nextStep: WaitForRefreshComplete

  - name: WaitForRefreshComplete
    action: aws:waitForAwsResourceProperty
    timeoutSeconds: 3600
    inputs:
      Service: autoscaling
      Api: DescribeInstanceRefreshes
      AutoScalingGroupName: '{{ AutoScalingGroupName }}'
      PropertySelector: $.InstanceRefreshes[0].Status
      DesiredValues:
        - Successful
    nextStep: NotifySuccess

  - name: NotifySuccess
    action: aws:executeAwsApi
    inputs:
      Service: sns
      Api: Publish
      TopicArn: !Sub 'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:automation-notifications'
      Message: 'AMI rotation completed successfully. New AMI: {{ AmiId }}'
      Subject: '[SSM Automation] AMI Rotation Succeeded'
    isEnd: true

  - name: FailWithAmiNotAvailable
    action: aws:executeScript
    inputs:
      Runtime: python3.11
      Handler: script_handler
      Script: |
        def script_handler(events, context):
            raise Exception(f"AMI {events['AmiId']} is not in available state")
      InputPayload:
        AmiId: '{{ AmiId }}'
    isEnd: true

このRunbookのポイントは**aws:branchでAMIの状態を検証してからオペレーションを実行**している点。バリデーションを省くと、後のステップで謎のエラーが出て原因究明に時間を取られる。地味だけど本当に大事で、これだけで障害対応の時間がかなり変わってくる。

フロー全体をざっと整理するとこんなイメージだ。

flowchart TD
    A([StartAutomation]) --> B[ValidateAmi\nAMI状態を確認]
    B --> C{CheckAmiAvailable\nState == available?}
    C -- Yes --> D[UpdateLaunchTemplate\n新AMIでLTバージョン作成]
    C -- No --> E[FailWithAmiNotAvailable\n例外を投げて終了]
    D --> F[RefreshInstances\nASGローリング更新を開始]
    F --> G[WaitForRefreshComplete\n更新完了を待機 最大3600s]
    G --> H[NotifySuccess\nSNSで完了通知]
    H --> Z([End])
    E --> Z

IAM権限の設計

SSM AutomationのIAM設計で悩む人が多い気がする。assumeRoleで渡すロールの権限範囲を間違えると、セキュリティリスクか権限不足エラーのどちらかになる。

# CDKでのAutomation実行ロール定義
from aws_cdk import (
    aws_iam as iam,
    aws_ssm as ssm,
)

# Automation実行ロール(Runbookが使うロール)
automation_role = iam.Role(
    self, "AutomationExecutionRole",
    assumed_by=iam.ServicePrincipal("ssm.amazonaws.com"),
    role_name="ssm-automation-execution-role",
    description="Role assumed by SSM Automation to perform AWS API operations",
)

# 必要最小限の権限だけ付与(ここを雑にやると後悔する)
automation_role.add_to_policy(iam.PolicyStatement(
    sid="EC2AmiOperations",
    effect=iam.Effect.ALLOW,
    actions=[
        "ec2:DescribeImages",
        "ec2:CreateLaunchTemplateVersion",
        "ec2:DescribeLaunchTemplates",
    ],
    resources=["*"],  # DescribeはリソースARN絞れないので仕方なく*
))

automation_role.add_to_policy(iam.PolicyStatement(
    sid="ASGOperations",
    effect=iam.Effect.ALLOW,
    actions=[
        "autoscaling:StartInstanceRefresh",
        "autoscaling:DescribeInstanceRefreshes",
    ],
    resources=[
        f"arn:aws:autoscaling:{self.region}:{self.account}:autoScalingGroup:*"
        f":autoScalingGroupName/prod-*",  # 本番ASGに限定
    ],
))

automation_role.add_to_policy(iam.PolicyStatement(
    sid="SNSNotification",
    effect=iam.Effect.ALLOW,
    actions=["sns:Publish"],
    resources=[
        f"arn:aws:sns:{self.region}:{self.account}:automation-notifications"
    ],
))

# Automation自体を呼び出すロール(ユーザーや別サービスが使うロール)
caller_role = iam.Role(
    self, "AutomationCallerRole",
    assumed_by=iam.AccountPrincipal(self.account),
    role_name="ssm-automation-caller-role",
)

caller_role.add_to_policy(iam.PolicyStatement(
    sid="StartAutomation",
    effect=iam.Effect.ALLOW,
    actions=["ssm:StartAutomationExecution"],
    resources=[
        f"arn:aws:ssm:{self.region}:{self.account}:automation-definition/AMIRotation:*"
    ],
))

# IAMロールのPassRole権限(必須。これ忘れてよく詰まる)
caller_role.add_to_policy(iam.PolicyStatement(
    sid="PassRoleToAutomation",
    effect=iam.Effect.ALLOW,
    actions=["iam:PassRole"],
    resources=[automation_role.role_arn],
    conditions={
        "StringEquals": {
            "iam:PassedToService": "ssm.amazonaws.com"
        }
    },
))

iam:PassRoleを忘れると「AutomationAssumeRole has no permissions」みたいな謎エラーになる。これは初見だと本当に分かりにくい。


マルチアカウント対応とIaCとの連携

うちのチームで一番効果を実感したのが、マルチアカウントへのAutomation実行とCDKとの組み合わせ。AWS Organizations運用でSCP・RCP設計を見直した話でも書いたような構成に、Automationを重ねる形だ。

アーキテクチャ全体像

graph TB
    subgraph ManagementAccount["管理アカウント (Management)"]
        EventBridge["EventBridge\nScheduler"]
        SSMCenter["SSM Automation\n(Central Orchestrator)"]
        SNS["SNS\n通知トピック"]
    end

    subgraph OrgDelegated["委任アカウント (Automation Hub)"]
        AutomationDoc["SSM Automation\nDocument"]
        AutomationRole["Automation\nExecution Role"]
        S3Logs["S3\n実行ログバケット"]
        CloudWatch["CloudWatch\nLogs"]
    end

    subgraph VPC_Prod["VPC: Production (ap-northeast-1)"]
        subgraph AZ_A["AZ: ap-northeast-1a"]
            EC2_A1["EC2 (Web)"]
            EC2_A2["EC2 (App)"]
        end
        subgraph AZ_C["AZ: ap-northeast-1c"]
            EC2_C1["EC2 (Web)"]
            EC2_C2["EC2 (App)"]
        end
        ASG["Auto Scaling Group"]
        SSMAgent["SSM Agent\n(各EC2にインストール済)"]
    end

    subgraph VPC_Staging["VPC: Staging (ap-northeast-1)"]
        EC2_STG["EC2 (Staging)"]
    end

    EventBridge -->|"週次スケジュール実行"| SSMCenter
    SSMCenter -->|"クロスアカウント実行"| AutomationDoc
    AutomationDoc -->|"assume role"| AutomationRole
    AutomationRole -->|"EC2/ASG操作"| ASG
    AutomationRole -->|"EC2/ASG操作"| EC2_STG
    ASG --> EC2_A1
    ASG --> EC2_A2
    ASG --> EC2_C1
    ASG --> EC2_C2
    EC2_A1 --- SSMAgent
    EC2_A2 --- SSMAgent
    AutomationDoc -->|"実行ログ"| S3Logs
    AutomationDoc -->|"ログ転送"| CloudWatch
    SSMCenter -->|"完了通知"| SNS

CDKからAutomationをトリガーする

IaCと組み合わせる際に便利なのが、CDK DeploymentのカスタムリソースとしてAutomationを呼び出すパターン。インフラ構築後のポストプロビジョニング処理に使えて、これが地味に強い。

import aws_cdk as cdk
from aws_cdk import (
    custom_resources as cr,
    aws_iam as iam,
    aws_ssm as ssm,
)

class PostProvisioningConstruct(cdk.Construct):
    def __init__(self, scope, id, **kwargs):
        super().__init__(scope, id, **kwargs)

        # Automation実行をCDKカスタムリソースから呼び出す
        post_provisioning = cr.AwsCustomResource(
            self, "RunPostProvisioning",
            on_create=cr.AwsSdkCall(
                service="SSM",
                action="startAutomationExecution",
                parameters={
                    "DocumentName": "PostProvisioningSetup",
                    "DocumentVersion": "$DEFAULT",
                    "Parameters": {
                        "EnvironmentName": ["production"],
                        "AutomationAssumeRole": [
                            "arn:aws:iam::123456789012:role/ssm-automation-execution-role"
                        ],
                    },
                },
                physical_resource_id=cr.PhysicalResourceId.of(
                    "PostProvisioningExecution"
                ),
                output_paths=["AutomationExecutionId"],
            ),
            policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
                resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
            ),
        )

このパターン、まだ全チームに展開できてるわけじゃないけど、インフラ構築と設定の同期ズレが劇的に減った。


運用して分かった落とし穴と監視設計

1年以上運用してわかった地雷を正直に書いておく。

タイムアウト設計は最初から慎重に

aws:waitForAwsResourcePropertyのデフォルトタイムアウトは3600秒(1時間)。大規模なASGローリング更新だとこれを超えることがある。実際にうちでは150台のASGで引っかかって、Automationが途中でFailed状態になり、インスタンスが中途半端な状態になったことがあった。これは結構ヒヤッとした。

設定すべき値の目安:

オペレーション推奨タイムアウト備考
AMI作成7200秒大きいEBSボリュームがあると遅い
ASGローリング更新(〜50台)3600秒デフォルトでほぼOK
ASGローリング更新(50台以上)7200〜10800秒台数に応じて調整
RDSスナップショット3600秒DBサイズ次第で変動大
EC2再起動 + ヘルスチェック600秒アプリ起動時間を加味

実行結果の可視化と監視

Automationの実行履歴はデフォルトでCloudWatch Logsには流れない。明示的に設定が必要で、これを知らずに1ヶ月運用して後から気づいた。設定自体は難しくないけど、最初から入れておけばよかったと今でも思う。

# EventBridgeでAutomation実行ステータスを監視する
from aws_cdk import aws_events as events
from aws_cdk import aws_events_targets as targets
from aws_cdk import aws_sns as sns

notification_topic = sns.Topic(self, "AutomationAlertTopic")

# Automation失敗をキャッチするルール
failure_rule = events.Rule(
    self, "AutomationFailureRule",
    event_pattern=events.EventPattern(
        source=["aws.ssm"],
        detail_type=["SSM Automation Execution Status-change Notification"],
        detail={
            "Status": ["Failed", "TimedOut", "Cancelled"]
        }
    ),
)

failure_rule.add_target(
    targets.SnsTopic(
        notification_topic,
        message=events.RuleTargetInput.from_event_path(
            "$.detail"
        ),
    )
)

実行メトリクスの現状

xychart-beta
    title "SSM Automation 月次実行統計 (2026年1〜6月)"
    x-axis ["1月", "2月", "3月", "4月", "5月", "6月"]
    y-axis "実行回数" 0 --> 200
    bar [45, 62, 78, 95, 120, 148]
    line [43, 58, 74, 91, 116, 145]

青が総実行数、橙が成功数。4月以降にRunbook設計を見直したあとはほぼ成功率100%を維持できてる。最初の3ヶ月の失敗率は約6%で、大半がタイムアウトと権限エラーだった。振り返ると、どちらも「最初にちゃんと設計していれば防げた」ものばかりだった。

インシデント対応のベストプラクティスでも触れられているように、自動化の失敗ケースのハンドリングをちゃんと設計しておかないと、自動化が逆にインシデントの原因になる。これは身に染みて感じた。

マルチアカウント実行の権限フロー

sequenceDiagram
    participant EB as EventBridge Scheduler
    participant SSM as SSM Automation (Hub)
    participant STS as STS AssumeRole
    participant Tgt as Target Account EC2/ASG
    participant CW as CloudWatch Logs

    EB->>SSM: StartAutomationExecution
    SSM->>STS: AssumeRole (CrossAccountRole)
    STS-->>SSM: Temporary Credentials
    SSM->>Tgt: EC2/ASG API Call
    Tgt-->>SSM: Response
    SSM->>CW: ExecutionLog
    SSM-->>EB: ExecutionId
    Note over SSM,CW: 実行ログはCloudWatch Logsに<br/>明示的に設定が必要

クロスアカウント実行時のポイントは、ターゲットアカウント側にも信頼ポリシーを設定する必要があること。これを忘れると「AccessDenied」で詰まる。SCP本番運用で学んだIAMとの組み合わせ地獄とも絡む話で、Organizations全体の権限設計と整合させないといけない。

// ターゲットアカウントのCrossAccountRole信頼ポリシー
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AUTOMATION_HUB_ACCOUNT_ID:role/ssm-automation-execution-role"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "ssm-automation-cross-account-2026"
        }
      }
    }
  ]
}

ExternalIdの設定は地味だけど重要。これがないとConfused Deputy問題が発生しうる。うっかり省略しがちな設定なので、CDKのコードレビューでも必ずチェックするようにしてる。


Terraform/CDKとの住み分け

IaC担当者によく聞かれるのが「SSM AutomationってTerraformやCDKと何が違うの?」という質問。整理するとこうなる。

CDK/TerraformSSM Automation
主な対象インフラリソースの定義・作成既存リソースへのオペレーション
実行タイミングデプロイ時イベント/スケジュール/手動
状態管理Stateファイルで管理実行履歴のみ(状態なし)
ロールバックStackレベルでロールバック可能自前で巻き戻しステップが必要
マルチアカウントStackSets等で可能Organizations統合で自然に対応
監査証跡CloudTrail + Stateファイル実行履歴 + CloudWatch Logs

「Terraformで全部やればいいじゃん」という意見も聞くけど、日次/週次で繰り返すオペレーション(パッチ適用、バックアップ、ログローテーション)をTerraformで管理するのは設計として歪だと思ってる。IaCはインフラの定義に使って、運用オペレーションはSSM Automationに任せる、という棲み分けが一番うまく機能してる感覚がある。

皆さんはどうしてます?この使い分けについては社内でも意見が割れることがあって、正解は一つじゃないかもしれない。


まとめ

1年以上SSM Automationを本番で使ってきてわかった要点をまとめると、こんな感じだ。

  1. Runbook設計はバリデーションステップから始めるaws:branchでリソース状態を確認してからオペレーションするだけで、謎エラーが激減する
  2. IAMは「実行ロール」と「呼び出しロール」を必ず分離するiam:PassRoleの設定漏れが最も多い詰まりポイント
  3. タイムアウトはリソース規模に合わせて明示的に設定する — デフォルト値は大規模環境では足りないことがある
  4. EventBridgeでステータス変更を監視する — Automationの失敗を検知する仕組みがないと、サイレント障害になる
  5. IaCはインフラ定義、SSM Automationは運用オペレーション — この棲み分けを最初から決めておくと設計が整理しやすい

次のアクションとしては、まず既存のシェルスクリプト手順書の1つをSSM Automationに移行してみるのがおすすめ。AWS Managed DocumentのAWS-UpdateSSMAgentAWS-RunPatchBaselineを試して動きを理解してから、自前のRunbook設計に入ると詰まりにくい。あとCloudWatch LogsへのAutomation実行ログ転送は最初から有効化しておいた方がいい。後から有効化しようとすると、監査証跡の空白期間ができて面倒なことになる。

設計の細かい話は省いたところも多いので、「ここもっと詳しく」みたいな部分があればコメントかTwitterで聞いてもらえると。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事