Terraformの仮想環境を作ってAzure Export for Terraformを使ってみた

最近よく聞くInfrastructure as Code(IaC)を使ってみようと、
TerraformやAzure Export for Terraformの仮想環境を構築しました。

# 構築する環境について

インストールするのは以下。
- Terraform
- Azure CLI
- Azure Export for Terraform

今回は入れてませんが、Terraformの静的解析ツールを一緒に入れておくと良いかもしれません。

# 環境の準備

以下のdockerfileを用意しました。

``` dockerfile:Dockerfile
FROM ubuntu:22.04

RUN apt update && apt install -y curl ca-certificates apt-transport-https lsb-release gnupg software-properties-common wget
RUN curl -sSL https://packages.microsoft.com/keys/microsoft.asc > /etc/apt/trusted.gpg.d/microsoft.asc
RUN apt-add-repository https://packages.microsoft.com/ubuntu/22.04/prod
RUN curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/azure-archive-keyring.gpg
RUN echo "deb [signed-by=/usr/share/keyrings/azure-archive-keyring.gpg] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list > /dev/null
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
RUN echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list
RUN apt update && apt install -y terraform　azure-cli aztfexport
```

ビルドします。

``` bash:コマンド
sudo docker build -t terraform:v1 .
```

あとは、コンテナを用意するだけで、terraformコマンドを使うことができます。

# Terraformについて

代表的なコマンドを紹介します。

## 初期化

使用するプロバイダに関するコードをダウンロードします。  
ダウンロードしたコードは、`.terraform`に格納されます。

``` bash:コマンド
terraform init
```

``` bash:実行例
Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v3.77.0

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.
```

## 実行計画の確認

どんな変更（追加、変更、削除）をする予定なのかを確認できます。  
構文のチェックも行われます。

``` bash:コマンド
terraform plan
```

``` bash:実行例
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.resource-group.azurerm_resource_group.bs will be created
  + resource "azurerm_resource_group" "res-0" {
      + id       = (known after apply)
      + location = "xxxxxxx"
      + name     = "xxxxxxx"
    }

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

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

削除の場合は、`-destroy`を付ける

``` bash:コマンド
terraform plan -destroy
```

``` bash:実行例
module.resource-group.azurerm_resource_group.bs: Refreshing state... [id=/subscriptions/xxxxxxx/resourceGroups/xxxxxxx]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # module.resource-group.azurerm_resource_group.bs will be destroyed
  - resource "azurerm_resource_group" "res-0" {
      - id       = "/subscriptions/xxxxxxx/resourceGroups/xxxxxxx" -> null
      - location = "xxxxxxx" -> null
      - name     = "xxxxxxx" -> null
      - tags     = {} -> null
    }

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

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

## 変更の適用

適用する内容を表示し、確認してきます。  
`yes`と入力すると指定の環境に変更が適用されます。

``` bash:コマンド
terraform apply
```

``` bash:実行例
Terraform will perform the following actions:Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.resource-group.azurerm_resource_group.bs will be created
  + resource "azurerm_resource_group" "res-0" {
      + id       = (known after apply)
      + location = "xxxxxxx"
      + name     = "xxxxxxx"
    }

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

module.resource-group.azurerm_resource_group.bs: Creating...
module.resource-group.azurerm_resource_group.bs: Creation complete after 0s [id=/subscriptions/xxxxxxx/resourceGroups/xxxxxxx]

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

## 削除の適用

変更の適用と同じく、削除する内容を表示し、確認してきます。  
`yes`と入力すると、指定の環境に削除が適用されます。

``` bash:コマンド
terraform destroy
```

``` bash:実行例
module.resource-group.azurerm_resource_group.bs: Refreshing state... [id=/subscriptions/xxxxxxx/resourceGroups/xxxxxxx]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # module.resource-group.azurerm_resource_group.bs will be destroyed
  - resource "azurerm_resource_group" "res-0" {
      - id       = "/subscriptions/xxxxxxx/resourceGroups/xxxxxxx" -> null
      - location = "xxxxxxx" -> null
      - name     = "xxxxxxx" -> null
      - tags     = {} -> null
    }

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: yes

module.resource-group.azurerm_resource_group.bs: Destroying... [id=/subscriptions/xxxxxxx/resourceGroups/xxxxxxx]
module.resource-group.azurerm_resource_group.bs: Still destroying... [id=/subscriptions/xxxxxxx/resourceGroups/xxxxxxx, 10s elapsed]
module.resource-group.azurerm_resource_group.bs: Destruction complete after 15s

Destroy complete! Resources: 1 destroyed.
```

## リソース状態の確認

適用済みのリソースの状態を表示します。

``` bash:コマンド
terraform show
```

``` bash:実行例
# azurerm_resource_group.res-0:
resource "azurerm_resource_group" "res-0" {
    id       = "/subscriptions/xxxxxxx/resourceGroups/xxxxxxx"
    location = "xxxxxxx"
    name     = "xxxxxxx"
    tags     = {}
}
```

# Azure Export for Terraformについて

terraform初心者の方にとってはとても便利なもので、既存のリソースの情報をterraformに出力してくれます。

特定のリソースを出力する方法は、以下の通りです。

- Azure PortalやCLIを使って、リソースIDを取得します。

- terraformの環境内で、Azureにログインします。
  ※ `--use-device-code`オプションを使えばTerraform実行環境に画面がない場合でも、ログインが可能です。
  ```bash:コマンド
  az login --use-device-code --tenant [テナントID]
  ```

- tfファイル出力用のディレクトリを用意します。

  ``` bash:コマンド
  mkdir vm
  ```

- terraformの環境内で、以下のコマンドを実行します。

  ``` bash:コマンド
  cd vm
  ```

  `--non-interactive`：会話形式をスキップするオプション
  ``` bash:コマンド
  aztfexport resource --non-interactive [リソースID]
  ```

- terraformのコードを表示します。

  ``` bash:コマンド
  terraform init
  terraform show
  ```

resource-group単位での出力も可能です。

- 以下のコマンドを実行します。

  ``` bash:コマンド
  aztfexport resource-group [リソースグループ名]
  ```

  指定したリソースグループ内のリソースの一覧が表示されるので、出力したいものを選択し、出力できます。

# まとめ

環境は簡単に作れました。皆様も軽い気持ちで構築してみてください。
terraformについては、まだまだ勉強中なので、いつかまとめ記事を出せるといいな、と思っています。
Azure Export for Terraformは、使いやすいので勉強用に使えます。Azureに慣れてない方にもおすすめです。