sorta kinda...

主にAWS関連ですが、これに限らずいろいろ勉強したことや思ったことを書いていきます。

EBS ボリューム ID とデバイス名とドライブレターの関連を Systems Manager で確認したい

今日も JSON ネタです、那須です。

先日 Windows Server でボリューム ID とドライブレターを関連付ける記事を書きました。 nasrinjp1.hatenablog.com

これ単純に PowerShell 実行してるだけなので、Systems Manager で簡単にできるんじゃないの?って思ったのでやってみました。 簡単は簡単なんですが、ちょっとだけてこずりました。

 

とりあえず PowerShell スクリプトを実行するドキュメントを作成

Systems Manager の既存の RunCommand ドキュメントから適当に aws:runPowerShellScript を実行してるものを探して、そのコンテンツを参考にスクリプトを埋め込みます。 前回記事で書いたスクリプトを実行するように書いた JSON が↓です。 JSON の中に PowerShell を書く場合は、下記に注意しましょう。

  • 1 行ごとに "(ダブルクォーテーション) で囲む
  • スクリプト内に出てくる "(ダブルクォーテーション) は \(バックスラッシュ) でエスケープする
{
    "schemaVersion": "1.2",
    "description": "Mapping Disks to Volumes on Your Windows Instance.",
    "runtimeConfig": {
        "aws:runPowerShellScript": {
            "properties": [
                {
                    "id": "0.aws:runPowerShellScript",
                    "timeoutSeconds": 7200,
                    "runCommand": [
                        "function Get-EC2InstanceMetadata",
                        "{",
                        "    param([string]$Path)",
                        "    (Invoke-WebRequest -Uri \"http://169.254.169.254/latest/$Path\").Content ",
                        "}",
                        "",
                        "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",
                        "}",
                        "",
                        "Try {",
                        "    $InstanceId = Get-EC2InstanceMetadata \"meta-data/instance-id\"",
                        "    $AZ = Get-EC2InstanceMetadata \"meta-data/placement/availability-zone\"",
                        "    $Region = $AZ.Remove($AZ.Length - 1)",
                        "    $BlockDeviceMappings = (Get-EC2Instance -Region $Region -Instance $InstanceId).Instances.BlockDeviceMappings",
                        "    $VirtualDeviceMap = @{}",
                        "    (Get-EC2InstanceMetadata \"meta-data/block-device-mapping\").Split(\"`n\") | ForEach-Object {",
                        "        $VirtualDevice = $_",
                        "        $BlockDeviceName = Get-EC2InstanceMetadata \"meta-data\/block-device-mapping\/$VirtualDevice\"",
                        "        $VirtualDeviceMap[$BlockDeviceName] = $VirtualDevice",
                        "        $VirtualDeviceMap[$VirtualDevice] = $BlockDeviceName",
                        "    }",
                        "}",
                        "Catch {",
                        "    Write-Host \"Could not access the AWS API, therefore, VolumeId is not available. ",
                        "  Verify that you provided your access keys.\" -ForegroundColor Yellow",
                        "}",
                        "",
                        "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)",
                        "        $BlockDevice = $BlockDeviceMappings | Where-Object { $_.DeviceName -eq $BlockDeviceName }",
                        "        $VirtualDevice = If ($VirtualDeviceMap.ContainsKey($BlockDeviceName)) { $VirtualDeviceMap[$BlockDeviceName] } Else { $null }",
                        "    } ElseIf ($DiskDrive.PNPDeviceID -like \"*PROD_AMAZON_EC2_NVME*\") {",
                        "        $BlockDeviceName = Get-EC2InstanceMetadata \"meta-data/block-device-mapping/ephemeral$($DiskDrive.SCSIPort - 2)\"",
                        "        $BlockDevice = $null",
                        "        $VirtualDevice = If ($VirtualDeviceMap.ContainsKey($BlockDeviceName)) { $VirtualDeviceMap[$BlockDeviceName] } Else { $null }",
                        "    } Else {",
                        "        $BlockDeviceName = $null",
                        "        $BlockDevice = $null",
                        "        $VirtualDevice = $null",
                        "    }",
                        "    New-Object PSObject -Property @{",
                        "        Disk = $DiskDrive.Index;",
                        "        Partitions = $DiskDrive.Partitions;",
                        "        DriveLetter = If ($Volumes -eq $null) { \"N/A\" } Else { $Volumes.DeviceID };",
                        "        EbsVolumeId = If ($BlockDevice -eq $null) { \"N/A\" } Else { $BlockDevice.Ebs.VolumeId };",
                        "        Device = If ($BlockDeviceName -eq $null) { \"N/A\" } Else { $BlockDeviceName };",
                        "        VirtualDevice = If ($VirtualDevice -eq $null) { \"N/A\" } Else { $VirtualDevice };",
                        "        VolumeName = If ($Volumes -eq $null) { \"N/A\" } Else { $Volumes.VolumeName };",
                        "    }",
                        "} | Sort-Object Disk | Format-Table -AutoSize -Property Disk, Partitions, DriveLetter, EbsVolumeId, Device, VirtualDevice, VolumeName"
                    ]
                }
            ]
        }
    }
}

ざっとドキュメントの作り方を書きます。「Create Document」を押します。
f:id:nasrinjp1:20180821103234p:plain

名前は適当につけてください。ドキュメントのタイプは、コマンドのドキュメントを選びましょう。今回はコンテンツは JSON を選んで、↑で書いた JSON をコピペします。これだけです、かんたんですね。
f:id:nasrinjp1:20180821103258p:plain

 

実行してみよう

RunCommand でさきほど作成したドキュメントを実行しましょう。簡単に流れを書きます。 先ほど作成したコマンドのドキュメントを選びます。
f:id:nasrinjp1:20180821103406p:plain

実行対象のインスタンスを選択します。
f:id:nasrinjp1:20180821103612p:plain

出力オプションを適当に指定します。S3 に出力しておくと日本語のログも文字化けすることなく見れるので、最低限 S3 には出力するようにしましょう。これで実行します。
f:id:nasrinjp1:20180821103734p:plain

 

エラー出た

あれ? なんか Could not access the AWS API, therefore, VolumeId is not available. ってエラー出てるな… これ Catch で指定したエラー文なので、本当のエラーを確認するために Catch の中を下記に書き換えます。

Write-Host $_.Exception.Message

ちなみに、schemaVersion を 1.2 で作ってしまったので、同じドキュメントを更新してバージョン 2 を作ることができません。

docs.aws.amazon.com

とりあえず、さっき作ったドキュメントを削除して再作成しましょう。 そして RunCommand で再実行します。

エラー内容出ました。Windows Server でスクリプト実行した時はすんなり結果出たのに不思議だ。。。

Internet Explorer エンジンを使用できないか、Internet Explorer の初回起動構成が完了していないため、応答のコンテンツを解析できません。UseBasicParsing パラメーターを指定して再試行してください。

なんか Windows Server を起動して一度もログインしてないか IE 開いてないからこのエラーが出てるように見えますね。

 

UseBasicParsing オプションとは?

このオプションをつけると IE エンジンを使わずにパースするみたいです。逆にこのオプションをつけなければ IE エンジンを使おうとします。 今回の場合は、AMI から Windows Server のインスタンス起動した直後の状態で IE は開いてもないし設定もしていないのでこうなったみたいです。 前回、OS にログインしてやった時は無意識に IE を開いていたんだろうか…?

TechNet フォーラムにかいてありました。
https://social.technet.microsoft.com/Forums/ja-JP/12e5ada3-5fc6-46c5-a504-e8d51719cad1/invokewebrequest203512999226178123981230012475124611251712522124861?forum=powershellja

PowerShell 6.0.0 以降だとこのオプションがついた状態での実行となるようです。 docs.microsoft.com

 

UseBasicParsing オプションをつけてみる

PowerShell の 4 行目の Invoke-WebRequest に UseBasicParsing オプションを追加します。 大事なことなので 2 回書きますが、schemaVersion を 1.2 で作ってしまったので同じドキュメントを更新してバージョン 2 を作ることができません。 さっき作ったドキュメントを削除して再作成します。 次からは schemaVersion は 2.2 で作ろうと心の底から思いました。

ここまでの変更点を反映したコンテンツの JSON は↓になります。

{
    "schemaVersion": "1.2",
    "description": "Mapping Disks to Volumes on Your Windows Instance.",
    "runtimeConfig": {
        "aws:runPowerShellScript": {
            "properties": [
                {
                    "id": "0.aws:runPowerShellScript",
                    "timeoutSeconds": 7200,
                    "runCommand": [
                        "function Get-EC2InstanceMetadata",
                        "{",
                        "    param([string]$Path)",
                        "    (Invoke-WebRequest -Uri \"http://169.254.169.254/latest/$Path\" -UseBasicParsing).Content ",
                        "}",
                        "",
                        "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",
                        "}",
                        "",
                        "Try {",
                        "    $InstanceId = Get-EC2InstanceMetadata \"meta-data/instance-id\"",
                        "    $AZ = Get-EC2InstanceMetadata \"meta-data/placement/availability-zone\"",
                        "    $Region = $AZ.Remove($AZ.Length - 1)",
                        "    $BlockDeviceMappings = (Get-EC2Instance -Region $Region -Instance $InstanceId).Instances.BlockDeviceMappings",
                        "    $VirtualDeviceMap = @{}",
                        "    (Get-EC2InstanceMetadata \"meta-data/block-device-mapping\").Split(\"`n\") | ForEach-Object {",
                        "        $VirtualDevice = $_",
                        "        $BlockDeviceName = Get-EC2InstanceMetadata \"meta-data\/block-device-mapping\/$VirtualDevice\"",
                        "        $VirtualDeviceMap[$BlockDeviceName] = $VirtualDevice",
                        "        $VirtualDeviceMap[$VirtualDevice] = $BlockDeviceName",
                        "    }",
                        "}",
                        "Catch {",
                        "    Write-Host $_.Exception.Message",
                        "}",
                        "",
                        "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)",
                        "        $BlockDevice = $BlockDeviceMappings | Where-Object { $_.DeviceName -eq $BlockDeviceName }",
                        "        $VirtualDevice = If ($VirtualDeviceMap.ContainsKey($BlockDeviceName)) { $VirtualDeviceMap[$BlockDeviceName] } Else { $null }",
                        "    } ElseIf ($DiskDrive.PNPDeviceID -like \"*PROD_AMAZON_EC2_NVME*\") {",
                        "        $BlockDeviceName = Get-EC2InstanceMetadata \"meta-data/block-device-mapping/ephemeral$($DiskDrive.SCSIPort - 2)\"",
                        "        $BlockDevice = $null",
                        "        $VirtualDevice = If ($VirtualDeviceMap.ContainsKey($BlockDeviceName)) { $VirtualDeviceMap[$BlockDeviceName] } Else { $null }",
                        "    } Else {",
                        "        $BlockDeviceName = $null",
                        "        $BlockDevice = $null",
                        "        $VirtualDevice = $null",
                        "    }",
                        "    New-Object PSObject -Property @{",
                        "        Disk = $DiskDrive.Index;",
                        "        Partitions = $DiskDrive.Partitions;",
                        "        DriveLetter = If ($Volumes -eq $null) { \"N/A\" } Else { $Volumes.DeviceID };",
                        "        EbsVolumeId = If ($BlockDevice -eq $null) { \"N/A\" } Else { $BlockDevice.Ebs.VolumeId };",
                        "        Device = If ($BlockDeviceName -eq $null) { \"N/A\" } Else { $BlockDeviceName };",
                        "        VirtualDevice = If ($VirtualDevice -eq $null) { \"N/A\" } Else { $VirtualDevice };",
                        "        VolumeName = If ($Volumes -eq $null) { \"N/A\" } Else { $Volumes.VolumeName };",
                        "    }",
                        "} | Sort-Object Disk | Format-Table -AutoSize -Property Disk, Partitions, DriveLetter, EbsVolumeId, Device, VirtualDevice, VolumeName"
                    ]
                }
            ]
        }
    }
}

 

さあ今度こそ!

無事成功しました!

Disk Partitions DriveLetter EbsVolumeId           Device    VirtualDevice VolumeName
---- ---------- ----------- -----------           ------    ------------- -----
   0          1 C:          vol-0cec1838471c7db24 /dev/sda1 root               
   1          0 G:          vol-06bce859498e0fa05 xvdb      ebs2          G drive
   2          0 H:          vol-0ed34a2e652141916 xvdc      ebs3          H drive
   3          0 I:          vol-06a9e298faeec098b xvdd      ebs4          I drive

 

これで OS にログインしなくても確認できますね

OS にログインしてしまうと権限によってはなんでもできてしまうので、オペミス等で変な操作してしまって業務に影響及ぼしてしまうとかありますが、Systems Manager を使えばそれもなくなりますね。 ログも簡単に残せるので OS 上でのコマンド作業は積極的に Systems Manager を使うようにしていきましょう。

あと、RunCommand ドキュメントの schemaVersion は 2.2 で書きましょう!