[Azure] 既存の仮想ネットワーク内にTerraformで仮想マシンを追加する

[以前、Terraformのステートファイル共有についてまとめましたが、](https://qiita.com/VA_nakatsu/items/3df4136988e3d49e3cc2)今回は既存の仮想ネットワーク内にTerraformでAzure VMを追加する手順になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/ca7fc493-3764-9b95-f4e1-a0db440b162b.png)

Azure環境も構成管理を始めよう、と言われた場合によく発生しがちな状況だと思いますが、既存のリソースをインポートしなければならないため手順は少し長めです。順に見ていきます。今回の例では、Ubuntu 18.04の仮想マシンを新規作成しています。

# Azure CLIとTerraformのインストール

Azure CLIとTerraformのインストールは [以前の記事](https://qiita.com/VA_nakatsu/items/3df4136988e3d49e3cc2#1-azure-cli%E3%81%A8terraform%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) に手順があるので参照して下さい。

# 鍵を作る

仮想マシンに設置するための鍵を作成しておきます。既存の鍵を使う場合は省略して下さい。

```
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/ubuntu/.ssh/id_rsa): /ubuntu/azureuser-key/id_rsa
Enter passphrase (empty for no passphrase):    ※パスフレーズを入力※
Enter same passphrase again:                   ※パスフレーズを入力※
※出力は省略※
$ ls -l ~/azureuser-key
total 8
-rw------- 1 root root 1766 Oct 23 06:35 id_rsa
-rw-r--r-- 1 root root  403 Oct 23 06:35 id_rsa.pub
# cat ~/azureuser-key/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2Exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ubuntu@localhost    ※以降の手順でこの鍵を設定※
```

# 1)  空のtfファイルを作成

最初が一番とっつきづらかったりするのですが(個人的意見)、まず空のtfファイルを作成します。

```terraform:my_vm.tf
provider "azurerm" {
}

resource "azurerm_resource_group" "tf_name_resource_group" {
}

resource "azurerm_virtual_network" "tf_name_vnet" {
}

resource "azurerm_subnet" "tf_name_subnet" {
}

resource "azurerm_network_security_group" "tf_name_network_security_group" {
}

resource "azurerm_network_interface" "tf_name_nic1" {
}

resource "azurerm_virtual_machine" "tf_name_vm1" {
}
```

ポイント：

- 最初の providerブロック(azurerm)は必須です。
- resource ブロックを必要な数だけ作成します。今回新規作成するのは下の2つ("azurerm_network_interface"と"azurerm_virtual_machine")だけですが、関連するリソースは既存のものであっても全て追加しておく必要があります。といっても最初に完全なリストは分からないことは多いと思いますので、その場合はTerraformのドキュメント ( VMであれば https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html ) の例にあるものを追加しておき、後の手順内で追加していきます。今回の例ではリソースグループ、VNet、サブネットに加えてセキュリティグループが必要になることが分かっているので、あらかじめ追加しています。
- resource ブロックの3項目目は<font color="blue"><b>リソースローカル名で、tfファイル名でリソースを参照するための名前</b></font>です。サンプルでは "main"とか"default"とかになっていますが、複数作成するとすぐに分かりづらくなるので、最初から命名規則を決めてきちんとした名前を付けた方がいいです。
- 実際に作成されるリソースの名前はname属性で指定します(後の手順で追記します)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/09bce4af-b1cc-f3af-fa4f-711800c9fbaf.png)

# 2) terraform init 実行

initコマンドを一度実行しておきます。正常終了のメッセージが出ればOKです。

```
$ terraform init

Initializing the backend...

※途中省略※

Terraform has been successfully initialized!

※途中省略※
```

# 3) terraform importを実行し、リソースブロックを追記

既存のリソースについては、

- terraform importコマンドでインポート処理を実行
- resource ブロック内に情報を追記

がそれぞれ必要となります。同様の手順の繰り返しになりますが、以下が実行例です。

## リソースグループ

リソースIDを確認後、terraform importコマンドでインポート処理を実行します。

```
### az groupコマンドでリソースグループのidを確認
### az group show --name ＜リソースグループ名＞
$ az group show --name resource_group_test | jq -r '.id'
/subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test 

### idを指定してimport
### terraform import azurerm_resource_group.＜リソースローカル名＞ ＜リソースグループid＞
$ terraform import azurerm_resource_group.tf_name_resource_group /subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test

※途中省略※

Import successful!

※途中省略※
```

続いてTerraformのドキュメント ( https://www.terraform.io/docs/providers/azurerm/r/resource_group.html ) を参考にresource ブロック内に情報を追記します。最終的には必要十分な項目を設定する必要がありますが、いきなり手動で完成させるのはかなり難しいので、ここでは必須と思われる項目を追加しておきます( 後ほど terraform plan コマンドで検出できます )。

```terraform
resource "azurerm_resource_group" "tf_name_resource_group" {
  name     = "resource_group_test"
  location = "Japan East"
}
```

## 仮想ネットワーク

リソースIDを確認後、terraform importコマンドでインポート処理を実行します。

```
### az networkコマンドで仮想ネットワークのidを確認
### az network vnet show --name ＜仮想ネットワーク名＞ --resource-group ＜リソースグループ名＞
$ az network vnet show --name vnet_test --resource-group resource_group_test | jq -r '.id'
/subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test/providers/Microsoft.Network/virtualNetworks/vnet_test

### idを指定してimport
### terraform import azurerm_virtual_network.＜リソースローカル名＞ ＜リソースグループid＞
$ terraform  import azurerm_virtual_network.tf_name_vnet /subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test/providers/Microsoft.Network/virtualNetworks/vnet_test

※途中省略※

Import successful!

※途中省略※
```

続いてTerraformのドキュメント ( https://www.terraform.io/docs/providers/azurerm/r/virtual_network.html ) を参考にresource ブロック内に情報を追記します。

```terraform
resource "azurerm_virtual_network" "tf_name_vnet" {
  name                = "vnet_test"
  address_space       = ["10.10.0.0/16"]
  location            = "Japan East"
  resource_group_name = "${azurerm_resource_group.tf_name_resource_group.name}"
}
```

## サブネット

リソースIDを確認後、terraform importコマンドでインポート処理を実行します。

```
### az networkコマンドでサブネットのidを確認
### az network vnet show --name ＜仮想ネットワーク名＞ --resource-group ＜リソースグループ名＞
### ※サブネット自体を取得するコマンドが無いため、vnetコマンド結果のsubnet属性で確認します。サブネットのリストが出るので対象サブネットのidを選択して下さい。
$ az network vnet show --name vnet_test --resource-group resource_group_test | jq -r '.subnets[] | [.name, .id]'
[
  "subnet_test",
  "/subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test /providers/Microsoft.Network/virtualNetworks/vnet_test/subnets/subnet_test"
]

### idを指定してimport
### terraform import azurerm_subnet.＜リソースローカル名＞ ＜リソースグループid＞
$ terraform import azurerm_subnet.tf_name_subnet /subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test /providers/Microsoft.Network/virtualNetworks/vnet_test/subnets/subnet_test

※途中省略※

Import successful!

※途中省略※
```

続いてTerraformのドキュメント ( https://www.terraform.io/docs/providers/azurerm/r/subnet.html ) を参考にresource ブロック内に情報を追記します。

```terraform
resource "azurerm_subnet" "tf_name_subnet" {
  name                 = "subnet_test"
  resource_group_name  = "${azurerm_resource_group.tf_name_resource_group.name}"
  virtual_network_name = "${azurerm_virtual_network.tf_name_vnet.name}"
  address_prefix       = "10.10.0.0/24"
}
```

## セキュリティグループ

リソースIDを確認後、terraform importコマンドでインポート処理を実行します。

```
### az networkコマンドでセキュリティグループのidを確認
### az network nsg show --name ＜セキュリティグループ名＞ --resource-group ＜リソースグループ名＞
$ az network nsg show --name nsg_test --resource-group resource_group_test | jq -r '.id'
/subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test /providers/Microsoft.Network/networkSecurityGroups/nsg_test

### idを指定してimport
### terraform import azurerm_network_security_group.＜リソースローカル名＞ ＜リソースグループid＞
$ terraform import azurerm_network_security_group.tf_name_network_security_group  /subscriptions/65b7e404-xxxxxxxxxxxxxxxxxxx/resourceGroups/resource_group_test/providers/Microsoft.Network/networkSecurityGroups/nsg-test

※途中省略※

Import successful!

※途中省略※
```

続いてTerraformのドキュメント ( https://www.terraform.io/docs/providers/azurerm/r/network_security_group.html ) を参考にresource ブロック内に情報を追記します。

```terraform
resource "azurerm_network_security_group" "tf_name_network_security_group" {
  name                = "nsg_test"
  location            = "Japan East"
  resource_group_name = "${azurerm_resource_group.tf_name_resource_group.name}"

  security_rule {
    name                       = "allow_ssh_any"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}
```

# 4) 新規作成するリソースのリソースブロックを書く

既存リソースと同様に、Terraformのドキュメント ( https://www.terraform.io/docs/providers/azurerm/r/virtual_network.html ) を参考にresource ブロック内に情報を追記します。

```terraform
resource "azurerm_network_interface" "tf_name_nic1" {
  name                = "nic-test-vm1"
  location            = "Japan East"
  resource_group_name = "${azurerm_resource_group.tf_name_resource_group.name}"

  ip_configuration {
    name                          = "testvm1configuration1"
    subnet_id                     = "${azurerm_subnet.tf_name_subnet.id}"
    private_ip_address_allocation = "Static"
    private_ip_address            = "10.10.0.5"
  }
}

resource "azurerm_virtual_machine" "tf_name_vm1" {
  name                  = "vm-test1"
  location              = "Japan East"
  resource_group_name   = "${azurerm_resource_group.tf_name_resource_group.name}"
  network_interface_ids = ["${azurerm_network_interface.tf_name_nic1.id}"]
  vm_size               = "Standard_DS2_v2"

  delete_os_disk_on_termination = true
  delete_data_disks_on_termination = true

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  storage_os_disk {
    name              = "testvm1osdisk1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  os_profile {
    computer_name  = "vm-test1"
    admin_username = "azureuser"
  }

  os_profile_linux_config {
    disable_password_authentication = true
    ssh_keys {
      path     = "/home/azureuser/.ssh/authorized_keys"
      key_data = "${file("/ubuntu/azureuser-key/id_rsa.pub")}"
    }
  }
}
```

# 5) terraform planを通す

terraform planコマンドを実行し、エラーが出た場合はtfファイルを修正します。
ここでの期待値は、リソース2個の新規作成(予定)だけが表示される状態です。

```
$ terraform plan

※途中省略※

Plan: 2 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
```

エラーが出る場合や、リソースの変更(予定)が表示される場合は、メッセージを見て修正します。例えば、

```
※途中省略※

        name                 = "subnet_test"
        resource_group_name  = "resource_group_test"
      ~ service_endpoints    = [
          - "Microsoft.Storage",
          - "Microsoft.ContainerRegistry",
        ]
        virtual_network_name = "vent_test"
    }

Plan: 2 to add, 1 to change, 0 to destroy.

※途中省略※
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/d6300b4d-86f4-6b60-e1a0-9e8912de025d.png)

仮想ネットワークにサービスエンドポイントが設定されていたため、変更(エンドポイントを削除)となってしまいました。ドキュメントを参考にtfファイル(azurerm_subnetブロック内)に設定を追加します。

```
  service_endpoints    = ["Microsoft.Storage", "Microsoft.ContainerRegistry"]
```

その他、関連するリソースが作成されていた場合などは、リソースブロック自体を追加する必要があるかもしれません。その場合は手順 3) と同様に対応します。

# 6) terraform apply実行

planコマンドの結果が問題なくなったら、terraform applyを実行します。

```
$ terraform apply

※途中省略※

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes   ※"yes"を入力

※途中省略※

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```

Apply complete!というメッセージが表示されたら作成完了です。ポータルなどで結果を確認してください。

# Link

- [Terraform docs : Resources](https://www.terraform.io/docs/configuration/resources.html)
- [Terraform docs : resource_group](https://www.terraform.io/docs/providers/azurerm/r/resource_group.html)
- [Terraform docs : virtual_network](https://www.terraform.io/docs/providers/azurerm/r/virtual_network.html)
- [Terraform docs : subnet](https://www.terraform.io/docs/providers/azurerm/r/subnet.html)
- [Terraform docs : azurerm_network_security_group](https://www.terraform.io/docs/providers/azurerm/r/network_security_group.html)
- [Terraform docs : azurerm_virtual_machine](https://www.terraform.io/docs/providers/azurerm/r/virtual_machine.html)