[Azure] Terraformで複数人がAzureリソースを作成(変更)できる環境を設定する

Terraformは、インフラリソースの構築・変更・バージョン管理を行うためのツールです。コードはTerraform Configuration File( .tf形式または .tf.json形式)で記載します。
[Providers](https://www.terraform.io/docs/providers/index.html) にある通り、多数のクラウドサービスに対応していますが、QiitaなどでもAWSの構築記事を見かけることが多いかと思います。

サジェストにも出てこないAzure...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/7a7aae08-33f8-fe8e-767b-75b74b1b859b.png" width="450px">

本記事は、TerraformでAzureリソースの作成・変更(複数人でのステートファイル共有)をするための手順となります。

図にするほどでもないがやっていること：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/1a62cf2c-a76e-767a-f11e-c03acd6daa71.png)

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

実行にはAzure CLI 2.0(azコマンド)が必要となります。
[Azure CLI のインストール](https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest)にある通りのコマンドを実行してインストールします。以降の手順は、Ubuntu 18.04で実行した手順です。

```console
※インストール実行※
$ which az
$ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
※出力は長いので省略※

※インストール結果の確認※
$ which az
/usr/bin/az
$ az --version
azure-cli                         2.0.68

command-modules-nspkg               2.0.3
core                              2.0.68
nspkg                              3.0.4
telemetry                          1.0.3

Python location '/opt/az/bin/python3'
Extensions directory '/home/azureuser/.azure/cliextensions'

Python (Linux) 3.6.5 (default, Jun 28 2019, 06:19:45)
[GCC 7.4.0]

Legal docs and information: aka.ms/AzureCliLegal


Your CLI is up-to-date.
```

terraformはダウンロード・解凍してできる単一ファイルにパスを通すだけです。[Download Terraform](https://www.terraform.io/downloads.html)から最新版のパスを確認して指定します。

```bash
※ダウンロード※
$ wget https://releases.hashicorp.com/terraform/0.12.3/terraform_0.12.3_linux_amd64.zip
--2019-07-05 06:15:42--  https://releases.hashicorp.com/terraform/0.12.3/terraform_0.12.3_linux_amd64.zip
Resolving releases.hashicorp.com (releases.hashicorp.com)... 151.101.77.183, 2a04:4e42:15::439
Connecting to releases.hashicorp.com (releases.hashicorp.com)|151.101.77.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16036886 (15M) [application/zip]
Saving to: ‘terraform_0.12.3_linux_amd64.zip’

terraform_0.12.3_linux_amd64.zip      100%[=========================================================================>]  15.29M  23.3MB/s    in 0.7s

2019-07-05 06:15:43 (23.3 MB/s) - ‘terraform_0.12.3_linux_amd64.zip’ saved [16036886/16036886]

※解凍してパスを通す※
$ unzip terraform_0.12.3_linux_amd64.zip
Archive:  terraform_0.12.3_linux_amd64.zip
  inflating: terraform

$ sudo mkdir /opt/terraform0.12.3
$ sudo cp terraform /opt/terraform0.12.3
$ sudo ln -s /opt/terraform0.12.3/terraform /usr/local/bin/terraform

※確認※
$ which terraform
/usr/local/bin/terraform
$ terraform -v
Terraform v0.12.3
```

# 2) ステートファイル共有場所(ストレージアカウントとコンテナ)作成

Terraformでは管理しているインフラの状態をステートファイル( .tfstate)というファイルで管理してます。
ステートファイルが正しく共有できていないと、変更のつもりが同名リソースの新規作成になってしまった、といったことが発生するので注意が必要です。Azureなので、今回はBlobストレージ内に共有用のコンテナを1つ作成しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/4e67622d-9557-57ff-eefa-59deebed52fc.png)

# 3) リソースグループを作成・変更してみる

## 1台目の操作(リソースグループ作成)

リソースグループ作成のための .tfファイルを作成します。ステートファイル設定のところは実行する環境に合わせて修正して下さい。

```text:azure.tf
# Azure Providerの設定
provider "azurerm" {
}

# ステートファイルの設定
terraform {
    backend "azurerm" {
        storage_account_name  = "<先ほど作成したストレージアカウント名>"
        container_name        = "<先ほど作成したコンテナ名>"
        key                   = "terraform.tfstate" # ステートファイル名
        resource_group_name  = "<先ほど作成したストレージアカウントのリソースグループ>"
    }
}

# 作成するリソースグループの設定
resource "azurerm_resource_group" "test_resource_group" {
    name     = "test_resource_group"
    location = "Japan East"
}
```

Azure CLIにログイン。サブスクリプションが複数ある場合は、az account setコマンドで対象のサブスクリプションを設定しておきます。

```
※ログイン※
$ az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code GZYxxxxxxx to authenticate.
※→ 表示されたURLをブラウザで表示し、表示されたコードを入力する※

※アカウント(サブスクリプション)一覧の確認※
$ az account list --output table
Name                             CloudName    SubscriptionId       State    IsDefault
-------------------------------  -----------  -------------------  -------  -----------
Azure_Dev                        AzureCloud   939cxxxxxxx-xxxxxxx  Enabled  True
Visual Studio Enterprise MPN     AzureCloud   0ce0xxxxxxx-xxxxxxx  Enabled  False
visionarts-demo-env              AzureCloud   65b7xxxxxxx-xxxxxxx  Enabled  False

※作業対象サブスクリプションの設定※
$ az account set --subscription "visionarts-demo-env"

※IsDefaultが設定されたことの確認※
$ az account list --output table
Name                             CloudName    SubscriptionId       State    IsDefault
-------------------------------  -----------  -------------------  -------  -----------
Azure_Dev                        AzureCloud   939cxxxxxxx-xxxxxxx  Enabled  False
Visual Studio Enterprise MPN     AzureCloud   0ce0xxxxxxx-xxxxxxx  Enabled  False
visionarts-demo-env              AzureCloud   65b7xxxxxxx-xxxxxxx  Enabled  True
```

.tfファイルを置いたディレクトリでterraform initを実行

```
$ terraform init

Initializing the backend...

Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "azurerm" (terraform-providers/azurerm) 1.31.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.azurerm: version = "~> 1.31"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

terraform planを実行し、リソースグループが1つ作成(予定)と表示されることを確認する。

```
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


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

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.test_resource_group will be created
  + resource "azurerm_resource_group" "test_resource_group" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "test_resource_group"
      + tags     = (known after apply)
    }

Plan: 1 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.
```

terraform applyを実行（途中確認を求められるので"yes"を入力）

```
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.test_resource_group will be created
  + resource "azurerm_resource_group" "test_resource_group" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "test_resource_group"
      + tags     = (known after apply)
    }

Plan: 1 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

azurerm_resource_group.test_resource_group: Creating...
azurerm_resource_group.test_resource_group: Creation complete after 2s [id=/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group]

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

リソースグループが実際に作成されたことを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/0449f70b-0b43-eb9c-4e03-b36c4bd7d009.png)

また、指定したコンテナ内にステートファイルが作成されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/c67f0541-b6b6-985c-afeb-0e2b77af8ce1.png)

## 2台目の操作(リソースグループ変更)

続いて、2台目の作業です。インストールとログイン・対象サブスクリプションへの切り替え手順は、1台目と全く同じ手順を実施しておきます。

変更用の .tfファイルを作成します。差分は最後の4行で、タグを追加しています。

```text:azure2.tf
# Azure Providerの設定
provider "azurerm" {
}

# ステートファイルの設定
terraform {
    backend "azurerm" {
        storage_account_name  = "<1台目で設定したのと同じストレージアカウント名>"
        container_name        = "<1台目で設定したのと同じコンテナ名>"
        key                   = "terraform.tfstate" # ステートファイル名
        resource_group_name  = "<1台目で設定したのと同じリソースグループ名>"
    }
}

# 作成するリソースグループの設定
resource "azurerm_resource_group" "test_resource_group" {
    name     = "test_resource_group"
    location = "Japan East"

    tags = {
        environment = "Demo"
    }
}
```

terraform initの実行。

```console
$ terraform init

Initializing the backend...

Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "azurerm" (terraform-providers/azurerm) 1.31.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.azurerm: version = "~> 1.31"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```
次に terraform planを実行し、リソースグループが1つ変更(予定)と表示されることを確認します。
ここで 1台目と同じ新規作成(予定)が表示される場合はステートファイルの参照に失敗しているので、ここまでの手順を再確認して下さい。

```console
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

azurerm_resource_group.test_resource_group: Refreshing state... [id=/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group]

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

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_resource_group.test_resource_group will be updated in-place
  ~ resource "azurerm_resource_group" "test_resource_group" {
        id       = "/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group"
        location = "japaneast"
        name     = "test_resource_group"
      ~ tags     = {
          + "environment" = "Demo"
        }
    }

Plan: 0 to add, 1 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.

```

terraform applyを実行（1台目同様確認に"yes"を入力）

```console
$ terraform apply
azurerm_resource_group.test_resource_group: Refreshing state... [id=/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_resource_group.test_resource_group will be updated in-place
  ~ resource "azurerm_resource_group" "test_resource_group" {
        id       = "/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group"
        location = "japaneast"
        name     = "test_resource_group"
      ~ tags     = {
          + "environment" = "Demo"
        }
    }

Plan: 0 to add, 1 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

azurerm_resource_group.test_resource_group: Modifying... [id=/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group]
azurerm_resource_group.test_resource_group: Modifications complete after 3s [id=/subscriptions/65b7xxxxxxx-xxxxxxx/resourceGroups/test_resource_group]

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

リソースグループにタグが付与されたことを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/66f29668-1f3a-3219-b8fe-73438e0c0808.png)

また、2台目の作業によってステートファイルが更新されたことが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/8eeee7c3-835c-e4f9-9fa7-3a16ca4e0600.png)

以上でTerraformによるAzureリソースの作成・変更(複数人でのステートファイル共有)が確認できました。

# 参考資料

- [Introduction to Terraform](https://www.terraform.io/intro/index.html)
- [Download Terraform](https://www.terraform.io/downloads.html)
- [Terraform: Azure Provider](https://www.terraform.io/docs/providers/azurerm/index.html)
- [Azure CLI のインストール](https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest)