Azure の構成管理を ARM templates と Terraform で検討してみた

# 前書き
現在所属している組織では Azure を利用しており、これから IaC を推進していくために技術検証をはじめました。ARM templates のチュートリアル、Terraform for Azure のチュートリアル、[HashiCorp on Azure のウェビナー](https://info.microsoft.com/JA-AzureApp-WBNR-FY21-10Oct-23-AzureAppInnovation-SRDEM43379_LP02OnDemandRegistration-ForminBody.html) 、ドキュメント読み漁りで得た情報をまとめて行きます。

なお、記事作成時の筆者のスペックは Azure経験:2週間程度、ARM templates経験:20時間程度、Terraformの経験:40時間程度のぺいぺいである事をご了承ください。 :bow: GCP では僅かながら IaC (Google Cloud SDK や Terraform) を用いた構成管理の経験があります。  

# 何を使うべきかを検討する上での私のスタンス

構成管理は"1つのツールで全てやろうとするのはあまり良い考えでは無い"という考えに共感しています。なぜなら、課題に合ったツールを採用すべきであり、目的のシステムを取り巻く課題は刻々と変化するためです。  

ゴールはあくまで "インフラを継続的に再利用可能にする"であるかなと思います。  

そのため、各比較象限で ARM templates と Terraform の優劣を決めて行くのではなく、特徴を書いて行きます。 

参考1:

> We compare Terraform to a number of these tools, but it should be noted that Terraform is not mutually exclusive with other systems

by https://www.terraform.io/intro/vs/index.html

参考2:

> 自動化関連のツールの流行り廃りは早いので、1つのツールで全てやろうとするのはあまり良い考えでは無いと思います
https://engineering.mobalab.net/2018/01/10/terraform-%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E6%A7%8B%E7%AF%89/

# Terraform に関して

## 特徴
### マルチプロバイダーでも統一されたワークフローを提供する
Terraform はとにかく統一されたインフラ構築のワークフローをもたらします。インフラの構築先が Azure であっても、 GCP であっても、オンプレであっても、構築対象が VM であっても k8s であってもです。planでインフラの変更差分を確認して、apply でインフラ構築を実行します。以下の様な流れです。

```
構成ファイル(.tf)を記述
↓
init (provider plugins の取得。)
↓
plan (新規作成した場合の構築内容を表示。)
↓
apply (インフラを新規構築。stateファイルの作成。)
↓
(インフラの変更が必要になる。)
↓
構成ファイル(.tf)を修正
↓
plan -out=newplan (インフラの変更内容の確認。変更プランとして書き出し。)
↓
apply "new_plan" (変更プランを適用。)
↓
(インフラが不要になる。)
↓
destroy (インフラの破棄。)
```

### コードレビュー + CI の運用に乗せやすそう

ウェビナーでおすすめされていた運用は以下の様なものでした。

- state ファイルを blob ストレージ (または Terraform Cloud)で管理する
- 構成ファイルを記述したらコードレビューを入れる
- CI で plan を実行させ、構成ファイルと plan コマンドで出力される変更の両方を確認する
- apply は必ず CI に実行させる
- 新規にインフラを作る場合は必ず Terraform を使って Azure Portal から作成をしない
- Azure の Provider できない値指定は ARM templates と併用するか Provider に Pull Request を投げる

インフラ構築・修正がコードレビューした上で、CIから実行するフローはとても素敵ですね :heart_eyes:

ちなみに、既存AzureリソースはHCLに置き換えるとができないとウェビナーでは回答頂いたのですが、`terraform　import`を利用する事で、stateファイルを作成する事ができました。

### 運用のためのTerraform Cloud が用意されている

私は試していませんがこちらの記事を拝見させて頂いた限り、とても良さそうです :heart_eyes: 
https://blog.shibayan.jp/entry/20200910/1599749096

### その他特徴
- 構成ファイル(.tf)が読みやすい
- 構成ファイルはコメントが書ける 
- VS Code にも JetBrain のIDEでもプラグインがある

## 気になるところ

気になるところもいくつかありました。

### Provider が Azure の変更に対応するか
ウェビナーで質問したところ、OSS なので Azure の変更への追随には多少遅れが生じるため、是非コントリビュートして欲しいという言葉をいただきました。 Go (Terraform本体・Provider共に)で書かれており、私には残念ながら難しいかもしれません。    

ソースの更新は頻繁であるように感じました。
https://github.com/terraform-providers/terraform-provider-azurerm/pulse/monthly  

HashiCorp のサイトには強力なパートナーシップが長年実現されてきた旨が書かれています。
https://www.hashicorp.com/cloud-partners/microsoft?product=terraform

### Terraform はあくまで Wrapper であること
Azureの各インフラのAPIバージョンなどの細かい指定や細かな属性調整ができるのか不安です。
(ろくに内部のコードを読まずにこんなことを言って申し訳ない)

## 手を動かしてみる

チュートリアル+アルファを実行したメモを置いておくので、雰囲気を見るのに役立ててください。

- チュートリアル
    - https://learn.hashicorp.com/tutorials/terraform/infrastructure-as-code?in=terraform/azure-get-started

### 環境設定

- azsure cli を install する
- install
    - `brew install hashicorp/tap/terraform`
    - `Terraform v0.13.5`
    - `terraform -install-autocomplete`
    - vscode の pligin を install
       - `hashicorp.terraform`
- azure でリソースグループ `terra_test` を作成
    - location: `japaneast` だけを設定する
- `cat ~/ghq/github.com/github/gitignore/Terraform.gitignore >> .gitignore`

### 既存のインフラを後からコード化する
- https://dev.classmethod.jp/articles/aws-with-terraform/

- ex. recource group のマイニング
    - https://www.terraform.io/docs/providers/azurerm/r/resource_group.html
    - (`terraform import azurerm_resource_group.rg /subscriptions/<subscription_id>/resourceGroups/terra_test`)

- tfstateが存在する場合に、状態を確認する流れ

```
$ terraform state list
azurerm_resource_group.rg

$ terraform state show azurerm_resource_group.rg
# azurerm_resource_group.rg:
resource "azurerm_resource_group" "rg" {
    id       = "/subscriptions/<subscription_id>/resourceGroups/terra_test"
    location = "japaneast"
    name     = "terra_test"
    tags     = {}

    timeouts {}
}
```

### インフラを変更する

`main.tf` を以下のように変更する

```
resource "azurerm_resource_group" "rg" {
  name     = "terra_test"
  location = "japaneast"

  tags = {
        Environment = "Terraform Getting Started"
    }
}
```

`terraform plan -out=newplan` を実行する。  
変更箇所が分かりやすい。

```
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.rg will be updated in-place
  ~ resource "azurerm_resource_group" "rg" {
        id       = "/subscriptions/<subscription_id>/resourceGroups/terra_test"
        location = "japaneast"
        name     = "terra_test"
      ~ tags     = {
          + "Environment" = "Terraform Getting Started"
        }

        timeouts {}
    }

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

- `terraform apply "newplan"` を適用する。 
    - `Apply complete! Resources: 0 added, 1 changed, 0 destroyed.` が表示される
    - `terraform state show azurerm_resource_group.rg` すると結果が変わっている

### インフラを削除する

- `terraform destroy`

- 実行するかどうかが表示される

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

Terraform will perform the following actions:

  # azurerm_resource_group.rg will be destroyed
  - resource "azurerm_resource_group" "rg" {
      - id       = "/subscriptions/<subscription_id>/resourceGroups/terra_test" -> null
      - location = "japaneast" -> null
      - name     = "terra_test" -> null
      - tags     = {
          - "Environment" = "Terraform Getting Started"
        } -> null

      - timeouts {}
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value:
```

# Terraform 関連のリンク

有用だったリンクや公式ドキュメントのリンクを置いておきます。

- Azure Doc 上の Terraform
    - https://docs.microsoft.com/ja-jp/azure/developer/terraform/
- Terraform Doc の Azure Provider
    - https://www.terraform.io/docs/providers/azurerm/index.html
- Terraform ソース
    - https://github.com/hashicorp/terraform
- Azure Provider ソース
    - https://github.com/terraform-providers/terraform-provider-azurerm
- とにかくここからはじめよう
    - https://learn.hashicorp.com/collections/terraform/azure-get-started
- Terraform Cloudのレビュー記事
    - https://blog.shibayan.jp/entry/20200910/1599749096
- チュートリアル + アルファの手順
    - https://github.com/nnashiki/start-terraform-azure

# ARM templates に関して

## ARM Templates の特徴

### Azure の新しいサービスと機能をすぐに利用できる
公式の"ARM テンプレートを選択する理由"に記載があります。
https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/overview#why-choose-arm-templates

### パラメータをファイル管理できる
テンプレートとパラメータに分けて管理できます :heart_eyes: 
https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/parameter-files

### what-if と呼ばれるもので dry run ができる (preview)
状態ファイルが無くても what-if コマンドで dry run ができます :heart_eyes: 

### 既存リソースを定義ファイルにエクスポートができる
ポータルから手軽に作成済のリソースをテンプレートとしてエクスポートする事ができます:heart_eyes: 

### その他特徴

- モジュール化できる
- 状態ファイルを管理しない
- 相互に依存するリソースのデプロイが調整され、正しい順序で作成される
- Azure Key Vault と連携してセキュアな管理ができる
- 日本語ドキュメントが充実している
- VScode の Azure Resource Manager (ARM) Tools のスニペットが強力
- 定義ファイルはJSON
    - パラメーターを説明は metadata に記載する

```    
"parameters": {
     "storageAccountType": {
         "type": "string",
         "metadata": {
             "description": "The type of the new storage account created to store the VM disks."
         }
     }
}
```

## 手を動かしてみる

[チュートリアル - 初めての ARM テンプレートを作成してデプロイする](https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/template-tutorial-create-first-template?tabs=azure-powershell)を実施し、VM の立ち上げ + VMリソースの修正をしました。雰囲気を感じられると思うので手順と結果を置いておきます。

- VSCodeの拡張機能(ARMTools) を準備する
    - [拡張機能](https://marketplace.visualstudio.com/items?itemName=msazurermtools.azurerm-vscode-tools)を準備する
    - `.net core`が入ってないと、パラメータのスニペットが動かない
    - mac に wget のコマンドが入っていなかったので、 VSCode をがエラーを吐いていた
    - VSCode を再起動すると`.net core`が再度インストールが始まる
    - "azuredeploy.json"のファイルタイプを`AzureResourveManagerTemplate`に変更しないとスニペットが効かない
- [チュートリアル 初めての ARM テンプレートを作成してデプロイする](https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/templates/template-tutorial-create-first-template?tabs=azure-powershell)を実施する
- VM をデプロイする用のリソースグループを作成する
    - `az group create --name armt_test --location japaneast`
- ポータルからVMのスペックを選択して、テンプレートとパラメータをエクスポートする
    - スクラッチでいきなり書くのは厳しいので
- エクスポートしたテンプレートとパラメータを DRY RUN する
    - `az deployment group what-if --resource-group armt_test --template-file template.json --parameters parameters.json`
    - 新規作成は緑で表示されます
        - ![armt_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110482/2a2a1940-953f-0f54-cf36-0c007c1c3af6.png)
        - 中略
        - ![armt_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110482/05f684fd-b166-a772-4810-5fa863543984.png)
- エクスポートしたテンプレートとパラメータをデプロイする
    - `az deployment group create --resource-group armt_test --template-file template.json --parameters parameters.json`
- VMが作成された事をポータルから確認する
- VMのパラメータを変更した parameters2.json を作成する
- parameters2.json を DRY RUN する
    - `az deployment group what-if --resource-group armt_test --template-file template.json --parameters parameters2.json`
    - change は delete create で表現されています
        - ![armt_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110482/5bb785a7-51ac-84a1-7670-b4b261640507.png)
        - 中略
        - ![armt_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110482/792a2f13-41fd-f8ba-5035-d85b4a89e828.png)
- parameters2.json を適用する
    - `az deployment group create --resource-group armt_test --template-file template.json --parameters parameters2.json`
- リソースグループを削除する
    - `az group delete --name armt_test`


# 所感
少し触ったり調べた程度だと どちらも DRY RUN 、 コードレビューができ、IaC をする上で申し分ないように感じます。
ただし、大規模化すると使い勝手も変わってくるかもしれません。

以上でございます。