使用Terraform、Chef或Powershell编程方式设置EBS卷Windows驱动器字母

7
我正在使用terraform和chef创建多个aws ebs卷并将它们附加到EC2实例。问题是我想能够为每个ebs卷分配一个特定的Windows驱动器号。问题在于,当EC2实例被实例化时,Windows只会给它顺序驱动器号(D、E、F等)。
一些驱动器的大小相同,因此我不能基于驱动器大小重命名。有人知道如何在terraform或chef中实现这一点吗?我的谷歌搜索没有找到任何结果。
肯定其他人也遇到过这个问题吧?
我确实看到了使用EC2Config Windows GUI来设置它们的参考,但整个重点是自动化这个过程,因为最终我希望使用chef安装SQL Server,并且期望某些数据放在特定的驱动器号上。
这似乎可行-尽管我确实想知道是否有更简单的方法。
function Convert-SCSITargetIdToDeviceName
{
param([int]$SCSITargetId)
If ($SCSITargetId -eq 0) {
    return "/dev/sda1"
}
$deviceName = "xvd"
If ($SCSITargetId -gt 25) {
    $deviceName += [char](0x60 + [int]($SCSITargetId / 26))
}
$deviceName += [char](0x61 + $SCSITargetId % 26)
return $deviceName
}

Get-WmiObject -Class Win32_DiskDrive | ForEach-Object {
$DiskDrive = $_
$Volumes = Get-WmiObject -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='$($DiskDrive.DeviceID)'} WHERE AssocClass=Win32_DiskDriveToDiskPartition" | ForEach-Object {
    $DiskPartition = $_
    Get-WmiObject -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='$($DiskPartition.DeviceID)'} WHERE AssocClass=Win32_LogicalDiskToPartition"
}
If ($DiskDrive.PNPDeviceID -like "*PROD_PVDISK*") {
    $BlockDeviceName = Convert-SCSITargetIdToDeviceName($DiskDrive.SCSITargetId)
    If ($BlockDeviceName -eq "xvdf") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="D:"; Label="SQL Data"} };
    If ($BlockDeviceName -eq "xvdg") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="L:"; Label="SQL Logs"} };
    If ($BlockDeviceName -eq "xvdh") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="R:"; Label="Report Data"} };
    If ($BlockDeviceName -eq "xvdi") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="T:"; Label="Temp DB"} };
    If ($BlockDeviceName -eq "xvdj") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="M:"; Label="MSDTC"} };
    If ($BlockDeviceName -eq "xvdk") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="B:"; Label="Backups"} };
} ElseIf ($DiskDrive.PNPDeviceID -like "*PROD_AMAZON_EC2_NVME*") {
    $BlockDeviceName = Get-EC2InstanceMetadata "meta-data/block-device-mapping/ephemeral$($DiskDrive.SCSIPort - 2)"
    If ($BlockDeviceName -eq "xvdf") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="D:"; Label="SQL Data"} };
    If ($BlockDeviceName -eq "xvdg") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="L:"; Label="SQL Logs"} };
    If ($BlockDeviceName -eq "xvdh") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="R:"; Label="Report Data"} };
    If ($BlockDeviceName -eq "xvdi") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="T:"; Label="Temp DB"} };
    If ($BlockDeviceName -eq "xvdj") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="M:"; Label="MSDTC"} };
    If ($BlockDeviceName -eq "xvdk") { $drive = gwmi win32_volume -Filter "DriveLetter = '$($Volumes.DeviceID)'"; Set-WmiInstance -input $drive -Arguments @{DriveLetter="B:"; Label="Backups"} };
} Else {
    write-host "Couldn't find disks";
}
}

1
{btsdaf} - Martin Atkins
{btsdaf} - coderanger
所以主题是terraform,但我也提到了chef和powershell,因为我真的不确定最好的方法是什么。是使用terraform(现在我认为可能不是)-它可能需要powershell和/或chef。 - Brad
{btsdaf} - Brad
现在有一个工作示例,尽管我仍然在想这是否是最好的方法。它似乎相当复杂。 - Brad
@Brad:你可能会发现将磁盘映射到Windows实例上的卷有用,以理解Disk/Volume之间的区别。我们在AWS托管的SQL服务器通过使用Remove-PartitionAccessPath -AccessPath "$($WrongDriveLetter):"取消挂载来重新分配驱动器号,并使用Set-Partition -DiskNumber $DiskNumber -PartitionNumber $Partition+1 -NewDriveLetter $NewDriveLetter重新挂载它们。请注意,Win32_DiskPartition编号是基于0的数字,但Set-Partition期望基于1的数字。 - AlwaysLearning
5个回答

3
我需要一个具有4个相同大小的驱动器的Windows Server 2016,但我不在意哪个块设备成为哪个驱动器盘符。以下是我采取的步骤(使用Packer):
首先,在模板的构建器区域中添加所需的块设备(在我的情况下-在launch_block_device_mapping下添加4个条目)。然后,在供应程序列表中运行以下命令:
  1. initialize the disks using the script available on any Windows 2016 Amazon instance; this will bring every disk online, add a partion to it, extend the partition to maximum possible size, format it and assign a Windows drive letter to it.

    {
        "type": "powershell",        
        "inline": [
            "C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Scripts\\InitializeDisks.ps1"        
        ]
    }
    

    Notes:

    If you add the '-Schedule' parameter, the disks will not be initialized at this point, as this option will only add the script to a task scheduled to run one time at the next boot of the instance (afterwards it's de-activated).

    The drive letters are assigned in alphabetical order, starting with D (because C is reserved for the root drive).

    The order in which volumes are attached to an instance is not related to the block device name and will not have a 1-on-1 correspondance (xvdb will not become the D:\ drive, xvdc will not become E:\, etc.)

  2. Assign the label you desire to each drive letter of the already initialized disks.

    {
        "type": "powershell",
        "inline": [
            "write-output \"Label partitions after initializing disks\"",
            "label C: \"OS\"",
            "label D: \"Programs\"",
            "label E: \"Data\"",
            "label F: \"Backup\"",
            ...
        ]
    }
    

    Note: Another possible option would be to add the labels directly in the DriveLetterMapping.json file (available on any Windows 2016 Amazon AMI) before runnning the disks initialization script (I could not make this work).

  3. After you add any other provisioners you might need (e.g. activate Windows components, install applications or check for Windows updates), as the last entry in the provisioners list make sure the instance initialization and SysPrep scripts are added

    {
        "type": "powershell",
        "inline": [
            "C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/InitializeInstance.ps1 -Schedule",
            "C:/ProgramData/Amazon/EC2-Windows/Launch/Scripts/SysprepInstance.ps1 -NoShutdown"
        ]
    }
    

    Note: This last step is specific to EC2Launch and applies from Windows 2016 onwards. For older versions (like Windows 2012), the syntax differs and it's based on EC2Config.

获取此配置的AMI后,从其启动的任何实例的驱动器字母都应如所需。

如果驱动器字母及其标签未按预期映射,则还可以尝试通过使用实例的用户数据强制重新标记驱动器。在启动之前,可以轻松传递PowerShell脚本作为纯文本;以下仅是一个可能的示例:

<powershell>
write-output "Force re-map of drive letters based on labels, after disk initialization"
# remove drive letters, but keep labels
Get-Volume -Drive D | Get-Partition | Remove-PartitionAccessPath -accesspath "D`:\"
Get-Volume -Drive E | Get-Partition | Remove-PartitionAccessPath -accesspath "E`:\"
Get-Volume -Drive F | Get-Partition | Remove-PartitionAccessPath -accesspath "F`:\"
# add drive letters based on labels
get-volume | where filesystemlabel -match "Programs" | Get-Partition | Set-Partition -NewDriveLetter D
get-volume | where filesystemlabel -match "Data" | Get-Partition | Set-Partition -NewDriveLetter E
get-volume | where filesystemlabel -match "Backup" | Get-Partition | Set-Partition -NewDriveLetter F
</powershell>

2
如果您考虑此链接中的表格:https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-windows-volumes.html 您会发现在EBS上,第一行是:
Bus Number 0, Target ID 0, LUN 0 /dev/sda1
Bus Number 0, Target ID 1, LUN 0 xvdb

EC2会为您始终设置磁盘0 (/dev/sda1) 作为C:

因此,当您运行“New-Partition -DiskNumber 1 -UseMaximumSize -IsActive -AssignDriveLetter”时,您将会获得D:驱动器。

因此,如果您使用以下构建器中的卷来使用Packer提供AMI镜像(在此示例中只有两个,但您可以使用任意多个):

        "launch_block_device_mappings": [{
        "device_name": "/dev/sda1",
        "volume_size": 30,
        "volume_type": "gp2",
        "delete_on_termination": true
    },
    {
        "device_name": "xvdb",
        "volume_size": 30,
        "volume_type": "gp2",
        "delete_on_termination": true    
    }]

你可以进行规划,知道xvd[b]实际上比映射的字母落后两个位置。

然后使用Terraform启动一个多卷AMI的EC2实例,并将其放在aws_instance资源的user_data部分中:

    user_data = <<EOF
    <powershell>
    Initialize-Disk -Number 1 -PartitionStyle "MBR"
    New-Partition -DiskNumber 1 -UseMaximumSize -IsActive -AssignDriveLetter
    Format-Volume -DriveLetter d -Confirm:$FALSE
    Set-Partition -DriveLetter D -NewDriveLetter S
    </powershell>
    EOF
Set-Partition -DriveLetter D -NewDriveLetter S这一行是用来将你已知的顺序驱动器重命名为你习惯的任何字母。对我来说,他们想要将D: 作为S: - 只需重复此行以将E: 重命名为X: 或您需要的任何内容。
希望这能有所帮助。
更新: 还有另一种方法(Server 2016以上版本),当我发现Sysprep清除了所有映射并被烤进AMI镜像时,我发现了它。
您必须在C:\ProgramData\Amazon\EC2-Windows\Launch\Config中提供一个DriveLetterMappingConfig.json文件来执行映射。文件的格式为:
{
  "driveLetterMapping": [
    {
      "volumeName": "sample volume",
      "driveLetter": "H"
    }
  ]
}

然而,默认情况下,我的驱动器没有卷标名称,它们是空白的。因此,回到1980年代那个好用的“LABEL”命令。将D:驱动器命名为volume2。因此,该文件看起来像:

{
  "driveLetterMapping": [
    {
      "volumeName": "volume2",
      "driveLetter": "S"
    }
  ]
}

运行C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeDisks.ps1测试,这样就可以确保D:变成S:

现在,回到Packer,我还需要使用位于C:\ProgramData\Amazon\EC2-Windows\Launch\Config的DriveLetterMappingConfig.json文件来规划镜像,以确保我对AMI的S:磁盘所做的所有工作在实例上都能以S:的形式出现。(我把这个文件放在了一个S3存储桶中,连同我们将要安装在盒子上的所有其他东西一起。)

我把磁盘内容放进了一个.ps1文件中,并从一个供应程序中调用它:

{ "type": "powershell", "script": "./setup_two_drive_names_c_and_s.ps1"
},

上述.ps1文件内容如下:

# Do volume config of the two drives
write-host "Setting up drives..."
Initialize-Disk -Number 1 -PartitionStyle "MBR"
New-Partition -DiskNumber 1 -UseMaximumSize -IsActive -AssignDriveLetter
Format-Volume -DriveLetter d -Confirm:$FALSE
label c: "volume1"
label d: "volume2"
Set-Partition -DriveLetter D -NewDriveLetter S

# Now insert DriveLetterMappingConfig.json file into C:\ProgramData\Amazon\EC2-Windows\Launch\Config to ensure instance starts with correct drive mappings
Write-Host "S3 Download: DriveLetterMappingConfig.json"
Read-S3Object -BucketName ********* -Key DriveLetterMappingConfig.json -File 'c:\temp\DriveLetterMappingConfig.json'
Write-Host "Copying DriveLetterMappingConfig.json to C:\ProgramData\Amazon\EC2-Windows\Launch\Config..."
Copy-Item "c:\temp\DriveLetterMappingConfig.json" -Destination "C:\ProgramData\Amazon\EC2-Windows\Launch\Config\DriveLetterMappingConfig.json" -Force
Write-Host "Set Initialze Disks to run on every boot..."
C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeDisks.ps1 -Schedule

是的,没有理由给c打标签。但是我正在进行中...

最后一行带有“-Schedule”参数意味着这将在每次启动时发生。


1
使用LaunchConfig,您无法确定哪个驱动器是哪个字母。而且您也无法设置多个驱动器。 - EnzoR

1
稍微复杂的解决方案。为了使其工作,需要进行以下设置:
  1. 使用“DriveLetter”标记每个卷,并赋予您想要分配的值。不带“:”
  2. 在IAM中授予EC2实例“ec2:DescribeTags”和“ec2:DescribeVolumes”的权限
  3. 通过将其传递到用户数据或创建一个ssm文档并在启动后运行它来运行以下脚本
Get-Disk|where-Object IsSystem -eq $False|Foreach-Object {
  if ( $_.PartitionStyle -Eq 'RAW') {
      Initialize-Disk -Number $_.Number –PartitionStyle MBR
      Set-Disk -Number $_.Number -IsOffline $False
      $VolumeId=$_.SerialNumber -replace "_[^ ]*$" -replace "vol", "vol-"
      $InstanceId = (Invoke-WebRequest -Uri "http://169.254.169.254/latest/meta-data/instance-id" -UseBasicParsing).Content
      $DriveLetter = Get-EC2Volume -Filter @{Name="volume-id";Values=$VolumeId},@{Name="attachment.instance-id";Values=$instanceId}  |ForEach-Object {$_.Tags}|where Key -eq "DriveLetter"|Select-Object -Property Value |foreach-Object {$_.Value}
      New-Partition -DiskNumber $_.Number -DriveLetter $DriveLetter –UseMaximumSize
      Format-Volume -DriveLetter $DriveLetter
    }

这是我认为的真正解决方案(前提是它完全可行)。所有带有正确标签的驱动器都可以根据标签进行初始化和映射。我还会添加一个带有标签的标签,以便更好地标识。赞。 - EnzoR
可能需要使用元数据v2进行更新。 - user9934423

1
在AWS Windows服务器2016及以上版本中,您可以在EC2创建期间使用以下行来初始化次要卷。
C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeDisks.ps1

更多信息请参见:

亚马逊支持

上述AWS脚本仅初始化磁盘为MBR类型。(我们无法使用MBR类型扩展容量>2TB)

我的用例是初始化GPT类型的卷。

因此,我最终将以下脚本传递给用户数据,并将其发送到保存在C驱动器中的文件中 (我已经参考了Manpreet Nehra的建议来制定用户脚本 https://dev59.com/Jqbja4cB1Zd3GeqPczjT#61530894)

$disks = Get-Disk|where-Object  partitionstyle -eq 'RAW' 
foreach ($diski in $disks) {
      Initialize-Disk -Number $diski.Number
      Set-Disk -Number $diski.Number -IsOffline $False
      $VolumeId=$diski.SerialNumber -replace "_[^ ]*$" -replace "vol", "vol-"
      $InstanceId = (Invoke-WebRequest -Uri "http://169.254.169.254/latest/meta-data/instance-id" -UseBasicParsing).Content
      $DriveLetter = (Get-EC2Tag -Filter @{Name="resource-type";Value="volume"},@{Name="resource-id";Value=$VolumeId} | where-object Key -eq "driveletter").value
      New-Partition -DiskNumber $diski.Number -DriveLetter $DriveLetter –UseMaximumSize
      Format-Volume -DriveLetter $DriveLetter
    }
Start-Sleep -s 120
'@
$initializescript | Out-File C:\initializescript.ps1
Start-Sleep -s 30  

Then, I called the above script through AWS SSM by creating and associating it with instance . Below is the code


resource "aws_ssm_association" "initialize" {
  count = length(var.ec2_name)
  name        = var.ssm_document_name
  targets {
    key    = "InstanceIds"
    values = [element(aws_instance.ec2server[*].id, count.index)]
  }
    }


resource "aws_ssm_document" "InitializeDrives" {
  name          = "initializedriveswindows"
  document_type = "Command"

  content = <<DOC
{
  "schemaVersion": "2.2",
  "description": "Run command to initialize drives",
  "parameters": {
    "Message": {
      "type": "String",
      "description": "Run command to initialize drives",
      "default": "Run command to initialize drives"
    }
  },
  "mainSteps": [
    {
      "action": "aws:runPowerShellScript",
      "name": "powershell",
      "inputs": {
        "runCommand": [
          "C:\\initializescript.ps1",
          "Restart-Computer -Force"
        ]
      }
    }
  ]
}
DOC
} ```

I have included sleep time to avoid race issues.

0
首先,我们要强制执行一种挂载约定,该约定简单地规定了对于非根卷,使用xvdDRIVE约定来表示您要挂载到的驱动器号。其中,DRIVE与您想要挂载的驱动器字母相同。
   xvdd - D:
   xvde - E:
   xvdm = M:

为了支持驱动器分配.. 包括“跳级”安装

format the volumes .. with the drive letter or some other convention you like
  we run diskpart with an input file.. but basically
      format fs=ntfs label=D quick

然后我们更新 DriveletterConfig.xml 或 DriveLetterConfig.json(取决于 ec2config 还是 ec2launch)

    for xml looks like:
        <Mapping> <VolumeName>D</VolumeName> <DriveLetter>D:</DriveLetter></Mapping>
        <Mapping> <VolumeName>E</VolumeName> <DriveLetter>E:</DriveLetter> </Mapping>
        <Mapping> <VolumeName>M</VolumeName> <DriveLetter>M:</DriveLetter> </Mapping>  
    

poof


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接