作りながら覚えるTerraform入門(2) - 基本編

作りながら覚えるTerraform入門シリーズの第２回です。
第1回の [インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e) でTerraformを使う準備ができたので、とりあえず動かしてみましょう。
最初はVPCの操作を通じてTerraformの基本的なコマンドを覚えるのが良いと思います。

##### 作りながら覚えるTerraform入門シリーズ

1. [インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e)
1. **基本編** => 今回はコチラ
1. [VPC編](https://qiita.com/m-oka-system/items/bba8d32d57142a870ce5)
1. [EC2編](https://qiita.com/m-oka-system/items/8280f748f88ac2e28ad7)
1. [Route53 + ACM編](https://qiita.com/m-oka-system/items/34f3ac3d98210d06dbc8)
1. [ELB編](https://qiita.com/m-oka-system/items/8882431042a1d01d0b7c)
1. [RDS編](https://qiita.com/m-oka-system/items/a45867495f95461b0a2c)

今回の学習ポイントは以下です。

- Resourceブロックの書き方
- terraform init / plan / apply / destroyの実行の流れ
- Terraformの実行計画の見方

# VPCの作成

まず、作業用のフォルダを作成しておきます。

```bash
mkdir terraform
cd terraform/
```

`provider.tf`というファイルを作成して、以下のコードを貼り付けます。
拡張子が`.tf`であればファイル名は任意で大丈夫です。

```terraform:provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}
```

`provider.tf`にはAWSにリソースを作成するための宣言を記述します。
デフォルトリージョンもここで指定しておきます。このあたりはお決まりの構文ですね。
公式ドキュメントの[AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)を参考にしています。

続いて、`network.tf`というファイルを作成して、以下のコードを貼り付けます。
ファイルを分ける必要はありませんが、リソースごとに分けたほうが管理がしやすくなります。

```terraform:network.tf
# Resourceブロックで「aws_vpc」を指定してVPCを作成
resource "aws_vpc" "vpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "my-vpc"
  }
}
```

`resource`から始まる箇所をResourceブロックと呼びます。
`resource`に続けて以下の引数を与えて、波括弧{}で囲った中にパラメータを記述します。

①第１引数：リソースタイプ
②第２引数：任意の識別名

①はAWSのリソースタイプで、`aws_*`の形になっています。Azureであれば`azurerm_*`といった形。
基本的には*の箇所にサービス名が入るので連想しやすいですが、公式リファレンスを参照しながら記述します。
`aws_vpc`であれば、[Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) です。

②は識別名なので自由に命名できますが、他のリソースと重複はできません。
ここの命名に意外と悩んだりしますが、最初はとりあえず動かすことを目的として`example`や`main`としておくのも一つの手です。

作成したコードを元にリソースを作成してみましょう。
リソースを作成するには、`terraform apply`を実行します。

```bash
terraform apply
╷
│ Error: Could not load plugin
│ 
│ 
│ Plugin reinitialization required. Please run "terraform init".
:
```

そうすると、プラグインが読み込めないので`terraform init`を実行するようエラーが表示されます。Terraformのインストールによってコマンド自体は利用できるようになりますが、AWSやAzureなど各サービス(プロバイダー)を利用するためのプラグインは分離されているため、まず最初に`terraform init`を実行してダウンロード(初期化)する必要があります。

```bash
terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.44.0...
- Installed hashicorp/aws v3.44.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!
:
```

`terraform init`を実行すると、`.terraform`ディレクトリと`.terraform.lock.hcl`ファイルが作成されます。`.terraform`ディレクトリが「AWS」というプロバイダーを利用するためのもの、`.terraform.lock.hcl`は依存関係をコントロールするためのロックファイル、くらいに考えておきましょう。

```bash
tree -a
.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── aws
│                   └── 3.44.0
│                       └── darwin_amd64
│                           └── terraform-provider-aws_v3.44.0_x5
├── .terraform.lock.hcl
├── network.tf
└── provider.tf
```

それでは、改めて`terraform apply`を実行してみます。
「Do you want to perform these actions?」と確認を求められるので「yes」と入力して進めます。

```bash
terraform apply

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:

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "my-vpc"
        }
      + tags_all                         = {
          + "Name" = "my-vpc"
        }
    }

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

aws_vpc.vpc: Creating...
aws_vpc.vpc: Creation complete after 2s [id=vpc-0e32cf9d1213b770d]

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

最後の「Apply complete!」の行を見ると`1 added`となっていて、1つのリソースが追加されたことがわかります。コンソール画面を確認してみると、「my-vpc」のNameタグが付いたVPCが作成できていることがわかります。

![C101998D-1A0B-4519-B21F-7FE053B66210.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/081bf73c-5205-e51f-47b1-bb3d0e19ac5f.png)


# VPCの更新

続いて、更新の動きを見ていきましょう。
試しに`network.tf`のNameタグを「my-first-vpc」に変更してみます。

```terraform:network.tf
resource "aws_vpc" "vpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "my-first-vpc"
  }
}
```

もう一度`terraform apply`を実行して適用してみましょう。

```bash
terraform apply

aws_vpc.vpc: Refreshing state... [id=vpc-0e32cf9d1213b770d]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_vpc.vpc will be updated in-place
  ~ resource "aws_vpc" "vpc" {
        id                               = "vpc-0e32cf9d1213b770d"
      ~ tags                             = {
          ~ "Name" = "my-vpc" -> "my-first-vpc"
        }
      ~ tags_all                         = {
          ~ "Name" = "my-vpc" -> "my-first-vpc"
        }
        # (14 unchanged attributes hidden)
    }

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

aws_vpc.vpc: Modifying... [id=vpc-0e32cf9d1213b770d]
aws_vpc.vpc: Modifications complete after 2s [id=vpc-0e32cf9d1213b770d]

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

`1 changed`と表示されて更新されたことがわかります。
コンソール画面からもNameタグが変更できていることを確認しておきましょう。

次は`cidr_block`を「192.168.0.0/16」に変更してみます。

```terraform:network.tf
resource "aws_vpc" "vpc" {
  cidr_block       = "192.168.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "my-first-vpc"
  }
}
```

`terraform apply`を実行して適用してみましょう。

```bash
terraform apply

aws_vpc.vpc: Refreshing state... [id=vpc-0e32cf9d1213b770d]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_vpc.vpc must be replaced
-/+ resource "aws_vpc" "vpc" {
      ~ arn                              = "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-0e32cf9d1213b770d" -> (known after apply)
      ~ cidr_block                       = "10.0.0.0/16" -> "192.168.0.0/16" # forces replacement
      ~ default_network_acl_id           = "acl-009e09c086ff9c692" -> (known after apply)
      ~ default_route_table_id           = "rtb-0b555443103cfbe0b" -> (known after apply)
      ~ default_security_group_id        = "sg-0af32faf28e1dfa87" -> (known after apply)
      ~ dhcp_options_id                  = "dopt-2489e243" -> (known after apply)
      ~ enable_classiclink               = false -> (known after apply)
      ~ enable_classiclink_dns_support   = false -> (known after apply)
      ~ enable_dns_hostnames             = false -> (known after apply)
      ~ id                               = "vpc-0e32cf9d1213b770d" -> (known after apply)
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      ~ main_route_table_id              = "rtb-0b555443103cfbe0b" -> (known after apply)
      ~ owner_id                         = "123456789012" -> (known after apply)
        tags                             = {
            "Name" = "my-first-vpc"
        }
        # (4 unchanged attributes hidden)
    }

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

aws_vpc.vpc: Destroying... [id=vpc-0e32cf9d1213b770d]
aws_vpc.vpc: Destruction complete after 0s
aws_vpc.vpc: Creating...
aws_vpc.vpc: Creation complete after 3s [id=vpc-05c92959157ff59d0]

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

今回の`cidr_block`の変更のように既存リソースの変更では対応できないケースは**削除→作成の置き換えの動作になります。**

後述しますが、実行計画の中に「replaced」や「forces replacement」という文字が含まれている場合は、それが意図したものなのか十分に確認してから実行する必要があります。

# VPCの削除

最後に、作成したVPCを削除してみましょう。
リソースを削除するには、`terraform destroy`を実行します。
applyの時と同じように「yes」を入力して実行してみましょう。

```bash
terraform destroy

aws_vpc.vpc: Refreshing state... [id=vpc-05c92959157ff59d0]

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:

  # aws_vpc.vpc will be destroyed
  - resource "aws_vpc" "vpc" {
      - arn                              = "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-05c92959157ff59d0" -> null
      - assign_generated_ipv6_cidr_block = false -> null
      - cidr_block                       = "192.168.0.0/16" -> null
      - default_network_acl_id           = "acl-0896f7ab7cd8e3bbd" -> null
      - default_route_table_id           = "rtb-0ba11eb799c733669" -> null
      - default_security_group_id        = "sg-0ddd72a743034c145" -> null
      - dhcp_options_id                  = "dopt-2489e243" -> null
      - enable_classiclink               = false -> null
      - enable_classiclink_dns_support   = false -> null
      - enable_dns_hostnames             = false -> null
      - enable_dns_support               = true -> null
      - id                               = "vpc-05c92959157ff59d0" -> null
      - instance_tenancy                 = "default" -> null
      - main_route_table_id              = "rtb-0ba11eb799c733669" -> null
      - owner_id                         = "123456789012" -> null
      - tags                             = {
          - "Name" = "my-first-vpc"
        } -> null
      - tags_all                         = {
          - "Name" = "my-first-vpc"
        } -> 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

aws_vpc.vpc: Destroying... [id=vpc-05c92959157ff59d0]
aws_vpc.vpc: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.
```

`1 destroyed`と表示されて削除されたことがわかりますね。

# Terraformの実行計画

これまで見てきたように、`terraform apply`や`terraform destroy`を実行すると、これから起こる変更差分を「実行計画」として表示してくれます。

実行したコマンドの中にある`Terraform will perform the following actions`の箇所を振り返ってみてください。それぞれこれから行われる処理を示してくれています。

- aws_vpc.vpc will be created
- aws_vpc.vpc will be updated in-place
- aws_vpc.vpc must be replaced
- aws_vpc.vpc will be destroyed

リソースやパラメータの頭にある記号からも判別できます。
なお、何も変更がない場合は`No changes.`と表示されます。

- 作成： +
- 更新： ~
- 削除： -
- 置き換え： -/+

繰り返しになりますが、単なる更新で済む場合と、作り直しになる場合があります。このあたりの挙動はコンソール画面から実施する場合と同じですが、実行計画はよく確認するように心がけましょう。

# Terraformの基本コマンド

この記事ではVPCの作成と削除を通じて、init → apply → destroyを実行して、初期化 → 適用 → 削除を行いましたが、その他のよく使うコマンドも確認しておきましょう。

## validate

`terraform validate`を実行すると、構文に誤りがないか、パラメータが適切かチェックすることができます。

```bash
# valid
terraform validate

Success! The configuration is valid.

# invalid (cidr_blockの範囲が誤っている例)
terraform validate

Error: expected cidr_block to contain a valid network Value, expected 10.0.0.0/16, got 10.0.1.0/16
│ 
│   with aws_vpc.vpc,
│   on network.tf line 2, in resource "aws_vpc" "vpc":
│    2:   cidr_block       = "10.0.1.0/16"
```

## plan

先に実践した例では`terraform apply`をいきなり実行しましたが、`terraform plan`を実行すると実行計画の確認のみ行うことができます。applyやdestroyはせずに実行計画だけ確認したい場合に利用します。

## fmt

`terraform fmt`を実行するとインデントを整えて見た目をキレイにしてくれます。`-recursive`のオプションを付け加えるとサブディレクトリ含めて再帰的にフォーマットしてくれます。(付けない場合はカレントディレクトリの.tfファイルのみ)
が、[インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e)でご紹介したように、VSCode拡張機能を利用して自動フォーマットを有効にすることをオススメします。

## console

`terraform console`を実行すると対話型のコンソールを起動することができます。変数の値や関数の評価結果を確認したい場合に便利です。コンソールを終了するには`exit`を入力します。

```bash
terraform console

> max(5, 12, 9)
12
```

他にもありますが、とりあえずこのあたりの基本コマンドを覚えておけば困らないと思います。

## コマンド実行の流れ

私の場合は、initのあとはvalidate → plan → applyを繰り返して、いらなくなったらdestoryするって形で学習を進めました。学習環境は作って壊すの繰り返しなので、[インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e)でご紹介したように、applyとdestoryの時に「yes」を入力しなくて済むように`-auto-approve`を付け加えたエイリアス設定を利用してtf init → tfv → tfp → tfa → tfdの流れで実行しています。エイリアス設定を再掲しておきます。

```bash:~/.zshrc
alias tf="terraform"
alias tfp="terraform plan"
alias tfv="terraform validate"
alias tff="terraform fmt -recursive"
alias tfa="terraform apply -auto-approve"
alias tfd="terraform destroy -auto-approve"
```

今回は以上です。次回はVPC編ということで、サブネットやルートテーブルなど含め、改めてVPC関連のリソースを作成する方法について書いてみたいと思います！

# 参考リンク

- [AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)
- [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)
- [Terraform CLI Documentation](https://www.terraform.io/docs/cli/index.html)