【AWS 】SSM Documentを使ったAMIのクロスアカウントコピー方法を解説【実践例付き】
はじめに
AWSでアカウント間にてAMIをコピーしたい場面は少なくありません。
しかし、手作業で行うとエラーのリスクが高まり、運用負荷も増えます。
この記事では、SSM Documentを活用してAMIを効率的にクロスアカウントコピーする方法を、実践例とともに解説します。
この記事を読むと次のことが分かります:
- SSM Documentを使ったAMIコピーの基本
- クロスアカウントに必要なIAMロールやポリシーの設定方法
- CloudFormationを用いた実装例
SSM Documentとは?
SSM Document (Systems Manager Document) は、AWS Systems Managerの機能の一部で、AWSリソースの管理や運用タスクを自動化するために使用されるJSONまたはYAML形式のドキュメントです。これにより、タスクの実行内容や設定を定義して、リソースに対する操作を簡単かつ標準化できます。
AWS Backupと比較したSSM Documentのメリット
AMIを自動で取得する方法として、AWS Backupを利用することは多いと思います。
ここでは、AWS Backupと比較した時、SSM Documentを使うメリットを以下にまとめました。
- 柔軟な実行タイミング
- インスタンス上で直接コマンドを使用してドキュメントを実行可能
- バッチファイルなどを使用して、特定の処理が完了した後に自動でAMI取得を実行するなど、AWS Backupでは難しい柔軟なタイミング制御が可能
- 細かなカスタマイズ
- ドキュメント内で複数のAPI操作を組み合わせることで、バックアップ時にタグ付けや追加処理を自動化
- 条件分岐やパラメータの設定により、実行内容を環境や状況に応じてカスタマイズ可能
- オンデマンドの実行
- AWS Management Console、AWS CLI、またはAWS SDKを使用して、必要なタイミングで即座に実行
- AWS Backupのようにスケジュールに依存しないオンデマンド実行が可能
- リアルタイムのフィードバック
- 実行ステータスやログがリアルタイムで確認できるため、問題発生時のトラブルシューティングが迅速
- 複数アカウントやリージョン対応の簡便さ
- クロスアカウントやクロスリージョンでのAMIコピーや処理が容易に設定可能
実践
前提
それでは早速実践に移ります。
今回は、例としてコピー元アカウントの東京リージョンからコピー先アカウントの大阪リージョンにAMIをバックアップ
するSSM Documentを用意します。
ターゲット準備として、コピー元のアカウントで以下を用意しておきます。
- EC2
- OS
- Windows2018(Windows系なら指定なし)
- EBS
- 暗号化なし
- IAM
- このあとの事前準備で作成した物をアタッチ
- SSM Agent
- 無ければインストール
- AWS CLI
- コマンド実行時に必要
- OS
事前準備
1.コピー先アカウントでクロスアカウント用のIAMロール作成
まずクロスアカウントのために、AMIコピー先のアカウントでIAMロールを用意します。
コンソール画面のIAMロールに移動し、ロール→ロール作成
信頼されたエンティティタイプで「AWS アカウント」を選択
AWS アカウントは、「別の AWS アカウント」を選択し、コピー元のアカウントIDを入力
許可ポリシー は、「AmazonEC2FullAccess」「AmazonSSMFullAccess」を付与し、
「SSMDocumentAssumeRole」という名前でIAMロールを作成。
作成後は、信頼関係の「信頼されたエンティティ」を以下に編集
信頼されたエンティティ
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[コピー元アカウントID]:root"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
2.コピー元アカウントのEC2用のIAMポリシーとロール作成・アタッチ
以下のIAMポリシーを作成します。
コンソール画面のIAMロールに移動し、ポリシー→ポリシー作成
iam_PassRole_ForAMIBackup
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:PassRole",
"ssm:StartAutomationExecution"
],
"Resource": [
"arn:aws:ssm:*:[コピー元アカウントID]:automation-definition/*:*",
"arn:aws:iam::[コピー元アカウントID]:role/*"
]
}
]
}
コンソール画面のIAMロールに移動し、ロール→ロール作成
信頼されたエンティティタイプで「AWS のサービス」を選択
ユースケースで「EC2」を選択
許可ポリシーで「AmazonSSMFullAccess」と作成した「iam_PassRole_ForAMIBackup」を付与し、任意の名前でIAMロールを作成。
上記IAMロールが作成できたら、今回使用するEC2にアタッチしてください。
3. 対象のインスタンスにタグ付け
コピー先アカウントはタグで判別するように作成しているため、以下のコピーしたいEC2に以下のタグを付与します。
Key | Value | 備考 |
---|---|---|
CopyAccountId | 111111111111 | コピー先のアカウントIDを記載してください |
CloudFormationでSSM Documentのデプロイを実行
以下のCloudFormationテンプレートを使って、SSM Documentを簡単にデプロイできます
※CopyAMICrossAccountPolicyの作成の部分のアカウントIDは置き換えてください。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
IAMPolicyCopyAMICrossAccountPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: "CopyAMICrossAccountPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "sts:AssumeRole"
# XXXXXXXXXXXはコピー先のアカウントIDを入れてください。
Resource:
- "arn:aws:iam::XXXXXXXXXXX:role/SSMDocumentAssumeRole"
MySSMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ssm.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchFullAccess
- arn:aws:iam::aws:policy/AmazonSSMFullAccess
- arn:aws:iam::aws:policy/AmazonEC2FullAccess
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- !Ref IAMPolicyCopyAMICrossAccountPolicy
Path: "/"
RoleName: AMIBackupRoleForSSM
SsmDocument:
Type: AWS::SSM::Document
Properties:
DocumentType: Automation
Name: AmiBackup
Content:
schemaVersion: "0.3"
description: "AMI Backup and Lotate"
assumeRole: !Sub arn:aws:iam::{{global:ACCOUNT_ID}}:role/${MySSMRole}
parameters:
InstanceId:
type: String
description: (Mandatory) Id of ec2 instance.
mainSteps:
#タグ取得
- name: getInstanceTag
action: aws:executeAwsApi
inputs:
Service: ec2
Api: DescribeTags
Filters:
- Name: resource-id
Values:
- "{{InstanceId}}"
- Name: key
Values:
- "CopyAccountId"
- "Name"
outputs:
- Name: CopyAccountId
Selector: $.Tags[0].Value
- Name: InstanceName
Selector: $.Tags[1].Value
#コピー先指定
- name: GetCopyTargetInfo
action: aws:executeScript
onFailure: Abort
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
CopyAccountId: "{{getInstanceTag.CopyAccountId}}"
region: "{{global:REGION}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Import-Module AWS.Tools.EC2
$json = $env:InputPayload | ConvertFrom-Json
$copyAccountId = $json.CopyAccountId
$fromRegion = $json.region
$toRegion = "ap-northeast-3"
return @{ CopyAccountId= $copyAccountId; FromRegion = $fromRegion; ToRegion = $toRegion }
outputs:
- Name: CopyAccountId
Selector: $.Payload.CopyAccountId
Type: String
- Name: FromRegion
Selector: $.Payload.FromRegion
Type: String
- Name: ToRegion
Selector: $.Payload.ToRegion
Type: String
#AMI作成(コピー元アカウント)
- name: createImage
action: aws:createImage
inputs:
InstanceId: "{{InstanceId}}"
ImageName: "{{getInstanceTag.InstanceName}}-{{global:DATE_TIME}}"
NoReboot: true
#AMIのステータスチェック(コピー元アカウント)
- name: checkAmiStatus
action: aws:waitForAwsResourceProperty
timeoutSeconds: 1800
onFailure: Abort
inputs:
Service: ec2
Api: DescribeImages
ImageIds:
- "{{createImage.ImageId}}"
PropertySelector: "$.Images[0].State"
DesiredValues:
- available
#AMIにタグを付与(コピー元アカウント)
- name: addTag
action: aws:createTags
maxAttempts: 3
onFailure: Abort
inputs:
ResourceType: EC2
ResourceIds:
- "{{createImage.ImageId}}"
Tags:
- Key: Name
Value: "{{getInstanceTag.InstanceName}}"
- Key: BackupIdentifier
Value: "CreatedBySSMAutomation"
- Key: DRTarget
Value: "True"
- Key: CopyAccountId
Value: "{{getInstanceTag.CopyAccountId}}"
# #AMIからEBSスナップショットIDを取得
- name: GetSnapshotIds
action: aws:executeScript
onFailure: Abort
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
ImageId: "{{createImage.ImageId}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Import-Module AWS.Tools.EC2
$json = $env:InputPayload | ConvertFrom-Json
$image = Get-EC2Image -ImageId $json.ImageId
$snapshots = @()
foreach ($device in $image.BlockDeviceMappings) {
if ($device.Ebs.SnapshotId -ne $null) {
$snapshots += $device.Ebs.SnapshotId
}
}
return @{ "SnapshotIds" = $snapshots }
outputs:
- Name: SnapshotIds
Selector: $.Payload.SnapshotIds
Type: StringList
# EBSスナップショットに共有許可を追加
- name: shareSnapshots
action: aws:executeScript
onFailure: Abort
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
SnapshotIds: "{{GetSnapshotIds.SnapshotIds}}"
CopyAccountId: "{{GetCopyTargetInfo.CopyAccountId}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Import-Module AWS.Tools.EC2
$json = $env:InputPayload | ConvertFrom-Json
foreach ($snapshotId in $json.SnapshotIds) {
Edit-EC2SnapshotAttribute -SnapshotId $snapshotId -Attribute createVolumePermission -OperationType add -UserIds $json.CopyAccountId
}
#コピー先アカウント大阪リージョンへのバックアップ
- name: copyAmi
action: aws:executeScript
onFailure: Abort
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
InstanceName: "{{getInstanceTag.InstanceName}}"
ImageId: "{{createImage.ImageId}}"
CopyAccountId: "{{GetCopyTargetInfo.CopyAccountId}}"
FromRegion: "{{GetCopyTargetInfo.FromRegion}}"
ToRegion: "{{GetCopyTargetInfo.ToRegion}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Install-Module AWS.Tools.SimpleSystemsManagement -Force
Install-Module AWS.Tools.Common -Force
Install-Module AWS.Tools.SecurityToken -Force
Import-Module AWS.Tools.EC2
Import-Module AWS.Tools.SimpleSystemsManagement
Import-Module AWS.Tools.Common
Import-Module AWS.Tools.SimpleSystemsManagement
$json = $env:InputPayload | ConvertFrom-Json
# コピー先アカウントのIAMロールを引き受け
$roleArn = "arn:aws:iam::" + $json.CopyAccountId + ":role/SSMDocumentAssumeRole"
$assumedRole = (Use-STSRole -RoleArn $roleArn -RoleSessionName "CheckAmiStatusSession").Credentials
Set-AWSCredentials -AccessKey $assumedRole.AccessKeyId -SecretKey $assumedRole.SecretAccessKey -SessionToken $assumedRole.SessionToken
# 大阪リージョンへAMIをコピー
$amiId = Copy-EC2Image -SourceRegion $json.FromRegion -SourceImageId $json.ImageId -Region $json.ToRegion -Name "$($json.InstanceName)"
return @{"OsakaAmiId"=$amiId}
outputs:
- Name: OsakaAmiId
Selector: $.Payload.OsakaAmiId
Type: String
#AMIのステータスチェック(コピー先アカウント)
- name: checkCopyAccountAmiStatus
action: aws:executeScript
onFailure: Abort
timeoutSeconds: 1800
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
OsakaAmiId: "{{copyAmi.OsakaAmiId}}"
InstanceName: "{{getInstanceTag.InstanceName}}"
CopyAccountId: "{{GetCopyTargetInfo.CopyAccountId}}"
ToRegion: "{{GetCopyTargetInfo.ToRegion}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Install-Module AWS.Tools.SimpleSystemsManagement -Force
Install-Module AWS.Tools.Common -Force
Install-Module AWS.Tools.SecurityToken -Force
Import-Module AWS.Tools.EC2
Import-Module AWS.Tools.SimpleSystemsManagement
Import-Module AWS.Tools.Common
Import-Module AWS.Tools.SimpleSystemsManagement
$json = $env:InputPayload | ConvertFrom-Json
# コピー先アカウントのIAMロールを引き受け
$roleArn = "arn:aws:iam::" + $json.CopyAccountId + ":role/SSMDocumentAssumeRole"
$assumedRole = (Use-STSRole -RoleArn $roleArn -RoleSessionName "CheckAmiStatusSession").Credentials
Set-AWSCredentials -AccessKey $assumedRole.AccessKeyId -SecretKey $assumedRole.SecretAccessKey -SessionToken $assumedRole.SessionToken
# 大阪リージョンのAMIステータスをチェック
$cnt = 0
do {
$OsakaAmiStatus = Get-EC2Image -Region $json.ToRegion -ImageId $json.OsakaAmiId
Write-Host @($cnt,$OsakaAmiStatus.State)
$cnt++
Start-Sleep -Seconds 60
}while($OsakaAmiStatus.State -ne "available")
#AMIにタグを付与(コピー先アカウント)
- name: addTagCopyAccount
action: aws:executeScript
onFailure: Abort
maxAttempts: 3
inputs:
Runtime: PowerShell Core 6.0
InputPayload:
OsakaAmiId: "{{copyAmi.OsakaAmiId}}"
InstanceName: "{{getInstanceTag.InstanceName}}"
CopyAccountId: "{{GetCopyTargetInfo.CopyAccountId}}"
ToRegion: "{{GetCopyTargetInfo.ToRegion}}"
Script: |
Install-Module AWS.Tools.EC2 -Force
Install-Module AWS.Tools.SimpleSystemsManagement -Force
Install-Module AWS.Tools.Common -Force
Install-Module AWS.Tools.SecurityToken -Force
Import-Module AWS.Tools.EC2
Import-Module AWS.Tools.SimpleSystemsManagement
Import-Module AWS.Tools.Common
Import-Module AWS.Tools.SimpleSystemsManagement
$json = $env:InputPayload | ConvertFrom-Json
# コピー先アカウントのIAMロールを引き受け
$roleArn = "arn:aws:iam::" + $json.CopyAccountId + ":role/SSMDocumentAssumeRole"
$assumedRole = (Use-STSRole -RoleArn $roleArn -RoleSessionName "AddTagOsakaSession").Credentials
Set-AWSCredentials -AccessKey $assumedRole.AccessKeyId -SecretKey $assumedRole.SecretAccessKey -SessionToken $assumedRole.SessionToken
# 大阪リージョンのAMIにタグを付与
$Tags = @( @{key="Name";value=$json.InstanceName},@{key="BackupIdentifier";value="CreatedBySSMAutomation"},@{key="CopyAccountId";value=$json.CopyAccountId} )
New-EC2Tag -Resource $json.OsakaAmiId -Tag $Tags -Region $json.ToRegion
各ステップの解説
ステップ 1: getInstanceTag (インスタンスのタグを取得)
- action: aws:executeAwsApi
- EC2 APIを実行
- 目的:
- InstanceIdで指定されたEC2インスタンスのタグを取得
- 以下を抽出
- CopyAccountId: コピー先アカウントID
- InstanceName: インスタンス名
ステップ 2: GetCopyTargetInfo (コピー先情報の設定)
- action: aws:executeScript
- PowerShellスクリプトでタグ情報取得
- 目的:
- CopyAccountId: コピー先アカウントIDを保持
- FromRegion: 現在のリージョンを保持
- ToRegion: 大阪リージョン(ap-northeast-3)を設定
ステップ 3: createImage (AMI作成)
- action: aws:createImage
- EC2インスタンスのAMIを作成
- 目的:
- 指定されたインスタンスの状態を保持するAMIを作成
ステップ 4: checkAmiStatus (AMI作成のステータス確認)
- action: aws:waitForAwsResourceProperty
- AMIの作成ステータスをチェック
- 目的:
- AMIが「available」になるまで待機
ステップ 5: addTag (AMIにタグ付与)
- action: aws:createTags
- AMIにタグを付与
- 目的:
- 作成したAMIにタグを付加する
- Name: インスタンス名
- BackupIdentifier: バックアップ識別子
- CopyAccountId: コピー先アカウントのID
- 作成したAMIにタグを付加する
ステップ 6: GetSnapshotIds (スナップショットIDを取得)
- action: aws:executeScript
- PowerShellスクリプトで、スナップショットIDを取得
- 目的:
- AMIに関連付けられたEBSスナップショットIDを収集
ステップ 7: shareSnapshots (スナップショットの共有設定)
- action: aws:executeScript
- PowerShellスクリプトで、スナップショットの共有設定
- 目的:
- スナップショットの「ボリューム作成権限」をコピー先アカウントに付与
ステップ 8: copyAmi (AMIを大阪リージョンにコピー)
- action: aws:executeScript
- PowerShellスクリプトで、AMIを大阪リージョンにコピー
- 目的:
- コピー先アカウントでIAMロールを引き受け
- 元のAMIを大阪リージョンにコピー
ステップ 9: checkCopyAccountAmiStatus (コピー後のAMIステータス確認)
- action: aws:executeScript
- PowerShellスクリプトで、コピー後のAMIステータス確認
- 目的:
- コピー先アカウントで大阪リージョンのAMIステータスを確認
- availableになるまでループ
ステップ 10: addTagCopyAccount (コピー後のAMIにタグ付与)
- action: aws:executeScript
- PowerShellスクリプトを実行
- 目的:
- コピー後のAMIに情報を付加するタグを設定
実践方法
実際に動作を確認するために今回は、以下のバッチファイルをEC2上に配置します。
インスタンスIDは自身の環境に置き換えてください。
aws ssm start-automation-execution ^
--document-name "AmiBackup" ^
--parameters "InstanceId=i-XXXXXXXXXXXXXXX"
配置したバッチファイルを実行するとドキュメントの実行が開始されます。
実行結果の確認は、マネジメントコンソール上のSystems Managerの「自動化」ページで以下のように結果を確認することができます。
Execution IDをクリックすることで更に詳細まで確認できます。
ステータスが「Success」になればコピー完了で、AMIがコピーされています。
最後に
今回は、コピー元アカウントの東京リージョンからコピー先アカウントの大阪リージョンにAMIをコピーしました。
実践で作成したCfnを一部改修することで、宛先のリージョンを変更したり、タグから宛先のリージョンを指定したりすることも可能です。
また、今回確認はしていないのですが、Linux環境でも同様のシェルスクリプトを用意すれば動作すると思うので確認できれば情報更新します。
以上参考になれば幸いです。