I’ve been doing a lot of AWS, EC2, Cloud Formation, Chef, and Windows lately. In particular, we’re building a Continuous Delivery pipeline that launches groups of machines to build up application stacks.

I have a situation where I need to build Microsoft SQL Server machines in the Amazon EC2 cloud environment. In particular, the operations team wants the machines to have 3 drives:

  • C: Operating System
  • (no D:)
  • E: SQL – SQL server installation and data
  • F: Filestream – SQL server file repository

We’ve built an automation framework for launching machines that makes heavy use of AWS Cloud Formation. This allows us to define the entire configuration of the new machines as well as specifying data and commands the machine will use when it boots up.

Originally, I just relied on Cloud Formation (CFN) to provision the drives and mount them properly. The problem is that Windows 2012 and 2008 mount drives slightly differently and both put drives as C:, D:, E: instead of C:, (skip), E:, F:. Supposedly, you can use Ec2Config service to set these, but I couldn’t find a way to feed the DriveLetter.xml file to it before it ran and mounted things incorrectly.

Basically, I just want to mount Disk 1:Partition 1 as E: and Disk 2:Paritition 1 as F:. This was way more of a pain to figure out than I expected. A lot of things in Windows report drives as “Volumes” (which makes sense) with long GUIDs for the volume ID. I couldn’t find a way to track this back to the drive and partition it represented. So, I abandoned volumes and went directly to partitions.

My solution was to use Windows primitives for disk and partition to manipulate the drives when the machine launches. This was additionally challenging because 2008 doesn’t have all the same capabilities as Win 2012. In particular, I’d love to be able to use Get-Paritition, Add-PartitionAccessPath, and Remove-PartitionAccessPath, but they’re only available in 2012. I also looked at MOUNTVOL and Get-WmiObject with Win32_DiskPartition, Win32_LogicalDiskToPartition. I eventually settled on using DISKPART.

To get started, I specify the drive configuration somewhere in my CFN template:

Resources > LaunchConfig > Properties

"BlockDeviceMappings" : [
{ "DeviceName" : "/dev/sda1", "Ebs" : { "VolumeSize" : "60" } },
{ "DeviceName" : "xvdc", "Ebs" : { "VolumeSize" : "50" } },
{ "DeviceName" : "xvdd", "Ebs" : { "VolumeSize" : "20" } }
],

This sets up Disk 0 as /dev/sda1, Disk 1 as xvdc (50 GiB), and Disk 2 as xvdd (20 Gib).

Next, I create the script files I need on the newly launched machine with another CFN entry. Note that I work backwards with the drives, shifting Drive 2 (probably E: during boot) up to F:, then moving back down to Drive 1 (probably D:) and shifting it up to E: after the drive letter is freed up.

Resources > LaunchConfig > Metadata > AWS::CloundFormation::Init > (MyConfig) > files


"c:\\cfn\\hooks.d\\drives.diskpart.txt" : {
"content": { "Fn::Join" : ["", [
"rem DISKPART script to mount drives on E: and F:\n",
"list disk\n",
"list volume\n",
"rem Make sure second disk is on F:\n",
"select disk 2\n",
"select partition 1\n",
"assign letter=f\n",
"list volume\n",
"rem Make sure first disk is on E:\n",
"select disk 1\n",
"select partition 1\n",
"assign letter=e\n",
"list volume\n"
]]}
},

Again, I specifically use “disk” and “partition” because they are the most predictable. I tried “select volume 2” but the volume numbers aren’t always the same between machines. Windows 2008 R2 and Windows 2012 images in Amazon behave a little differently in the way they mount drives when they spin up, especially if the machine has been AMI’d more than once. Also, the Windows 2012 image has an extra system volume before C: which throws off the volume numbers.

The next script uses PowerShell to run DISKPART and then set the labels for our newly mounted drives.


"c:\\cfn\\hooks.d\\renamedrives.ps1" : {
"content": { "Fn::Join" : ["", [
"# Mount and rename drives properly\n",
"diskpart c:\\cfn\\hooks.d\\drives.diskpart.txt\n",
"$fdrive = Get-WmiObject -Class win32_volume -Filter \"DriveLetter = 'F:'\"\n",
"$fdrive.Label = \"Filestream\"\n",
"$fdrive.put()\n",
"$edrive = Get-WmiObject -Class win32_volume -Filter \"DriveLetter = 'E:'\"\n",
"$edrive.Label = \"SQL\"\n",
"$edrive.put()\n"
]]}
},

Finally, we have the “command” section that actually executes the PowerShell script when the machine boots.

Resources > LaunchConfig > Metadata > AWS::CloundFormation::Init > (MyConfig) > commands


"1.1-rename_drives" :{
"command" : "powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -File c:\\cfn\\hooks.d\\renamedrives.ps1",
"waitAfterCompletion" : "0"
},

The result is the machine will launch and mount the drives in whatever order it wants. When cfn-init spins up, it runs ‘1.1-rename-drives” and puts the drives where they need to be. In my particular situation, I run subsequent commands that set the name of the machine and reboot which provides insurance that there isn’t some process that died being coupled to a drive letter that is no longer there.

The added bonus of all this is that I can capture the machine as an AMI after I install SQL Server (which takes 60-90 min), then launch the AMI with a very similar configuration and it will re-map the drives to the correct locations.

Leave a comment

Your email address will not be published. Required fields are marked *

X