管理アカウントからSSM Automationを使用して、複数のメンバーアカウントのEC2インスタンスに特定のコマンドを実行してみた
はじめに
自作のコマンドをオートメーションランブックとして作成し、管理アカウントからSSMオートメーションを使用して複数のメンバーアカウントのEC2インスタンスで実行する方法をまとめました。
SSMドキュメントにはいくつかのタイプがあり、その中でSSMオートメーションで利用するドキュメントは「オートメーションランブック」として分類されています。
そのため、本記事では、SSMオートメーションで利用するSSMドキュメントを「オートメーションランブック」と呼びます。
前提条件
- 対象のEC2インスタンスがマネージドインスタンス化されていること
- 今回は、OSがLinuxのEC2インスタンスを用意しています。
IAMロール作成
管理アカウントからメンバーアカウントに対して、SSMオートメーションを実行するには、管理アカウントとメンバーアカウントの両方にIAMロールを作成する必要があります。
以下のドキュメントには、2つのCloudFormationテンプレートが用意されています。
AWSTemplateFormatVersion: "2010-09-09"
Description: "Configure the AWS-SystemsManager-AutomationAdministrationRole to allow multi-region and multi-account automations for your organization."
Resources:
MasterAccountRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ssm.amazonaws.com
Action: sts:AssumeRole
Condition:
ArnLike:
aws:SourceArn:
Fn::Sub: arn:${AWS::Partition}:ssm:*:*:automation-execution/*
Path: "/"
RoleName: AWS-SystemsManager-AutomationAdministrationRole
Policies:
- PolicyName: 'AssumeRole-AWSSystemsManagerAutomationExecutionRole'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource:
Fn::Sub: arn:${AWS::Partition}:iam::*:role/AWS-SystemsManager-AutomationExecutionRole
- Effect: Allow
Action:
- organizations:ListAccountsForParent
- organizations:ListChildren
Resource: '*'
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Configure the AWS-SystemsManager-AutomationExecutionRole.'
Parameters:
AdminAccountId:
Type: String
Description: "The ID of the primary account from which automations will be initiated for your organization."
MaxLength: 12
MinLength: 12
OrganizationID:
Type: String
Description: AWS Organizations ID.
AllowedPattern: "^o-[a-z0-9]{10,32}$"
Resources:
AWSSystemsManagerAutomationExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: AWS-SystemsManager-AutomationExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS:
Fn::Sub:
- arn:${AWS::Partition}:iam::${AdminAccountId}:role/AWS-SystemsManager-AutomationAdministrationRole
- AdminAccountId:
Ref: AdminAccountId
Action: sts:AssumeRole
Condition:
StringEquals:
aws:PrincipalOrgID:
Ref: OrganizationID
- Effect: Allow
Principal:
Service: ssm.amazonaws.com
Action:
- sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount:
Fn::Sub: ${AWS::AccountId}
ArnLike:
aws:SourceArn:
Fn::Sub: arn:${AWS::Partition}:ssm:*:${AWS::AccountId}:automation-execution/*
ManagedPolicyArns:
- Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole
Path: "/"
Policies:
- PolicyName: ExecutionPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- resource-groups:ListGroupResources
- tag:GetResources
- ec2:DescribeInstances
Resource: "*"
- Effect: Allow
Action:
- iam:PassRole
Resource:
Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:role/AWS-SystemsManager-AutomationExecutionRole
CloudFormationでIAMロール作成
管理アカウントで作成するIAMロールは、以下の設定を使用してCloudFormationスタックを作成します。
作成されるIAMロール名は、AWS-SystemsManager-AutomationAdministrationRoleです。
- スタック名:AWS-SystemsManager-AutomationAdministrationRole
- テンプレートの指定:AWS-SystemsManager-AutomationAdministrationRole (org).yml
メンバーアカウントで作成が必要なIAMロールは、以下の設定を使用してCloudFormation StackSetsを作成します。
作成されるIAMロール名は、AWS-SystemsManager-AutomationExecutionRoleです。
- アクセス許可モデル:サービスマネージドアクセス許可(SERVICE_MANAGED)
- テンプレートの指定:AWS-SystemsManager-AutomationExecutionRole (org).yml
- StackSet 名:AWS-SystemsManager-AutomationExecutionRole
- パラメータ
- AdminAccountId:管理アカウントのAWSアカウントID
- OrganizationID :管理アカウントのAWS Organizationsページで確認できる「組織 ID」
- デプロイターゲット:組織単位 (OU) へのデプロイ
- AWS OU ID:対象アカウントを含むOU ID
- リージョンの指定:東京リージョン
オートメーションランブックを作成
管理アカウントで、SSMドキュメントに遷移し、[ドキュメントの作成]から[オートメーション]を選択します。
以下のコードを貼り付け、check-os
という名前でオートメーションランブックを作成します。
schemaVersion: '0.3'
description: Check OS settings using SSM Automation.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
InstanceId:
type: String
description: The ID of the EC2 instance to check.
CheckHostname:
type: String
default: check
description: Check hostname or skip.
allowedValues:
- check
- skip
CheckTimezone:
type: String
default: check
description: Check timezone or skip.
allowedValues:
- check
- skip
CheckLocale:
type: String
default: check
description: Check locale or skip.
allowedValues:
- check
- skip
CheckUpdate:
type: String
default: check
description: Check yum updates or skip.
allowedValues:
- check
- skip
CheckNTP:
type: String
default: check
description: Check NTP status or skip.
allowedValues:
- check
- skip
mainSteps:
- name: CheckHostname
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
nextStep: CheckTimezone
isEnd: false
onFailure: Continue
inputs:
DocumentName: AWS-RunShellScript
Parameters:
commands:
- if [[ "{{ CheckHostname }}" == "check" ]]; then hostname; else echo "Skipping hostname check"; fi
InstanceIds:
- '{{ InstanceId }}'
outputs:
- Name: HostnameOutput
Selector: $.CommandPlugins[0].Output
Type: String
- name: CheckTimezone
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
nextStep: CheckLocale
isEnd: false
onFailure: Continue
inputs:
DocumentName: AWS-RunShellScript
Parameters:
commands:
- if [[ "{{ CheckTimezone }}" == "check" ]]; then timedatectl status; else echo "Skipping timezone check"; fi
InstanceIds:
- '{{ InstanceId }}'
outputs:
- Name: TimezoneOutput
Selector: $.CommandPlugins[0].Output
Type: String
- name: CheckLocale
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
nextStep: CheckUpdate
isEnd: false
onFailure: Continue
inputs:
DocumentName: AWS-RunShellScript
Parameters:
commands:
- if [[ "{{ CheckLocale }}" == "check" ]]; then localectl status; else echo "Skipping locale check"; fi
InstanceIds:
- '{{ InstanceId }}'
outputs:
- Name: LocaleOutput
Selector: $.CommandPlugins[0].Output
Type: String
- name: CheckUpdate
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
nextStep: CheckNTP
isEnd: false
onFailure: Continue
inputs:
DocumentName: AWS-RunShellScript
Parameters:
commands:
- if [[ "{{ CheckUpdate }}" == "check" ]]; then yum check-update; else echo "Skipping update check"; fi
InstanceIds:
- '{{ InstanceId }}'
outputs:
- Name: UpdateOutput
Selector: $.CommandPlugins[0].Output
Type: String
- name: CheckNTP
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
isEnd: true
onFailure: Continue
inputs:
DocumentName: AWS-RunShellScript
Parameters:
commands:
- if [[ "{{ CheckNTP }}" == "check" ]]; then chronyc sources; else echo "Skipping NTP check"; fi
InstanceIds:
- '{{ InstanceId }}'
outputs:
- Name: NTPOutput
Selector: $.CommandPlugins[0].Output
Type: String
以下の記事のSSMドキュメント(コマンドドキュメント)を参考にしました。
タイムアウト
今回作成したオートメーションランブックでは、各ステップごとのコマンド実行に対して、以下のようにタイムアウトを設定しました。
mainSteps:
- name: CheckHostname
action: aws:runCommand
maxAttempts: 1
timeoutSeconds: 10
SSMオートメーションの対象EC2インスタンスは、起動中のマネージドインスタンス以外にもアンマネージドインスタンスや停止中のEC2インスタンスも実行対象となります。
タイムアウトを設定しないと、長時間オートメーション実行のステータスが進行中のままになってしまうため、基本的にはタイムアウトは設定した方がよいと思います。
SSM Automationを実行
マネジメントコンソール上ではなく、AWS CLIでSSM Automationを実行します。
理由は、ターゲットアカウントを指定する際、OUで指定すると、OU直下のAWSアカウントしか対象にならず、指定したOUの子OU配下に存在するアカウントは対象にならないためです。
Accounts and organizational units (OUs)
AWS CLIであれば、--target-locations
に"IncludeChildOrganizationUnits": true,
を設定すると子や孫OUも対象になりますが、マネジメントコンソールでは設定できません。
IncludeChildOrganizationUnits
Indicates whether to include child organizational units (OUs) that are children of the targeted OUs. The default is false.
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/APIReference/API_TargetLocation.html
その点が問題なければ、マネジメントコンソール上で実行しても構いません。
実行するEC2インスタンスをタグで指定して実行する場合、AWS CloudShellから以下のコマンドで実行します。
aws ssm start-automation-execution \
--document-name "check-os" \
--parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
--target-parameter-name InstanceId \
--targets Key=tag:ssm-automation,Values=true \
--target-locations '[{
"Accounts": ["ou-o2pk-xxxxxxxx"],
"IncludeChildOrganizationUnits": true,
"Regions": ["ap-northeast-1"],
"ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
}]'
--parameters
- AutomationAssumeRoleは管理アカウントで作成したIAMロールのARNの指定が必須です。
- 以下はオートメーションランブックに設定されているパラメータです。
- CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check
--target-locations
- Regions:対象リージョンを指定
- Accounts:アカウントIDかOU IDを指定する
- ExecutionRoleName:リソースアカウントに存在するIAMロール名を指定
--targets
- タグキー名:ssm-automation
- タグ値:true
実行後、AutomationExecutionId
が出力されます。
{
"AutomationExecutionId": "xxxxxxxx"
}
オートメーションランブックはデフォルトバージョンで実行されます。
明示的にオートメーションランブックのバージョンを指定したい場合、--document-version "6"
のように追加するとよいです。
aws ssm start-automation-execution \
--document-name "check-os" \
--document-version "6" \
--parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
--target-parameter-name InstanceId \
--targets Key=tag:ssm-automation,Values=true \
--target-locations '[{
"Accounts": ["ou-o2pk-xxxxxxxx"],
"IncludeChildOrganizationUnits": true,
"Regions": ["ap-northeast-1"],
"ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
}]'
タグを指定せず、対象アカウントの全てのEC2インスタンスを対象にする場合、--targets Key=AWS::EC2::Instance,Values=*
と指定します。
aws ssm start-automation-execution \
--document-name "check-os" \
--parameters AutomationAssumeRole=arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
--target-parameter-name InstanceId \
--targets Key=AWS::EC2::Instance,Values=* \
--target-locations '[{
"Accounts": ["ou-o2pk-xxxxxxxx"],
"IncludeChildOrganizationUnits": true,
"Regions": ["ap-northeast-1"],
"ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
}]'
マネジメントコンソール上の場合、以下のように設定します。
実行結果の内容を確認
実行結果(Execution ID)は以下のように作成されます
- 管理アカウント
- 親Execution IDは1つ
- 各メンバーアカウントの実行結果をまとめたもの
- 親Execution IDは1つ
- メンバーアカウント
- 子Execution IDは2つ以上作成される
- 1つは、全EC2インスタンスの実行結果をまとめたもの
- 残りは、各EC2インスタンスごとの実行結果詳細
- 子Execution IDは2つ以上作成される
管理アカウントでSSMオートメーションを実行していますので、メンバーアカウントに親Execution IDは存在しません
管理アカウントでは、実行した対象メンバーアカウントごとにExecuted steps
が作成されます。Step IDを選択します。
実行詳細ページに遷移しますが、ExecutionId
は、メンバーアカウントのExecutionId
に紐づいているため、以下のスクリーンショットのExecutionId
である0c521866-d0b7-4ffa-9b42-e720c59cdffd
を選択しても実行結果内容(コマンド実行結果)は確認できません。
Automation自体の成功可否は管理アカウントからステータスで確認できますが、コマンド実行がエラーであるかどうかや、コマンド実行結果のOutputsについては管理アカウントから直接確認することはできません。
これらの詳細な情報を確認するには、メンバーアカウントにログインして確認する必要があります
メンバーアカウントにログインし、ExecutionId
(例: 0c521866-d0b7-4ffa-9b42-e720c59cdffd)を選択して実行詳細ページに移動します。
このページでは、各EC2インスタンスごとに異なるStep execution IDが表示されます。
次に、確認したいStep execution ID
を選択します。
これにより、オートメーションランブックの各Stepごとに実行されたコマンドの結果を確認できます。
さらに、特定のStep execution ID
を選択すると、そのStepで実行されたコマンドの詳細な結果を確認することができます。
Windowsでの文字化け対策
OSがWindowsの場合、コマンド実行結果のOutputに文字化けが発生することがあります。これを解決するために、実行するコマンドの前にchcp 65001
を追加します。
このコマンドは、コンソールの文字コードをUTF-8に設定します。
UTF-8は、日本語を含む多言語の文字を正しく表示できる文字エンコーディングです。
以下は、オートメーションランブックでの実装例です
- name: xxxx
action: aws:runCommand
inputs:
DocumentName: AWS-RunPowerShellScript
Parameters:
commands:
- chcp 65001
- # ここに特定のWindowsコマンドを記述
管理アカウントのEC2インスタンスもAutomation対象にしたい場合
SSM Automation実行時に管理アカウントのEC2インスタンスも対象に含めたい場合、Root OUを指定すると以下のようなエラーが発生します。
aws ssm start-automation-execution \
--document-name "check-os" \
--parameters AutomationAssumeRole=arn:aws:iam::111111111111:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
--target-parameter-name InstanceId \
--targets Key=tag:ssm-automation,Values=true \
--target-locations '[{
"Accounts": ["r-o2pk"],
"IncludeChildOrganizationUnits": true,
"Regions": ["ap-northeast-1"],
"ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
}]'
Failure message
The provided role: arn:aws:iam::管理アカウントID:role/AWS-SystemsManager-AutomationExecutionRole can't be assumed. (Service: null; Status Code: 0; Error Code: null; Request ID: null; Proxy: null)
このエラーは、管理アカウントにAWS-SystemsManager-AutomationExecutionRole
が存在しないことが原因です。
管理アカウントにAWS-SystemsManager-AutomationExecutionRole
を作成するため、以下の設定でCloudFormationスタックを作成します。
- スタック名:AWS-SystemsManager-AutomationExecutionRole-org
- テンプレートの指定:AWS-SystemsManager-AutomationExecutionRole (org).yml
- パラメータ
- AdminAccountId:管理アカウントのAWSアカウントID
- OrganizationID:管理アカウントのAWS Organizationsページで確認できる「組織 ID」
CloudFormationスタックを作成後、再度以下のコマンドを実行すると成功します。
aws ssm start-automation-execution \
--document-name "check-os" \
--parameters AutomationAssumeRole=arn:aws:iam::111111111111:role/AWS-SystemsManager-AutomationAdministrationRole,CheckHostname=check,CheckTimezone=check,CheckLocale=check,CheckUpdate=check,CheckNTP=check \
--target-parameter-name InstanceId \
--targets Key=tag:ssm-automation,Values=true \
--target-locations '[{
"Accounts": ["r-o2pk"],
"IncludeChildOrganizationUnits": true,
"Regions": ["ap-northeast-1"],
"ExecutionRoleName": "AWS-SystemsManager-AutomationExecutionRole"
}]'
実行後、以下のようにExecution IDが作成されます。
- 管理アカウント
- 親Execution ID:1つ
- 管理アカウントと各メンバーアカウントの実行結果をまとめたもの
- 子Execution ID:2つ以上
- 管理アカウント内の全EC2インスタンスの実行結果をまとめたもの
- 管理アカウント内の各EC2インスタンスごとの実行結果詳細
- 親Execution ID:1つ
- メンバーアカウント
- 子Execution ID:2つ以上
- 1つは、メンバーアカウント内の全EC2インスタンスの実行結果をまとめたもの
- 残りは、各EC2インスタンスごとの実行結果詳細
- 子Execution ID:2つ以上
これにより、管理アカウントのEC2インスタンスもAutomationの対象に含めることが可能になります。
参考