はじめに
この記事では、CloudFormationを使って複数のEBSボリュームを個別設定で作成し、ディスク初期化まで自動化する方法を紹介します。
こんな方におすすめ:
- 大量のEBSを異なるIOPS/スループット設定で作成したい
- WindowsインスタンスでEBSの自動初期化・ドライブレター割り当てを行いたい
- CloudFormationのBlockDeviceMappingsの制約に悩んでいる
注意事項:
- 本記事はWindows Server環境を対象としています
- 実際の本番環境での使用前には十分な検証を行ってください
背景・目的
- 背景:複数EBSのタグ設定やディスク初期化が手動だと大変
- 目的:CloudFormationで可能な限り自動化
課題
- BlockDeviceMappingsの制約(IOPS/Throughput/タグ設定不可)
- アタッチ順序の不確実性(Dドライブがディスク1とは限らない)
- Userdataでの直接実行の不安定性
解決策
CloudFormation側の工夫
- AWS::EC2::Volume + VolumeAttachmentで個別設定
UserData側の工夫
- タスクスケジューラでディスク初期化スクリプトを実行
- ドライブレターはEBSのデバイス名から取得
- タスクスケジューラは初回起動後に再起動して実行
前提条件
- EC2のIAMロールに
ec2:DescribeInstances権限が必要
→EBSのデバイス名とボリュームIDを取得するため - EBSのデバイス名はドライブレターに合わせる(例:Dドライブ用EBS → xvdd)
→EBSに適切なドライブレターを割り当てるため
PowerShellスクリプトの詳細
ディスク初期化スクリプトファイルを作成
- この部分では、EBSボリュームを自動的に初期化・フォーマットするPowerShellスクリプトを作成しています。
スクリプトファイル作成部分
## スクリプトファイルのパス定義
$scriptPath = "C:\Initialize-EBSDisks.ps1"
## ヒアドキュメント形式でスクリプト内容を定義
$scriptContent = @'
### 実際のスクリプト内容がここに入る ###
'@
## UTF-8エンコードでファイル作成
[System.IO.File]::WriteAllText($scriptPath, $scriptContent, [System.Text.Encoding]::UTF8)
「@' ... '@」 で囲むことで、文字列内の変数展開を防ぎつつ複数行をそのまま書けます。
スクリプト内容の詳細解説
① IMDSv2トークン取得
# IMDSv2トークン取得(セキュリティ強化)
$token = Invoke-RestMethod -Method PUT -Uri http://169.254.169.254/latest/api/token -Headers @{"X-aws-ec2-metadata-token-ttl-seconds"="21600"}
今回は「IMDSv2」を必須にしているため、こちらの処理が必要となります。
「IMDSv1」も許可する場合は、本処理は不要です。
② インスタンス情報取得
# 自分のインスタンスID取得
$instanceId = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/instance-id -Headers @{"X-aws-ec2-metadata-token"=$token}
# AWS PowerShellモジュール読み込み
Import-Module AWSPowerShell.NetCore
# インスタンス詳細情報取得
$instance = Get-EC2Instance -InstanceId $instanceId
$rootDeviceName = $instance.Instances.RootDeviceName
・メタデータサービス:自分のインスタンスIDを取得
・AWS CLI:PowerShellモジュールでEC2詳細情報を取得
・ルートデバイス:初期化対象から除外するため取得
③ 対象ディスク特定
# ルート以外のデバイス情報取得
$dataDevices = $instance.Instances.BlockDeviceMappings |
Where-Object { $_.DeviceName -ne $rootDeviceName }
# 未初期化ディスク取得
$disks = Get-Disk | Where-Object { $_.PartitionStyle -eq 'RAW' }
・BlockDeviceMappings:アタッチされた全EBSボリューム情報
・RAWディスク:未初期化(パーティション未作成)のディスクのみ対象
④ ディスク初期化ループ
foreach ($device in $dataDevices) {
## デバイス名の末尾からドライブレター抽出(例:xvdd → D)
$driveLetter = $device.DeviceName[-1].ToString().ToUpper()
## VolumeIdからディスク特定
$volId = $device.Ebs.VolumeId
$normalizedVolId = $volId -replace '-'
$matchDisk = $disks | Where-Object {
($_.AdapterSerialNumber -replace '-', '').ToLower() -eq $normalizedVolId.ToLower()
} | Select-Object -First 1
### ディスク初期化実行 ###
}
・ドライブレター自動抽出:
xvdd → D
xvde → E
xvdf → F
etc...
・ディスク特定方法:
EBSのVolumeId(vol-***)とディスクのAdapterSerialNumberを照合
ハイフンを除去して正規化してから比較
⑤ ディスク初期化実行
if ($matchDisk) {
# GPTパーティション作成
Initialize-Disk -Number $matchDisk.Number -PartitionStyle GPT -Confirm:$false
# 最大サイズでパーティション作成(ドライブレター未割り当て)
$partition = New-Partition -DiskNumber $matchDisk.Number -UseMaximumSize -AssignDriveLetter:$false
# NTFSフォーマット
Format-Volume -Partition $partition -FileSystem NTFS -NewFileSystemLabel "Volume" -Confirm:$false
# 指定ドライブレター割り当て
Set-Partition -DiskNumber $matchDisk.Number -PartitionNumber $partition.PartitionNumber -NewDriveLetter $driveLetter
}
・初期化手順:
- GPTパーティション:2TB以上対応のため
- パーティション作成:ディスク全体を使用
- NTFSフォーマット:Windowsの標準ファイルシステム
- ドライブレター割り当て:デバイス名に対応した文字
⑥ 後処理
Start-Sleep -Seconds 120
# タスクスケジューラから削除
Unregister-ScheduledTask -TaskName "InitializeEBSDisks" -Confirm:$false
# スクリプトファイル自体を削除
Remove-Item -Path $PSCommandPath -Force
・待機時間:ディスク初期化完了を確実にするため
・自動削除:実行後にタスクとスクリプトをクリーンアップ
ディスク初期化スクリプト実行用タスクスケジューラ作成
- この部分では、ディスク初期化スクリプトを確実に実行するためのタスクスケジューラを設定しています。
タスクスケジューラ作成の全体構成
# 3. ディスク初期化スクリプト実行用タスクスケジューラ作成
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File $scriptPath"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "InitializeEBSDisks" -Force
各コンポーネントの詳細解説
① アクション設定(実行内容)
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File $scriptPath"
パラメータ詳細
・Execute: PowerShell.exe - PowerShellを実行
・Argument:-ExecutionPolicy Bypass -File C:\Initialize-EBSDisks.ps1
→ -ExecutionPolicy Bypass:スクリプト実行ポリシーを無視
→ -File $scriptPath:作成したスクリプトファイルを実行
なぜBypassが必要?
・Windowsの既定設定ではPowerShellスクリプト実行が制限されている
・Bypassにより一時的に制限を回避してスクリプト実行
② トリガー設定(実行タイミング)
$trigger = New-ScheduledTaskTrigger -AtStartup
実行タイミング
・AtStartup:システム起動時に実行
・ユーザーログイン不要で自動実行
なぜ起動時実行?
・UserData実行時点ではEBSアタッチが未完了の可能性
・Windows起動完了後なら確実にEBSが認識済み
③ プリンシパル設定(実行権限)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
権限設定詳細
・UserId:SYSTEM - 最高権限のシステムアカウント
・LogonType:ServiceAccount - サービスとして実行
・RunLevel:Highest - 管理者権限で実行
なぜSYSTEM権限?
・ディスク初期化には管理者権限が必要
・ユーザーログイン前でも実行可能
・セキュリティ制限を受けない
④ タスク登録
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "InitializeEBSDisks" -Force
登録パラメータ
・Action:実行内容 (①の値)
・Trigger:実行タイミング (②の値)
・Principal: 実行権限 (③の値)
・TaskName:InitializeEBSDisks (タスク名)
・Force:既存タスクがあれば上書き
テンプレート全体
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 Instance
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Network Configuration
Parameters:
- VPC
- Subnet
- PrivateIpAddress
- Label:
default: EC2 Configuration
Parameters:
- InstanceType
- WindowsLatestAmi
- EC2KeyName
- EC2SecurityGroup
- RootVolumeType
- RootVolumeSize
- Label:
default: Add EBS Configuration
Parameters:
- DVolumeIops
- DVolumeThroughput
- DVolumeType
- DVolumeSize
### Eドライブ以降も同様に定義... ###
- Label:
default: EC2Tag Configuration
Parameters:
- EC2NameTag
- EC2SystemTag
- Label:
default: IAM Role
Parameters:
- EC2InstanceProfileName
Parameters:
#ネットワーク
##VPC ID
VPC:
Type: AWS::EC2::VPC::Id
Default: vpc-***
Description: VPC ID
##サブネットID
Subnet:
Type: AWS::EC2::Subnet::Id
Default: subnet-***
Description: subnet ID
##プライベートIP
PrivateIpAddress:
Type: String
Default: 10.20.5.20
Description: Server Private IpAddress.
#EC2設定
##インスタンスタイプ
InstanceType:
Type: String
Default: t3.medium
Description: EC2 instance type
##AMI
WindowsLatestAmi:
Type : AWS::SSM::Parameter::Value<String>
Default: /aws/service/ami-windows-latest/Windows_Server-2025-Japanese-Full-Base
Description: EC2 AMI
##キーペア
EC2KeyName:
Type: String
Default: test_win
Description: The EC2 Key Pair to allow SSH access to the instance
##SG
EC2SecurityGroup:
Type: List<AWS::EC2::SecurityGroup::Id>
Default: sg-***
Description: The security group ID
##ルートEBS
RootVolumeType:
Type: String
Default: gp3
Description: The type of the EBS volume
RootVolumeSize:
Type: Number
Default: 80
Description: The size of the EBS volume in GB
#追加EBS設定
## D
DVolumeIops:
Type: Number
Default: 3600 ###
Description: The IOPS of the EBS volume
DVolumeThroughput:
Type: Number
Default: 250 ###
Description: The throughput of the EBS volume
DVolumeType:
Type: String
Default: gp3 ###
Description: The type of the EBS volume
DVolumeSize:
Type: Number
Default: 600 ###
Description: The size of the EBS volume in GB
### Eドライブ以降も同様に定義... ###
#タグ
EC2NameTag:
Type: String
Default: TestWin2025
Description: EC2 Name Tag
EC2SystemTag:
Type: String
Default: TEST
Description: System Tag for EC2 Instance.
#インスタンスロール(インスタンスプロファイル)
EC2InstanceProfileName:
Type: String
Default: EC2InstanceRole
Description: EC2 Instance Profile Name.
Resources:
# EC2作成
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref EC2KeyName
SubnetId: !Ref Subnet
ImageId: !Ref WindowsLatestAmi
MetadataOptions:
HttpTokens: required # ← IMDSv2を必須にする
HttpEndpoint: enabled # ← Metadata Service有効化
SecurityGroupIds: !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfileName
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: !Ref RootVolumeType
VolumeSize: !Ref RootVolumeSize
DeleteOnTermination: true
Encrypted: true
PrivateIpAddress: !Ref PrivateIpAddress
EbsOptimized: true
DisableApiTermination: true
PropagateTagsToVolumeOnCreation: true
Tags:
- Key: Name
Value: !Ref EC2NameTag
- Key: System
Value: !Ref EC2SystemTag
UserData:
Fn::Base64: !Sub |
<powershell>
# 1. タイムゾーンを東京(JST)に変更
Set-TimeZone -Id "Tokyo Standard Time"
# 2. ディスク初期化スクリプトファイルを作成
## スクリプトファイルのパス定義
$scriptPath = "C:\Initialize-EBSDisks.ps1"
## ヒアドキュメント形式でスクリプト内容を定義
$scriptContent = @'
## IMDSv2トークン取得(セキュリティ強化)
$token = Invoke-RestMethod -Method PUT -Uri http://169.254.169.254/latest/api/token -Headers @{"X-aws-ec2-metadata-token-ttl-seconds"="21600"}
## 自分のインスタンスID取得
$instanceId = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/instance-id -Headers @{"X-aws-ec2-metadata-token"=$token}
## AWS PowerShellモジュール読み込み
Import-Module AWSPowerShell.NetCore
## インスタンス詳細情報取得
$instance = Get-EC2Instance -InstanceId $instanceId
$rootDeviceName = $instance.Instances.RootDeviceName
## ルート以外のデバイス情報取得
$dataDevices = $instance.Instances.BlockDeviceMappings |
Where-Object { $_.DeviceName -ne $rootDeviceName }
## 未初期化ディスク取得
$disks = Get-Disk | Where-Object { $_.PartitionStyle -eq 'RAW' }
foreach ($device in $dataDevices) {
## デバイス名の末尾からドライブレター抽出(例:xvdd → D)
$driveLetter = $device.DeviceName[-1].ToString().ToUpper()
## VolumeIdからディスク特定
$volId = $device.Ebs.VolumeId
$normalizedVolId = $volId -replace '-'
$matchDisk = $disks | Where-Object {
($_.AdapterSerialNumber -replace '-', '').ToLower() -eq $normalizedVolId.ToLower()
} | Select-Object -First 1
if ($matchDisk) {
## GPTパーティション作成
Initialize-Disk -Number $matchDisk.Number -PartitionStyle GPT -Confirm:$false
## 最大サイズでパーティション作成(ドライブレター未割り当て)
$partition = New-Partition -DiskNumber $matchDisk.Number -UseMaximumSize -AssignDriveLetter:$false
## NTFSフォーマット
Format-Volume -Partition $partition -FileSystem NTFS -NewFileSystemLabel "Volume" -Confirm:$false
## 指定ドライブレター割り当て
Set-Partition -DiskNumber $matchDisk.Number -PartitionNumber $partition.PartitionNumber -NewDriveLetter $driveLetter
}
}
Start-Sleep -Seconds 120
## タスクスケジューラから削除
Unregister-ScheduledTask -TaskName "InitializeEBSDisks" -Confirm:$false
## スクリプトファイル自体を削除
Remove-Item -Path $PSCommandPath -Force
'@
## UTF-8エンコードでファイル作成
[System.IO.File]::WriteAllText($scriptPath, $scriptContent, [System.Text.Encoding]::UTF8)
# 3. ディスク初期化スクリプト実行用タスクスケジューラ作成
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File $scriptPath"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "InitializeEBSDisks" -Force
Start-Sleep -Seconds 60
# 4. ホスト名変更
Rename-Computer -NewName ${EC2NameTag} -Force
# 最後に再起動
# wmic os where Primary='TRUE' reboot
shutdown /r /t 10 /f
</powershell>
# ------------------------------------------------------------#
# Add EBS
# ------------------------------------------------------------#
#Dドライブ
DVolume:
Type: AWS::EC2::Volume
Properties:
AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
Size: !Ref DVolumeSize
VolumeType: !Ref DVolumeType
Iops: !Ref DVolumeIops
Throughput: !Ref DVolumeThroughput
Encrypted: true
Tags:
- Key: Name
Value: !Sub "${EC2NameTag}-D"
- Key: System
Value: !Ref EC2SystemTag
DVolumeAttachment:
Type: AWS::EC2::VolumeAttachment
Properties:
Device: xvdd
InstanceId: !Ref EC2Instance
VolumeId: !Ref DVolume
### Eドライブ以降も同様に定義... ###
処理フロー
- CloudFormation実行 → EC2 + EBS作成
- UserData実行 → スクリプト + タスク作成
- 再起動 → タスクスケジューラ実行
- ディスク初期化 → 自動削除
ハマったポイント
- アタッチ順序が不定
- Userdataの直接実行が不安定
- ボリュームラベルの文字化け
制約・今後の課題
- EBS設定は1つずつ記述が必要
- Cドライブ用のEBSは手動設定が必要
- ボリュームラベルに全角を使えない
- CFnの制約上、現時点ではこれがベストプラクティス
よくある問題(トラブルシュート)
- ディスク初期化が実行されない
→IAM 権限(ec2:DescribeInstances)を確認
→初期化はディスク数・ボリュームサイズ・インスタンスタイプにより時間がかかるため、待機時間を長めに確保する
- ドライブレターが期待と違う
→ボリュームのデバイス名(例:xvdd, xvde)が正しいか確認
まとめ
開発のきっかけ
このCloudFormationテンプレートを作成することになったきっかけは、EBSボリュームを合計23個作成するという大規模な要件でした。
課題の複雑さ
単純にEBSを大量作成するだけでなく、以下の要件が重なり手動作業では現実的ではありませんでした。
・IOPSとスループットがデフォルト値ではない
・各EBSでIOPS・スループット値がバラバラ
自動化への挑戦
せっかく大量のEBSを作成するなら、ディスク初期化まで含めて完全自動化しようと考えました。
開発過程での苦労
検証を進める中で、想定以上に多くの課題に直面しました。
・EBSアタッチ順序の不確実性
・UserDataでの直接実行の不安定性
・デバイス名とドライブレターの紐付け問題
なかなか思うように動かないことも多々ありましたが、タスクスケジューラを活用した仕組みや、デバイス名ベースのドライブレター割り当てなど、試行錯誤を重ねて何とか形にすることができました。
成果と今後
結果として、EBS作成からディスク初期化まで一気に自動化でき、作業効率が大幅に向上しました。
まだまだ改善の余地はありますが、CloudFormationの制約を考慮すると、現時点では実用的なソリューションになったと考えています。今後も継続的に改善・改良を進めていきたいと思います。