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/Terraform | SSM Automation | |
|---|---|---|
| 主な対象 | インフラリソースの定義・作成 | 既存リソースへのオペレーション |
| 実行タイミング | デプロイ時 | イベント/スケジュール/手動 |
| 状態管理 | Stateファイルで管理 | 実行履歴のみ(状態なし) |
| ロールバック | Stackレベルでロールバック可能 | 自前で巻き戻しステップが必要 |
| マルチアカウント | StackSets等で可能 | Organizations統合で自然に対応 |
| 監査証跡 | CloudTrail + Stateファイル | 実行履歴 + CloudWatch Logs |
「Terraformで全部やればいいじゃん」という意見も聞くけど、日次/週次で繰り返すオペレーション(パッチ適用、バックアップ、ログローテーション)をTerraformで管理するのは設計として歪だと思ってる。IaCはインフラの定義に使って、運用オペレーションはSSM Automationに任せる、という棲み分けが一番うまく機能してる感覚がある。
皆さんはどうしてます?この使い分けについては社内でも意見が割れることがあって、正解は一つじゃないかもしれない。
まとめ
1年以上SSM Automationを本番で使ってきてわかった要点をまとめると、こんな感じだ。
- Runbook設計はバリデーションステップから始める —
aws:branchでリソース状態を確認してからオペレーションするだけで、謎エラーが激減する - IAMは「実行ロール」と「呼び出しロール」を必ず分離する —
iam:PassRoleの設定漏れが最も多い詰まりポイント - タイムアウトはリソース規模に合わせて明示的に設定する — デフォルト値は大規模環境では足りないことがある
- EventBridgeでステータス変更を監視する — Automationの失敗を検知する仕組みがないと、サイレント障害になる
- IaCはインフラ定義、SSM Automationは運用オペレーション — この棲み分けを最初から決めておくと設計が整理しやすい
次のアクションとしては、まず既存のシェルスクリプト手順書の1つをSSM Automationに移行してみるのがおすすめ。AWS Managed DocumentのAWS-UpdateSSMAgentやAWS-RunPatchBaselineを試して動きを理解してから、自前のRunbook設計に入ると詰まりにくい。あとCloudWatch LogsへのAutomation実行ログ転送は最初から有効化しておいた方がいい。後から有効化しようとすると、監査証跡の空白期間ができて面倒なことになる。
設計の細かい話は省いたところも多いので、「ここもっと詳しく」みたいな部分があればコメントかTwitterで聞いてもらえると。