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