Terraform CLIの環境をTerraform Cloudに移行してみた

# はじめに
Terraform CLIの環境をTerraform Cloudに移行してみました。
その際にローカルとは違う部分や詰まったところを書いていきます。
ローカル（作業端末）はWindows 11で構築するクラウドのリソースはAzureです。

# Terraform Cloudの準備
まずはアカウント登録してWorkspaceを作成します。
今回はworkflowを`CLI-driven`にしました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/dcd2ca64-3131-1b73-ea87-33127b2d3354.png)


workspaceの設定です。
Execution ModeはRemoteにします。
terraformの実行環境をterraform Cloudに任せるタイプです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/d4199900-9349-ded3-35cb-d0078c70ad0c.png)

後は適当に。

# Terraform CLIでのModuleの構成
Terraform CLIでは共通的な部分をModuleとして利用していました。
各リソースを作成する定義を用意しておき、変数を渡して複数作成できるようにしていました。

```plaintext:ローカルのフォルダ構成
home
│  recovery_services_vault.tf
│  resource_group.tf
│  storage_account.tf
│  variables.tf
│  virtual_network.tf
├─env
│  ├─dev
│  │      main.tf
│  │      variables.tf
│  └─prd    
│
└─modules
    ├─lb
    │      lb.tf
    │      outputs.tf
    │      variables.tf
    ├─nsg
    │      nsg.tf
    │      variables.tf
    └─vm
            manageddisk.tf
            outputs.tf
            variables.tf
            vm.tf
```

呼び出しの指定はこんな感じ。
実行ディレクトリからの相対パスです。

```terraform
module "main" {
  source = "../../"

  resource_group            = local.resource_group
  virtual_network           = local.virtual_network
  subnet                    = local.subnet
}

module "vm01" {
  source = "../../modules/vm/"

  vm              = local.vm01_vm
  data_disk       = local.vm01_data_disk
  virtual_network = local.virtual_network.vnet1
  subnet          = local.subnet.sub1
}

```

# Terraform CloudでのModuleの構成
Module自体はGitHubやS3等色々なサービスで使用出来ますが今回は折角Terraform Cloudを使うので
Terraform Registryに登録してみます。


## Module用GitHubリポジトリの作成
Module登録にはGitHubのリポジトリを指定する必要があります。
そのためGitHubのリポジトリを事前に作成しておきます。
リポジトリ名は`terraform-<PROVIDER>-<NAME>`とする必要がありますのでAzureの場合、
terraform-azurerm-devmodule 等のようにします。
また、リリースしている必要もありますので忘れずにリリースもします。

なお、リポジトリはPrivateで大丈夫です。

```plaintext:リポジトリの構成
home
│  availability.tf
│  recovery_services_vault.tf
│  resource_group.tf
│  storage_account.tf
│  variables.tf
│  virtual_network.tf
└─modules
    ├─lb
    │      lb.tf
    │      outputs.tf
    │      variables.tf
    ├─nsg
    │      nsg.tf
    │      variables.tf
    └─vm
            manageddisk.tf
            outputs.tf
            variables.tf
            vm.tf
```

## Terraform RegistryにModule登録
上記のリポジトリを登録

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/d14a0298-395b-4f83-0fe8-72a3278216fe.png)


![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/35a36263-80b4-fc70-14ce-c4f026405506.png)

リポジトリが命名規則に則っていない場合は一覧に表示されません。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/5572aad2-bb0b-f057-d731-733cb1ebda10.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/35bf4701-d42b-76d9-e84f-8ab5d3f120ec.png)


Terraform Registryでバージョン毎の構成を見れます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/7b905d54-e04c-836d-600b-ffee242f107c.png)


上記のようなフォルダ構成にした場合は`modules`以下のフォルダはサブモジュールとして登録されます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/60c0458b-76b6-9331-c317-19176b72a224.png)

# Moduleの呼び出し
Moduleの呼び出し記述は`app.terraform.io/<organization>/module/azurerm`となります。
Moduleのversionも指定します。
サブモジュールを指定する場合は基本のパスの最後に`/`を2つ入れた後に配下のフォルダ名となります。

```terraform:main.tf

module "main" {
  source  = "app.terraform.io/<organization>/module/azurerm"
  version = "1.0.4"

  resource_group            = local.resource_group
  virtual_network           = local.virtual_network
  subnet                    = local.subnet
}

module "vm01" {
  source  = "app.terraform.io/<organization>/module/azurerm//modules/vm/"
  version = "1.0.4"

  vm              = local.vm01_vm
  data_disk       = local.vm01_data_disk
  virtual_network = local.virtual_network.vnet1
  subnet          = local.subnet.sub1
}

```

Terraform Cloudで実行するように定義していきます。
Terraform Cloudのorganizationとworkspace名を指定します。
テナントIDとサブスクリプションIDはTerrform Cloudの`Variables`でも登録出来ますが、
気軽に変更出来るようにローカル環境で定義します。


```terraform:remote.tf
terraform {
  cloud {
    organization = "<organization name>"

    workspaces {
      name = "workspace name"
    }
  }
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = "<subscription_id>"
  tenant_id       = "<tenant_id>"
}

```

# Azureへの認証
Azureにはサービスプリンシパルを使って認証します。
Azure Portalでサービスプリンシパルを作成します。
ロールの付与も忘れずに。

https://learn.microsoft.com/ja-jp/azure/active-directory/develop/howto-create-service-principal-portal

シークレットキーとクライアントIDをVariablesに登録します。

- ARM_CLIENT_ID
- ARM_CLIENT_SECRET

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/0f6ef13f-0b26-b013-627b-a66fa4a27209.png)

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret


# Terraform Cloudへの認証
Terraform CloudへはTokenで認証します。
Windowsの場合、Tokenを`%APPDATA%\terraform.rc`に格納すれば大丈夫とドキュメントに記載があったのですが、うまく動作しなかったので
`terraform login`で遷移した画面でTokenを発行しました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/2dc2e1e7-e3e0-6b75-1eb3-bc69f14d0f0a.png)


`%AppData%`に`terraform.d`というフォルダが出来上がり、その中のファイルに記述されていました。

Token記述の形式は以下です。

```terraform
credentials "app.terraform.io" {
  token = "xxxxxx.atlasv1.zzzzzzzzzzzzz"
}

```

https://developer.hashicorp.com/terraform/cli/config/config-file#location


# 実行
準備完了後はCLIと同じように実行することでリソース作成が出来ます。

```terraform
terraform init
terraform plan
terraform apply

```

実行結果や状態はTerraform Cloudで確認出来ます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604991/2b7a676a-7b95-5cdf-065e-d855500c8018.png)


# さいごに
複数人で実行する場合はTerraform Cloudで管理するのがいいですね。