#012: az コマンドとGithub CopilotでARMテンプレートを Bicepに変換

Bicepを作成する時、今まではMS公式ドキュメントを参照しつつ、Github Copilotくんの協力を得ながら手動でゼロから作成していた。

Copilotくんが結構優秀なので手動でもスピード感はあったけど、今回はAzure Portalで作成済みのリソースからARMテンプレートをダウンロードし、azコマンドとGithub copilotくんを組み合わせてもう少し効率的にBicepファイルが作成できないかやってみた。

ターゲットはVPN Gatewayで、P2S VPNを構成する。

実際にやってみると、この方法を利用することで以下の流れができるので、今のところ一番楽な方法だと感じる。

- Azure Portalで目的のリソースを生成し動作確認
- 生成したリソースのARMテンプレートをAzure Portalからダウンロード
- az bicep decompileコマンドでARMテンプレートからBicepテンプレートを生成
- Github Copilotで微調整を自動化
- 手動で最終化（コメントの精緻化、main.bicepとの結合、各パラメータの外だしなど）
- 作成済みのリソースを削除し、Bicepでリソースを再デプロイ（デバッグと動作確認を繰り返して最終系に仕上げる）

今回生成したbicepを含む全体ソースは以下。

https://github.com/masatoshisato/RagSystem/tree/v1.0.6

# 1. 既存のリソースからARMテンプレートを生成

## 1-1. Azure Portalで作成したリソースのARMテンプレート参照方法

VPN GatewayのARMテンプレートをAzure Portalで参照する方法は２つ。

1. Azure Portalで構築済みのリソースでARMテンプレートを参照
2. Azure Portalで構築済みのリソースグループ全体のARM テンプレートファイルをダウンロードして、目的のリソース部分を参照

どちらも似たような内容なのだが、微妙に定義の差異がある。

今回は、依存リソースが分かりやすいという理由から上記2の方を採用。

## 1-2. リソースグループ全体のARMテンプレートのダウンロード

リソースグループの「Automation」-「Export template」で、対象リソースグループ全体のARMテンプレート＋パラメータファイルをダウンロードできる。

::: note
いくつかのリソースはARMテンプレート未対応。以下はAADLoginForWindowsの拡張機能が未対応という警告。前回までに設定したものが残っていた。今はAADLoginForWindowsは使わない想定なので無視。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466853/f0f54432-a13f-4f7d-55f8-656ef4dc9a82.png)

ダウンロードしたARMテンプレートファイル（jsonファイル）は以下スクリーンショットのようにresources配下が各リソースの定義部分になっている。

VSCodeなどで各リソース内の「properties」部分を折りたたんでいくと各リソース定義が小さくなるので全体が参照しやすくなる。

以下のイメージは、Basion用のNSG、IPアドレス、およびVPN用の１つ目のIP定義が見える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466853/fc7cd4f5-061c-8c85-158b-717518edb122.png)

## 1-3. 対象とするリソースのARM テンプレートファイルの作成

今回は以下のリソースを参照し、Bicep変換元として利用するARMテンプレートを作成する。

- VPN Gateway
- VPN Gatewayを定義するために必要となった３つのPublic IP
- VPN Gatewayを配置するSubnet

::: note
３つのpublic IPとは、VPN Gatewayのリソース作成時、ActiveActiveモードで２つのIP、それに加えてクライアントからアクセスするためのエントリーポイントとなるIP。
:::

VPN Gatewayは、「Microsoft.Network/virtualNetworkGateways」というtypeで表現されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466853/61d4d1f1-d7a4-c71d-30a2-e10be238a815.png)

::: note
公式ドキュメントでは、VPNリソースと思われるtypeが２つ存在する。１つが「VirtualNetworkGateways」、もう一つが「vpnGateways」。
「vpnGateways」というtypeは、propretiesで定義できる項目をみると今回期待しているものではないことがわかる。こちらは、Virtual WANを構成する際に利用するリソースらしい。紛らわしい。

https://learn.microsoft.com/ja-jp/azure/templates/microsoft.network/vpngateways?pivots=deployment-language-bicep
:::

３つのipと、VPN Gatewayのリソース定義を抽出し、propertiesを展開したjsonファイルを作成する。

```json
{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Network/publicIPAddresses",
            "apiVersion": "2024-01-01",
            "name": "RagSystem-P2SVpnIp1-dev",
            "location": "eastus2",
            "sku": {
                "name": "Standard",
                "tier": "Regional"
            },
            "properties": {
                "ipAddress": "172.177.154.185",
                "publicIPAddressVersion": "IPv4",
                "publicIPAllocationMethod": "Static",
                "idleTimeoutInMinutes": 4,
                "ipTags": []
            }
        },
        {
            "type": "Microsoft.Network/publicIPAddresses",
            "apiVersion": "2024-01-01",
            "name": "RagSystem-P2SVpnIp2-dev",
            "location": "eastus2",
            "sku": {
                "name": "Standard",
                "tier": "Regional"
            },
            "properties": {
                "ipAddress": "172.177.154.183",
                "publicIPAddressVersion": "IPv4",
                "publicIPAllocationMethod": "Static",
                "idleTimeoutInMinutes": 4,
                "ipTags": []
            }
        },
        {
            "type": "Microsoft.Network/publicIPAddresses",
            "apiVersion": "2024-01-01",
            "name": "RagSystem-P2SVpnIpEntryPoint-dev",
            "location": "eastus2",
            "sku": {
                "name": "Standard",
                "tier": "Regional"
            },
            "properties": {
                "ipAddress": "20.10.169.45",
                "publicIPAddressVersion": "IPv4",
                "publicIPAllocationMethod": "Static",
                "idleTimeoutInMinutes": 4,
                "ipTags": []
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworkGateways",
            "apiVersion": "2024-01-01",
            "name": "RagSystem-P2SVpn-dev",
            "location": "eastus2",
            "dependsOn": [
                "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIp1-dev')]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')]",
                "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIp2-dev')]",
                "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIpEntryPoint-dev')]"
            ],
            "properties": {
                "enablePrivateIpAddress": false,
                "ipConfigurations": [
                    {
                        "name": "default",
                        "id": "[concat(resourceId('Microsoft.Network/virtualNetworkGateways', 'RagSystem-P2SVpn-dev'), '/ipConfigurations/default')]",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIp1-dev')]"
                            },
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')]"
                            }
                        }
                    },
                    {
                        "name": "activeActive",
                        "id": "[concat(resourceId('Microsoft.Network/virtualNetworkGateways', 'RagSystem-P2SVpn-dev'), '/ipConfigurations/activeActive')]",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIp2-dev')]"
                            },
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')]"
                            }
                        }
                    },
                    {
                        "name": "RagSystem-P2SVpnIpEntryPoint-dev",
                        "id": "[concat(resourceId('Microsoft.Network/virtualNetworkGateways', 'RagSystem-P2SVpn-dev'), '/ipConfigurations/RagSystem-P2SVpnIpEntryPoint-dev')]",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses', 'RagSystem-P2SVpnIpEntryPoint-dev')]"
                            },
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')]"
                            }
                        }
                    }
                ],
                "natRules": [],
                "virtualNetworkGatewayPolicyGroups": [],
                "enableBgpRouteTranslationForNat": false,
                "disableIPSecReplayProtection": false,
                "sku": {
                    "name": "VpnGw1",
                    "tier": "VpnGw1"
                },
                "gatewayType": "Vpn",
                "vpnType": "RouteBased",
                "enableBgp": false,
                "activeActive": true,
                "vpnClientConfiguration": {
                    "vpnClientAddressPool": {
                        "addressPrefixes": [
                            "192.168.10.0/24"
                        ]
                    },
                    "vpnClientProtocols": [
                        "OpenVPN"
                    ],
                    "vpnAuthenticationTypes": [
                        "AAD"
                    ],
                    "vpnClientRootCertificates": [],
                    "vpnClientRevokedCertificates": [],
                    "vngClientConnectionConfigurations": [],
                    "radiusServers": [],
                    "vpnClientIpsecPolicies": [],
                    "aadTenant": "https://login.microsoftonline.com/<tenantID>/",
                    "aadAudience": "c632b3df-fb67-4d84-bdcf-b95ad541b5c8",
                    "aadIssuer": "https://sts.windows.net/<tenantID>/"
                },
                "bgpSettings": {
                    "asn": 65515,
                    "bgpPeeringAddress": "10.0.0.229,10.0.0.228",
                    "peerWeight": 0,
                    "bgpPeeringAddresses": [
                        {
                            "ipconfigurationId": "[concat(resourceId('Microsoft.Network/virtualNetworkGateways', 'RagSystem-P2SVpn-dev'), '/ipConfigurations/default')]",
                            "customBgpIpAddresses": []
                        },
                        {
                            "ipconfigurationId": "[concat(resourceId('Microsoft.Network/virtualNetworkGateways', 'RagSystem-P2SVpn-dev'), '/ipConfigurations/activeActive')]",
                            "customBgpIpAddresses": []
                        }
                    ]
                },
                "customRoutes": {
                    "addressPrefixes": []
                },
                "vpnGatewayGeneration": "Generation1",
                "allowRemoteVnetTraffic": false,
                "allowVirtualWanTraffic": false
            }
        }
    ]
}
```

このテンプレートを、例えば「P2SVpn.json」というファイルに保存しておく。

# 2. ARMテンプレートをbicepテンプレートに変換

以下２つの方法を組み合わせて、ARMテンプレートをBicepテンプレートに変換していく。

- “az bicep decompile”コマンド
- Github Copilotで既存bicepの活用やエラー箇所の修正

これだけではまだ微調整が必要な状態だが、これ以上細かい部分をGithub Copilotくんにお願いするより、VSCodeのGithub Copilotプラグインをつかって自分で細かいエラーや気になる箇所を微調整した方が速い。

## 2-1. “az bicep decompile”コマンドで変換

変換コマンドは以下、変換前のARMテンプレートファイル名が変換後のBicepテンプレートファイル名になる。

いくつかワーニングとエラーが出力された。対応方法は後述。

```bash
sato@[17:33:32]:~/proj/RagSystem/workToDecompile% **az bicep decompile --file P2SVpn.json**
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(124,27) : Warning no-hardcoded-env-urls: Environment URLs should not be hardcoded. Use the environment() function to ensure compatibility across clouds. Found this disallowed host: "login.microsoftonline.com" [https://aka.ms/bicep/linter/no-hardcoded-env-urls]
WARNING: Decompilation is a best-effort process, as there is no guaranteed mapping from ARM JSON to Bicep Template or Bicep Parameters.
You may need to fix warnings and errors in the generated bicep/bicepparam file(s), or decompilation may fail entirely if an accurate conversion is not possible.
If you would like to report any issues or inaccurate conversions, please see https://github.com/Azure/bicep/issues.
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(57,16) : Error BCP079: This expression is referencing its own declaration, which is not allowed.
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(70,16) : Error BCP079: This expression is referencing its own declaration, which is not allowed.
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(83,16) : Error BCP079: This expression is referencing its own declaration, which is not allowed.
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(134,33) : Error BCP079: This expression is referencing its own declaration, which is not allowed.
/Users/sato/proj/RagSystem/workToDecompile/P2SVpn.bicep(138,33) : Error BCP079: This expression is referencing its own declaration, which is not allowed.
sato@[17:33:38]:~/proj/RagSystem/workToDecompile% 
```

## 2-2. 変換時のワーニングとエラーの修正方針

### 2-2-1. 環境依存の定数を利用：no-hardcoded-env-urls

これはワーニング。

公式ドキュメントに記載されているbicepのlinterにより、Azureの環境情報に依存している情報をBicep内でリテラル値として指定すると警告が出力される。

https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-deployment#environment

具体的には、VPN Gatewayに定義したEntraID認証の設定で指定したEntraへの認証URLが上記Linterによって制限されている。

```yaml
resource RagSystem_P2SVpn_dev 'Microsoft.Network/virtualNetworkGateways@2024-01-01' = {
  name: 'RagSystem-P2SVpn-dev'
  ...
  properties: {
    ...
    vpnClientConfiguration: {
      ...
      ↓ここ
      aadTenant: 'https://login.microsoftonline.com/<tenantID>/'
    }
  }
}
```

この”login.microsoftonline.com”という部分は、BicepのLinterにより、リテラル値として直接定義することは推奨されていない。

このワーニングは以下のように定義すると解消する。

```yaml
      aadTenant: '${environment().authentication.loginEndpoint}<tenantID>/'
```

### 2-2-2. 自己参照：referencing its own declaration

こちらはエラーなので修正が必要。エラーが発生するようなBicepを生成し、自らエラー検出しているのは、”az bicep decompile”コマンドのバグなのだろう。

VPN Gatewayの定義内で、自分自身を参照してしまっている部分について指摘されている。

例えば以下の部分。

```yaml
resource RagSystem_P2SVpn_dev 'Microsoft.Network/virtualNetworkGateways@2024-01-01' = {
  name: 'RagSystem-P2SVpn-dev'
  ...
  properties: {
    ...
    ipConfigurations: [
			{
			  name: 'default'
	      ↓ここ
        id: '${RagSystem_P2SVpn_dev.id}/ipConfigurations/default'
      }
    ]
  }
}
```

自己参照エラーになったのは、すべて”id”の指定の箇所。各リソースのIDはデプロイ時に自動生成するのでBicepからは全ての”id”の指定を削除する。

## 2-3. ここまでの対応結果と改善ポイント

”az bicep decompile”コマンドを用いてARMテンプレートをbicepテンプレートに変換し、警告・エラー箇所を修正した結果が以下。

```yaml
resource RagSystem_P2SVpnIp1_dev 'Microsoft.Network/publicIPAddresses@2024-01-01' = {
  name: 'RagSystem-P2SVpnIp1-dev'
  location: 'eastus2'
  sku: {
    name: 'Standard'
    tier: 'Regional'
  }
  properties: {
    ipAddress: '172.177.154.185'
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
    idleTimeoutInMinutes: 4
    ipTags: []
  }
}

resource RagSystem_P2SVpnIp2_dev 'Microsoft.Network/publicIPAddresses@2024-01-01' = {
  name: 'RagSystem-P2SVpnIp2-dev'
  location: 'eastus2'
  sku: {
    name: 'Standard'
    tier: 'Regional'
  }
  properties: {
    ipAddress: '172.177.154.183'
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
    idleTimeoutInMinutes: 4
    ipTags: []
  }
}

resource RagSystem_P2SVpnIpEntryPoint_dev 'Microsoft.Network/publicIPAddresses@2024-01-01' = {
  name: 'RagSystem-P2SVpnIpEntryPoint-dev'
  location: 'eastus2'
  sku: {
    name: 'Standard'
    tier: 'Regional'
  }
  properties: {
    ipAddress: '20.10.169.45'
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
    idleTimeoutInMinutes: 4
    ipTags: []
  }
}

resource RagSystem_P2SVpn_dev 'Microsoft.Network/virtualNetworkGateways@2024-01-01' = {
  name: 'RagSystem-P2SVpn-dev'
  location: 'eastus2'
  properties: {
    enablePrivateIpAddress: false
    ipConfigurations: [
      {
        name: 'default'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: RagSystem_P2SVpnIp1_dev.id
          }
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')
          }
        }
      }
      {
        name: 'activeActive'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: RagSystem_P2SVpnIp2_dev.id
          }
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')
          }
        }
      }
      {
        name: 'RagSystem-P2SVpnIpEntryPoint-dev'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: RagSystem_P2SVpnIpEntryPoint_dev.id
          }
          subnet: {
            id: resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')
          }
        }
      }
    ]
    natRules: []
    virtualNetworkGatewayPolicyGroups: []
    enableBgpRouteTranslationForNat: false
    disableIPSecReplayProtection: false
    sku: {
      name: 'VpnGw1'
      tier: 'VpnGw1'
    }
    gatewayType: 'Vpn'
    vpnType: 'RouteBased'
    enableBgp: false
    activeActive: true
    vpnClientConfiguration: {
      vpnClientAddressPool: {
        addressPrefixes: [
          '192.168.10.0/24'
        ]
      }
      vpnClientProtocols: [
        'OpenVPN'
      ]
      vpnAuthenticationTypes: [
        'AAD'
      ]
      vpnClientRootCertificates: []
      vpnClientRevokedCertificates: []
      vngClientConnectionConfigurations: []
      radiusServers: []
      vpnClientIpsecPolicies: []
      aadTenant: '${environment().authentication.loginEndpoint}<tenantID>/'
      aadAudience: 'c632b3df-fb67-4d84-bdcf-b95ad541b5c8'
      aadIssuer: 'https://sts.windows.net/<tenantID>/'
    }
    bgpSettings: {
      asn: 65515
      bgpPeeringAddress: '10.0.0.229,10.0.0.228'
      peerWeight: 0
    }
    customRoutes: {
      addressPrefixes: []
    }
    vpnGatewayGeneration: 'Generation1'
    allowRemoteVnetTraffic: false
    allowVirtualWanTraffic: false
  }
  dependsOn: [
    resourceId('Microsoft.Network/virtualNetworks/subnets', 'RagSystem-MainVnet-dev', 'GatewaySubnet')
  ]
}
```

この時点で検討できるさらなる改善点は以下２点。

- PublicIPは別ファイルでテンプレートにしているため、moduleとして定義しなおす
- VPNの定義の最後にある”dependsOn”にはmoduleもしくはresourceを指定する必要があるが、stringであるリソースIDが指定されているので、ここもresourceに置き換える

# 3. Github CopilotくんがBicepテンプレートを修正

Github Cpilotくん、ARMテンプレートからBicepテンプレートを生成できるのだが、その後の微調整が少し面倒だった。azコマンドでより精度の高いBicepテンプレートに変換した後、Copilotくんに微調整を依頼してみた。

何度かやりとりして調整したので、全体は省略し、ステップと最終結果をここに掲載する。

## 3-1. Step1: デフォルトパラメータの指定

Azure Portalで作成した時のパラメータ値を指定してあげた。P2SVpn.bicepにはARMテンプレートに保存されていた値が反映されているので、各パラメータの値までプロンプトで指定する必要はなかったかもしれない。

以下、指定したプロンプト。

```yaml
P2SVpn.bicepファイルを読み、以下の修正を加えよ。

- IPアドレスは、public-ip.bicepをモジュールとして再利用
- subnetは、exists関数を使って既存リソースを参照

以下の情報はparam定義とし、デフォルト値を指定
- vpn gatewayのname:RagSystem-P2SVpn-dev
- Gateway type:VPN
- SKU:VpnGw1
- Generation:Generation1
- Virtual network:RagSystem-MainVnet-dev
- Subnet:GatewaySubnet
- Subnet address range:10.0.0.32/27
- Public IP1 name:RagSystem-P2SVpnIp1-dev
- Pubilc IP1 sku:Standard
- Pubilc IP1 Assignment:Static
- Enable active-active mode:Enabled
- Pubilc IP2 name:RagSystem-P2SVpnIp2-dev
- Pubilc IP2 sku:Standard
- Pubilc IP2 Assignment:Static
- Configure BGP:Disabled
- Enable Key Vault Access:Disabled

Point-to-site Configurationの設定は以下の通りとする。
- Address pool:192.168.10.0/24
- Tunnel type:OpenVPN(SSL)
- Authenticaion type:Azure Active Directory

Pubilc IP Address for User VPN configurationの 
- name:RagSystem-P2SVpnIpEntryPoint-dev 
- sku:Standard 
- Assignment:Static 
- TenantID:<tenantID> 

aadTenant,aadIssuerはTenantIDとの組み合わせて自動生成。 
aadAudienceは”c632b3df-fb67-4d84-bdcf-b95ad541b5c8"固定
tagsはobject型のパラメータとして、外部から提供される。
各public-ipのtagsおよびvpn-gwのtagsは共通の"tags"というパラメータを参照する。
未使用のパラメータは削除。
```

## 3-2. IPアドレスはpublic-ip.bicepをmodule参照

Github Copilotくん、ローカルファイルを読めないので、ファイルの内容を指定してあげる必要がある。

以下、指定したプロンプト。

```yaml
以下がpublic-ip.bicepの内容。
これをmoduleとして再利用したvpn-gw.bicepを生成して。
public-ip.bicep のファイルパスはカレントディレクトリ"./"とする。
ーーー
metadata description = 'Create a Azure IP by bicep template.'
targetScope = 'resourceGroup'

//////////////////////////////////////////////////////////// 
// Common parameters for the resources.

// The tags of the resource. 
param tags object

//////////////////////////////////////////////////////////// 
// The definitions of the public IP address.

// Parameters for the public IP address. 
param name string

// The public IP address. 
resource publicIp 'Microsoft.Network/publicIPAddresses@2024-01-01' = {
  name: name 
  location: resourceGroup().location 
  tags: tags

  sku: { 
    name: 'Standard' 
    tier: 'Regional' 
  } 
  properties: { 
    publicIPAllocationMethod: 'Static' 
    publicIPAddressVersion: 'IPv4' 
  } 
}

output publicIpId string = publicIp.id
```

## 3-3. 不要な条件判断処理を削除

気を利かせて、VPN GatewayのAcitve-Activeモードを切り替えられるようなパラメータも生成してくれていたが、文法が正しくなくエラーとなる状態だった。今回は常にActive-activeモードで良いことにした。

Github Copilotくんにそれを指示。

```yaml
このvpn-gwは、常にactive-activeモードで動作するよう定義する。
よって、２つめのpublic-ipを定義するかどうかの判断部分"if (enableActiveActive) {}"は不要で、常にActive-Activeを想定した定義に変更。
```

## 3-4. 不要なパラメータを確認しながら削除指示

Azure Portalでリソース作成時に指定してないパラメータがあったので、内容を確認しつつ、不要なパラメータを削除指示。

### 3-4-1. パラメータの目的とこれを指定するメリットを確認

allowRemoteVnetTrafficというパラメータは、Azure Portalで作成した時には指定していないパラメータ。これが指定されていたので、どういうものかをまずは確認。

```yaml
allowRemoteVnetTrafficはどのような場合にtrueにすべきか？
```

Copilotくんの返事。

```yaml
allowRemoteVnetTraffic を true に設定することで、VPN Gateway がリモート仮想ネットワーク間のトラフィックを許可します。
この設定は、複数の仮想ネットワーク間での通信やハブアンドスポークネットワークトポロジーでの通信を確立する場合に有効です。
```

さらに確認。

```yaml
通常、VNet間の通信は、peeringで接続すれば良いので、VPNを利用するメリットが理解できない。VNet間をVPNで接続するメリットを説明できるか？
```

Copilotくんの返事。

```yaml
VNet Peeringのメリット
1. 低レイテンシ:
  VNet Peeringは、同じリージョン内のVNet間で非常に低いレイテンシを提供します。
  データはMicrosoftのバックボーンネットワークを通じて直接転送されます。
2. 高帯域幅:
  VNet Peeringは、非常に高い帯域幅を提供し、大量のデータ転送が可能です。
3. シンプルな設定:
  VNet Peeringの設定は比較的シンプルで、管理が容易です。

VPNを利用するメリット
1. 異なるリージョン間の接続:
  VNet Peeringは同じリージョン内での接続に最適ですが、異なるリージョン間での接続にはVPNが有効です。
  グローバルVNet Peeringもありますが、VPNは異なるリージョン間での接続をより柔軟に提供します。
2. セキュリティ:
  VPNは暗号化されたトンネルを使用してデータを転送するため、セキュリティが強化されます。
  特に、インターネットを経由する場合や、セキュリティ要件が厳しい場合に有効です。
3. オンプレミスとの接続:
  VPNは、オンプレミスネットワークとAzure VNetを接続するための一般的な方法です。
  これにより、オンプレミスとクラウド間でシームレスなネットワーク接続が可能になります。
4. ハブアンドスポークトポロジー:
  ハブアンドスポークトポロジーでは、中央のハブVNetが複数のスポークVNetと接続され、スポークVNet間のトラフィックがハブVNetを経由します。
  この場合、ハブVNetのVPN Gatewayを使用して、スポークVNet間のトラフィックを管理できます。
5. 柔軟なルーティング:
  VPN Gatewayは、カスタムルートを設定することで、柔軟なルーティングポリシーを実現できます。
	これにより、特定のトラフィックを特定の経路にルーティングすることが可能です。
```

VNet間の接続にはVPNよりPeeringが最適であることを確認。

こうやって聞いていくと、どのような使い方ができるのか、どのような場合にVPNをVNet接続で利用すべきかが分かり、とても勉強になる。

今回はリージョン間のVNet接続は想定しないので、削除を指示。

```yaml
今回はallowRemoteVnetTrafficは不要なので、定義自体を削除。
```

このパラメータが理解できれば、allowVirtualWanTrafficも理解しやすい。

今回はWAN接続をVPNで実施する想定はないので、このパラメータも削除を指示。

```yaml
WANとの接続も想定外なので、allowVirtualWanTrafficも削除。
```

## 3-5. descriptinとコメントで説明を追加

```yaml
各パラメータにdescriptionを追記。各パラメータの説明を定義。
```

## 3-6. ここまでで生成できたBicepテンプレート

az コマンドででコンパイル直後のBicepからCopilotくんと共創した結果。

ほぼ完成系で、あとは、いくつかコメントを入れ、パラメータ値をbicepparamファイルに移動し、main.bicepと結合するだけ。

```yaml
targetScope = 'resourceGroup'

// パラメータの定義
@description('The location of the resource group.')
param location string = resourceGroup().location

@description('The name of the VPN Gateway.')
param vpnGatewayName string = 'RagSystem-P2SVpn-dev'

@description('The type of the gateway. For VPN Gateway, it should be "Vpn".')
param gatewayType string = 'Vpn'

@description('The SKU of the VPN Gateway.')
param sku string = 'VpnGw1'

@description('The generation of the VPN Gateway.')
param generation string = 'Generation1'

@description('The name of the virtual network.')
param virtualNetworkName string = 'RagSystem-MainVnet-dev'

@description('The name of the subnet within the virtual network.')
param subnetName string = 'GatewaySubnet'

@description('The name of the first public IP address.')
param publicIpName1 string = 'RagSystem-P2SVpnIp1-dev'

@description('The name of the second public IP address.')
param publicIpName2 string = 'RagSystem-P2SVpnIp2-dev'

@description('Whether to configure BGP (Border Gateway Protocol).')
param configureBgp bool = false

@description('The address pool for the VPN clients.')
param addressPool string = '192.168.10.0/24'

@description('The tunnel type for the VPN. For example, "OpenVPN".')
param tunnelType string = 'OpenVPN'

@description('The authentication type for the VPN. For example, "AAD".')
param authenticationType string = 'AAD'

@description('The name of the public IP address for the user VPN entry point.')
param userVpnPublicIpName string = 'RagSystem-P2SVpnIpEntryPoint-dev'

@description('The tenant ID for Azure Active Directory authentication.')
param tenantId string = '<tenantID>'

@description('The audience for Azure Active Directory authentication.')
param aadAudience string = 'c632b3df-fb67-4d84-bdcf-b95ad541b5c8'

@description('The tags to be applied to the resources.')
param tags object

// 既存のサブネットを参照
resource existingSubnet 'Microsoft.Network/virtualNetworks/subnets@2021-05-01' existing = {
  name: '${virtualNetworkName}/${subnetName}'
}

// Public IP アドレスのモジュールを再利用
module publicIp1 './public-ip.bicep' = {
  name: 'publicIp1'
  params: {
    name: publicIpName1
    tags: tags
  }
}

module publicIp2 './public-ip.bicep' = {
  name: 'publicIp2'
  params: {
    name: publicIpName2
    tags: tags
  }
}

module userVpnPublicIp './public-ip.bicep' = {
  name: 'userVpnPublicIp'
  params: {
    name: userVpnPublicIpName
    tags: tags
  }
}

// VPN Gateway の定義
resource vpnGateway 'Microsoft.Network/virtualNetworkGateways@2021-05-01' = {
  name: vpnGatewayName
  location: location
  tags: tags
  properties: {
    ipConfigurations: [
      {
        name: 'default'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: publicIp1.outputs.publicIpId
          }
          subnet: {
            id: existingSubnet.id
          }
        }
      }
      {
        name: 'activeActive'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: publicIp2.outputs.publicIpId
          }
          subnet: {
            id: existingSubnet.id
          }
        }
      }
      {
        name: 'entryPoint'
        properties: {
          privateIPAllocationMethod: 'Dynamic'
          publicIPAddress: {
            id: userVpnPublicIp.outputs.publicIpId
          }
          subnet: {
            id: existingSubnet.id
          }
        }
      }
    ]
    vpnType: 'RouteBased'
    enableBgp: configureBgp
    activeActive: true
    gatewayType: gatewayType
    sku: {
      name: sku
      tier: sku
    }
    vpnClientConfiguration: {
      vpnClientAddressPool: {
        addressPrefixes: [
          addressPool
        ]
      }
      vpnClientProtocols: [
        tunnelType
      ]
      vpnAuthenticationTypes: [
        authenticationType
      ]
      aadTenant: '${environment().authentication.loginEndpoint}${tenantId}/'
      aadAudience: aadAudience
      aadIssuer: 'https://sts.windows.net/${tenantId}/'
    }
    customRoutes: {
      addressPrefixes: []
    }
    vpnGatewayGeneration: generation
  }
  dependsOn: [
    existingSubnet
  ]
}
```

# 4. 手動で微調整

ここまでCopilotくんに手伝ってもらった。

あとは自分でmian.bicepと統合したり、bicepparamにパラメータを外だししたり、コメントを微修正したり、実際にbicepをデプロイ＆動作確認しながらデバッグして最終系に仕上げていった。

その結果は以下Githubにcommit。

https://github.com/masatoshisato/RagSystem/blob/v1.0.6/infra/network/vpn-gw.bicep