10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudFormationで複数EBSの個別設定とディスク初期化を自動化する

Last updated at Posted at 2025-12-09

はじめに

この記事では、CloudFormationを使って複数のEBSボリュームを個別設定で作成し、ディスク初期化まで自動化する方法を紹介します。

こんな方におすすめ

  • 大量のEBSを異なるIOPS/スループット設定で作成したい
  • WindowsインスタンスでEBSの自動初期化・ドライブレター割り当てを行いたい
  • CloudFormationのBlockDeviceMappingsの制約に悩んでいる

注意事項

  • 本記事はWindows Server環境を対象としています
  • 実際の本番環境での使用前には十分な検証を行ってください

背景・目的

  • 背景:複数EBSのタグ設定やディスク初期化が手動だと大変
  • 目的:CloudFormationで可能な限り自動化

課題

  1. BlockDeviceMappingsの制約(IOPS/Throughput/タグ設定不可)
  2. アタッチ順序の不確実性(Dドライブがディスク1とは限らない)
  3. 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
}

・初期化手順:

  1. GPTパーティション:2TB以上対応のため
  2. パーティション作成:ディスク全体を使用
  3. NTFSフォーマット:Windowsの標準ファイルシステム
  4. ドライブレター割り当て:デバイス名に対応した文字
⑥ 後処理
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ドライブ以降も同様に定義... ###

処理フロー

  1. CloudFormation実行 → EC2 + EBS作成
  2. UserData実行 → スクリプト + タスク作成
  3. 再起動 → タスクスケジューラ実行
  4. ディスク初期化 → 自動削除

ハマったポイント

  • アタッチ順序が不定
  • Userdataの直接実行が不安定
  • ボリュームラベルの文字化け

制約・今後の課題

  • EBS設定は1つずつ記述が必要
  • Cドライブ用のEBSは手動設定が必要
  • ボリュームラベルに全角を使えない
  • CFnの制約上、現時点ではこれがベストプラクティス

よくある問題(トラブルシュート)

  • ディスク初期化が実行されない
    →IAM 権限(ec2:DescribeInstances)を確認
    →初期化はディスク数・ボリュームサイズ・インスタンスタイプにより時間がかかるため、待機時間を長めに確保する
     
  • ドライブレターが期待と違う
    →ボリュームのデバイス名(例:xvdd, xvde)が正しいか確認

まとめ

開発のきっかけ

このCloudFormationテンプレートを作成することになったきっかけは、EBSボリュームを合計23個作成するという大規模な要件でした。

課題の複雑さ

単純にEBSを大量作成するだけでなく、以下の要件が重なり手動作業では現実的ではありませんでした。
・IOPSとスループットがデフォルト値ではない
・各EBSでIOPS・スループット値がバラバラ

自動化への挑戦

せっかく大量のEBSを作成するなら、ディスク初期化まで含めて完全自動化しようと考えました。

開発過程での苦労

検証を進める中で、想定以上に多くの課題に直面しました。
・EBSアタッチ順序の不確実性
・UserDataでの直接実行の不安定性
・デバイス名とドライブレターの紐付け問題

なかなか思うように動かないことも多々ありましたが、タスクスケジューラを活用した仕組みや、デバイス名ベースのドライブレター割り当てなど、試行錯誤を重ねて何とか形にすることができました。

成果と今後

結果として、EBS作成からディスク初期化まで一気に自動化でき、作業効率が大幅に向上しました。
まだまだ改善の余地はありますが、CloudFormationの制約を考慮すると、現時点では実用的なソリューションになったと考えています。今後も継続的に改善・改良を進めていきたいと思います。

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?