Automatically mount NVME volumes in AWS EC2 on Windows with Cloudformation and Powershell Userdata
Introduction
- attaching EBS volumes to Windows EC2 instances
- identifying them in the OS
- initializing them
- formatting them
- and assigning labels and drive letters.
Prerequisites
You should know something about Cloudformation and Powershell.
The AWS Image you use for your EC2 instance should be one provided by AWS and should have the following utility already deployed to C:\ProgramData\Amazon\Tools\ebsnvme-id.exe
AWS Cloudformation Step
I typically do not declare EBS Volumes together with the EC2 Instance declaration in Cloudformation but prefer to declare them as separate resources along with their own ebs attachment resources. Here is an example EC2 instance with three additional volume declarations on top of the root volume which is automatically mounted on C:\
WindowsServerInstance:
Type: AWS::EC2::Instance
Properties:
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 65
Encrypted: true
VolumeType: gp3
ImageId: !Ref WindowsImageId
KeyName: !Ref WindowsKeyPairName
InstanceType: !WindowsServerInstanceType
CreditSpecification:
CPUCredits: standard
IamInstanceProfile: !Ref WindowsServerInstanceProfile
SecurityGroupIds:
- !Ref ManagementSecurityGroup
- !Ref ApplicationSecurityGroup
SubnetId: !Ref WindowsServerSubnetId
Tags:
- Key: Name
Value: WindowsServerInstance
UserData:
Fn::Base64: |
<powershell>
mkdir C:\setup
Read-S3Object -BucketName example-bucket -Key /userdata/windows_bootstrap.ps1 -File C:\setup\bootstrap.ps1
. C:\setup\bootstrap.ps1
</powershell>
DataVolume:
Type: AWS::EC2::Volume
Properties:
Size: 100
VolumeType: gp3
Encrypted: true
AvailabilityZone: !GetAtt WindowsServerInstance.AvailabilityZone
Tags:
- Key: Name
Value: DataVolume
DataVolumeAttachment:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: !Ref WindowsServerInstance
VolumeId: !Ref DataVolume
Device: /dev/sdb
BackupVolume:
Type: AWS::EC2::Volume
Properties:
Size: 100
VolumeType: gp3
Encrypted: true
AvailabilityZone: !GetAtt WindowsServerInstance.AvailabilityZone
Tags:
- Key: Name
Value: BackupVolume
BackupVolumeAttachment:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: !Ref WindowsServerInstance
VolumeId: !Ref BackupVolume
Device: /dev/sdc
ScriptsVolume:
Type: AWS::EC2::Volume
Properties:
Size: 5
VolumeType: gp3
Encrypted: true
AvailabilityZone: !GetAtt WindowsServerInstance.AvailabilityZone
Tags:
- Key: Name
Value: ScriptsVolume
ScriptsVolumeAttachment:
Condition: DeployB2BResources
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: !Ref WindowsServerInstance
VolumeId: !Ref ScriptsVolume
Device: /dev/sdd
When this template builds, a windows instance will be available with 3 additional and un-initialized volumes attached.
The userdata property is configured to pull down a powershell script from S3 as permitted by the WindowsServerInstanceProfile. I've not bothered to explain how all that works. I assume if you are reading this, you already know how to allow an EC2 Instance access to objects in S3 via Instance Profiles and Policies.
Userdata Script
The userdata script below, declares a function and calls the function once for each of the volume attachments.
### Function to format a disk based on the disk number, label and drive letter.
function setup_disk {
param (
$DiskNumber,
$Label,
$DriveLetter
)
Initialize-Disk "$DiskNumber" -PartitionStyle MBR
New-Partition -DiskNumber "$DiskNumber" -DriveLetter "$DriveLetter" -UseMaximumSize
Format-Volume -DriveLetter "$DriveLetter" -FileSystem NTFS -NewFileSystemLabel "$Label" -Confirm:$false
}
# Create a hashtable of disk numbers and block device mappings
$hash = $null
$hash = @{}
get-disk | where partitionstyle -eq 'raw' | where-object IsSystem -eq $False| select Number | foreach-Object {
$num = $_.Number
$blk = (c:\ProgramData\Amazon\Tools\ebsnvme-id.exe $num | Select-String -Pattern 'Device Name: .*$'| ConvertFrom-String -Delimiter ':' | Select P2).P2
$blk = $blk -replace '^......'
$hash.add($blk, $num)
}
# Set up each of the volume attachments
setup_disk $hash.sdb "Data" "D"
setup_disk $hash.sdc "Backup" "E"
setup_disk $hash.sdd "Scripts" "I"
Explanation
The get-disk command returns a list of all the disks attached to the system. We can use the get-disk command to iterate through each attached disk that is not a system disk (exclude C:\) and then call the AWS Provided ebsnvme-id.exe tool to find out which device name it has and add it to a hashmap called $hash. This just maps Disk Number to Volume Attachment Device Name. Unfortunately Windows does not always enumerate disks in the same order so we have to build this map correctly. As a general rule it is never a good idea to rely on the order of things in computers.
Seeing as though our Cloudformation VolumeAttachment resources declare these device names, we have the mapping we need to proceed.
PS C:\setup> $hash Name Value ---- ----- sdb 2 sdc 1 sdd 3
Finally, our little script, calls the setup_disk function with the value of the hash for each volume we want and the parameters for Label and Drive Letter.
Comments