ARMテンプレートを使って、仮想マシン等のdev環境のコピーを別のリソースグループに作成する

# はじめに

プロジェクトを進めていく中で、使用しているdev環境と同じ環境のコピーが欲しくなることがままあると思います。

少ない数のリソースで構成された環境であればポータルでポチポチとやっていくのもありですが、リソースの数が多い場合など、手作業でポチポチやるのはかなり手間な作業だし、また間違いも犯しやすくなるので、できれば、コード管理してやっていきたいところです。

AzureではARMテンプレートでリソースのコード管理ができるので、できるだけARMテンプレートを使って、仮想マシン等のdev環境のコピーを別のリソースグループに作成してみたいと思います。

# やってみる

## 下準備

まずは、コピー元となるリソースが必要なので、今回は、オフィシャルの[Azure Resource Manager テンプレートを使用して Linux 仮想マシンを作成する方法](https://docs.microsoft.com/ja-jp/azure/virtual-machines/linux/create-ssh-secured-vm-from-template)より「Deploy to Azure」ボタンを押してリソースを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/1913dd19-8c5f-d5c4-ed6f-cdcdc42fb9b5.png)
リソースグループをCurrentGroup、Project NameをCurrentProjectとして作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/fee215c0-4fba-a09f-bd47-3011b57696c2.png)
正常にデプロイが完了すると、上記のように７つのリソースが作成されます。

SSHでアクセスできることを確認しておきます。

## リソースグループの作成

今回、NewGroupという新しいリソースグループにCurrentGroupの環境をコピーしたいと思いますので、まずは下記コマンドでNewGroupというリソースグループを作成します。

```bash
az group create -l japaneast -n NewGroup
```

## スナップショットの作成

CurrentGroupの仮想マシンをコピーするにあたり、コピー元の仮想マシンのスナップショットをとって、そちらから新しい仮想マシンを複製する方法をとるので、まずはコピー元の仮想マシンのススナップショットを作成します。

```bash
osDiskId=$(az vm show \
  -g CurrentGroup \
  -n CurrentProject-vm \
  --query "storageProfile.osDisk.managedDisk.id" \
  -o tsv)

az snapshot create \
  -g NewGroup \
  --source "$osDiskId" \
  --name CurrentProject-vm-disk1-snapshot
```

NewGroupのリソースグループ内にCurrentProject-vm-disk1-snapshotというスナップショットが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/4be8faa3-e754-b42c-3bf6-a80d10964ee0.png)

## ARMテンプレートのエクスポート

CurrentGroupのリソースグループのオートメーション→テンプレートのエクスポートより、ARMテンプレートをエクスポートします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/bc5334ab-241a-555f-43f4-31807fd80c78.png)

CurrentGroup内のディスクのARMテンプレートは一緒にエクスポートされないので、別途ディスクのオートメーション→テンプレートのエクスポートより、ARMテンプレートをエクスポートします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/795e410f-9143-4b69-ce22-c34f8a627c3f.png)

## ARMテンプレートの編集

上記２つのテンプレートを編集・統合し、NewGroup用のARMテンプレートを作成します。

まずは、ディスクのテンプレートから編集していきます。

```json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Compute/disks",
            "apiVersion": "2019-07-01",
            "name": "NewProject-vm_disk",
            "location": "japaneast",
            "sku": {
                "name": "Premium_LRS",
                "tier": "Premium"
            },
            "properties": {
                "osType": "Linux",
                "creationData": {
                    "createOption": "Copy",
                    "sourceResourceId": "[resourceId('Microsoft.Compute/snapshots', 'CurrentProject-vm-disk1-snapshot')]"
                },
                "diskSizeGB": 30,
                "diskIOPSReadWrite": 120,
                "diskMBpsReadWrite": 25,
                "encryption": {
                    "type": "EncryptionAtRestWithPlatformKey"
                }
            }
        }
    ]
}
```

下記の変更を行いました。

1. CurrentProjectとなっている箇所をNewProjectに変更
2. properties.creationDataをイメージからではなくスナップショットから作成するように変更

続いて、リソースグループのテンプレートを編集します。先ほど編集したテンプレートについてもこちらに統合していきます。

```json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Compute/disks",
            "apiVersion": "2019-07-01",
            "name": "NewProject-vm_disk",
            "location": "japaneast",
            "sku": {
                "name": "Premium_LRS",
                "tier": "Premium"
            },
            "properties": {
                "osType": "Linux",
                "creationData": {
                    "createOption": "Copy",
                    "sourceResourceId": "[resourceId('Microsoft.Compute/snapshots', 'CurrentProject-vm-disk1-snapshot')]"
                },
                "diskSizeGB": 30,
                "diskIOPSReadWrite": 120,
                "diskMBpsReadWrite": 25,
                "encryption": {
                    "type": "EncryptionAtRestWithPlatformKey"
                }
            }
        },
        {
            "type": "Microsoft.Network/networkSecurityGroups",
            "apiVersion": "2020-05-01",
            "name": "NewProject-nsg",
            "location": "japaneast",
            "properties": {
                "securityRules": [
                    {
                        "name": "ssh_rule",
                        "properties": {
                            "description": "Locks inbound down to ssh default port 22.",
                            "protocol": "Tcp",
                            "sourcePortRange": "*",
                            "destinationPortRange": "22",
                            "sourceAddressPrefix": "*",
                            "destinationAddressPrefix": "*",
                            "access": "Allow",
                            "priority": 123,
                            "direction": "Inbound",
                            "sourcePortRanges": [],
                            "destinationPortRanges": [],
                            "sourceAddressPrefixes": [],
                            "destinationAddressPrefixes": []
                        }
                    }
                ]
            }
        },
        {
            "type": "Microsoft.Network/networkSecurityGroups",
            "apiVersion": "2020-05-01",
            "name": "default-nsg",
            "location": "japaneast",
            "properties": {
                "securityRules": [
                    {
                        "name": "default-allow-22",
                        "properties": {
                            "protocol": "Tcp",
                            "sourcePortRange": "*",
                            "destinationPortRange": "22",
                            "sourceAddressPrefix": "*",
                            "destinationAddressPrefix": "*",
                            "access": "Allow",
                            "priority": 1000,
                            "direction": "Inbound",
                            "sourcePortRanges": [],
                            "destinationPortRanges": [],
                            "sourceAddressPrefixes": [],
                            "destinationAddressPrefixes": []
                        }
                    }
                ]
            }
        },
        {
            "type": "Microsoft.Network/publicIPAddresses",
            "apiVersion": "2020-05-01",
            "name": "NewProject-ip",
            "location": "japaneast",
            "sku": {
                "name": "Basic"
            },
            "properties": {
                "publicIPAddressVersion": "IPv4",
                "publicIPAllocationMethod": "Dynamic",
                "idleTimeoutInMinutes": 4,
                "ipTags": []
            }
        },
        {
            "type": "Microsoft.Compute/virtualMachines",
            "apiVersion": "2019-07-01",
            "name": "NewProject-vm",
            "location": "japaneast",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkInterfaces', 'NewProject-nic')]",
                "[resourceId('Microsoft.Compute/disks', 'NewProject-vm_disk')]"
            ],
            "properties": {
                "hardwareProfile": {
                    "vmSize": "Standard_D2s_v3"
                },
                "storageProfile": {
                    "osDisk": {
                        "osType": "Linux",
                        "name": "NewProject-vm_disk",
                        "createOption": "Attach",
                        "caching": "ReadWrite",
                        "managedDisk": {
                            "storageAccountType": "Premium_LRS",
                            "id": "[resourceId('Microsoft.Compute/disks', 'NewProject-vm_disk')]"
                        },
                        "diskSizeGB": 30
                    },
                    "dataDisks": []
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces', 'NewProject-nic')]"
                        }
                    ]
                }
            }
        },
        {
            "type": "Microsoft.Network/networkSecurityGroups/securityRules",
            "apiVersion": "2020-05-01",
            "name": "NewProject-nsg/ssh_rule",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkSecurityGroups', 'NewProject-nsg')]"
            ],
            "properties": {
                "description": "Locks inbound down to ssh default port 22.",
                "protocol": "Tcp",
                "sourcePortRange": "*",
                "destinationPortRange": "22",
                "sourceAddressPrefix": "*",
                "destinationAddressPrefix": "*",
                "access": "Allow",
                "priority": 123,
                "direction": "Inbound",
                "sourcePortRanges": [],
                "destinationPortRanges": [],
                "sourceAddressPrefixes": [],
                "destinationAddressPrefixes": []
            }
        },
        {
            "type": "Microsoft.Network/networkSecurityGroups/securityRules",
            "apiVersion": "2020-05-01",
            "name": "default-nsg/default-allow-22",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkSecurityGroups', 'default-nsg')]"
            ],
            "properties": {
                "protocol": "Tcp",
                "sourcePortRange": "*",
                "destinationPortRange": "22",
                "sourceAddressPrefix": "*",
                "destinationAddressPrefix": "*",
                "access": "Allow",
                "priority": 1000,
                "direction": "Inbound",
                "sourcePortRanges": [],
                "destinationPortRanges": [],
                "sourceAddressPrefixes": [],
                "destinationAddressPrefixes": []
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2020-05-01",
            "name": "NewProject-vnet",
            "location": "japaneast",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkSecurityGroups', 'default-nsg')]"
            ],
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/16"
                    ]
                },
                "subnets": [
                    {
                        "name": "default",
                        "properties": {
                            "addressPrefix": "10.0.0.0/24",
                            "networkSecurityGroup": {
                                "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'default-nsg')]"
                            },
                            "delegations": [],
                            "privateEndpointNetworkPolicies": "Enabled",
                            "privateLinkServiceNetworkPolicies": "Enabled"
                        }
                    }
                ],
                "virtualNetworkPeerings": [],
                "enableDdosProtection": false,
                "enableVmProtection": false
            }
        },
        {
            "type": "Microsoft.Network/networkInterfaces",
            "apiVersion": "2020-05-01",
            "name": "NewProject-nic",
            "location": "japaneast",
            "dependsOn": [
                "[resourceId('Microsoft.Network/publicIPAddresses', 'NewProject-ip')]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'NewProject-vnet', 'default')]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipconfig1",
                        "properties": {
                            "privateIPAddress": "10.0.0.4",
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses', 'NewProject-ip')]"
                            },
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'NewProject-vnet', 'default')]"
                            },
                            "primary": true,
                            "privateIPAddressVersion": "IPv4"
                        }
                    }
                ],
                "dnsSettings": {
                    "dnsServers": []
                },
                "enableAcceleratedNetworking": false,
                "enableIPForwarding": false
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2020-05-01",
            "name": "NewProject-vnet/default",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', 'NewProject-vnet')]",
                "[resourceId('Microsoft.Network/networkSecurityGroups', 'default-nsg')]"
            ],
            "properties": {
                "addressPrefix": "10.0.0.0/24",
                "networkSecurityGroup": {
                    "id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'default-nsg')]"
                },
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        }
    ]
}
```

下記の変更を行いました。

1. CurrentProjectとなっている箇所をNewProjectに変更
2. Microsoft.Compute/disksをこちらに統合
3. 仮想マシンの作成がdisksの作成の後になるようにMicrosoft.Compute/virtualMachinesのdependsOnに追加
4. Microsoft.Compute/virtualMachinesのproperties.storageProfile.osDiskの名前を変更し、createOptionをAttachに変更
5. Microsoft.Compute/virtualMachinesのproperties.storageProfile.imageReferenceと、properties.storageProfile.osProfileを削除
6. Microsoft.Network/publicIPAddressesのproperties.ipAddressを削除

## デプロイ

編集したテンプレートをNewGroupにデプロイします。

```bash
az deployment group create -g NewGroup --template-file template.json
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/bb2ff272-e034-bf1f-dc0b-7e5af44478ef.png)

正常にデプロイが完了すると、上記のように７つのリソースが作成されます。（スナップショット入れると8つ）

こちらも、SSHでアクセスできることを確認しておきます。

# さいごに

デプロイ時にパラメータを渡すことで、一つのテンプレートで複数環境にデプロイできるようにしたりするなど、まだまだブラッシュアップできる点は多いかと思いますが、とりあえずはARMテンプレートで環境のコピーを作成することはできたと思います。

複数環境に対してコードで管理できるようになると、ステージング・本番の環境の整合性を管理できたり、複数の開発環境を同時に整備できたりなど、メリットはかなり大きいと思います。