AWSで学ぶTerraform実践③〜countを使って複数のリソースを一気に作る

# チュートリアルのセクション
Tutorial-1. [AWSリソースを作成する（初級編）](https://qiita.com/shin7446/items/9ce36f1eb0846d95fa73)
Tutorial-2. [Local Valuesとリソース参照の活用](https://qiita.com/shin7446/items/9b94bdd2087aa7b5512d)
Tutorial-3. [countを使って複数のリソースを一気に作る](https://qiita.com/shin7446/items/ad2ff1d9139815e92926)  ←本記事
Tutorial-4. TerraformでAWSリソースを作成する（中級編）
Tutorial-5. for_eachを使ってよりフレキシブルな繰り返し処理を実現する
Tutorial-6. moduleを使ってresourceを共通化する
Tutorial-7. TerraformでAWSリソースを作成する（上級編）
Extra-1. Secret情報を暗号化してGitにアップする
Extra-2. tfstateファイルに外部のストレージを利用する
Extra-3. 他のTerraformで管理しているリソースを参照する

# はじめに
ここでは、「[AWSで学ぶTerraform実践②〜Local Valuesとリソース参照の活用](https://qiita.com/shin7446/items/9b94bdd2087aa7b5512d)」で作成したtfファイルを改良して、より利便性を高めるための方法をまとめます。

# 本セクションのゴール
* countを使って繰り返し処理を実現する

# 前提条件
「[AWSで学ぶTerraform実践②〜Local Valuesとリソース参照の活用](https://qiita.com/shin7446/items/9b94bdd2087aa7b5512d)」が完了していること

# Countについて

countリソースのパラメーターを使用することで、tfファイルの構成を簡素化し、数値をインクリメントするだけでリソースをスケーリングすることができるようになります。言い換えると、同じ種別のリソース時はひとつのresourceで一気に複数のリソースを作成できるということです。

...と言われてもピンと来づらいかと思うので、実際の例をもとに考えてみましょう。

# Countを使わない場合の記述方法

例えばひとつのVPC内に3つのSubnetをそれぞれのAZに作成したい場合、これまで学んだ内容をもとに考えると、tfファイルは以下のような記述になります。

```subnet.tf
resource "aws_subnet" "subnet1" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = local.subnet.subnet1.cidr_block
  availability_zone = local.subnet.subnet1.availability_zone

  tags = local.subnet.subnet1.tags
}

resource "aws_subnet" "subnet2" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = local.subnet.subnet2.cidr_block
  availability_zone = local.subnet.subnet2.availability_zone

  tags = local.subnet.subnet2.tags
}

resource "aws_subnet" "subnet3" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = local.subnet.subnet3.cidr_block
  availability_zone = local.subnet.subnet3.availability_zone

  tags = local.subnet.subnet3.tags
}

```

合わせて、local.tfも以下のような記述になります。

```local.tf
locals {
  owner             = "tutorial"

  vpc = {
    cidr_block           = "192.168.0.0/16"
    enable_dns_hostnames = true
    tags = {
      Name = "${local.owner}-vpc"
    }
  }

  subnet = {
    subnet1 = {
      cidr_block = "192.168.1.0/24"
      availability_zone = "ap-northeast-1a"

      tags = {
        Name = "${local.owner}-public-subnet-1"
      }
    }

    subnet2 = {
      cidr_block = "192.168.2.0/24"
      availability_zone = "ap-northeast-1c"

      tags = {
        Name = "${local.owner}-public-subnet-2"
      }
    }

    subnet3 = {
      cidr_block = "192.168.3.0/24"
      availability_zone = "ap-northeast-1d"

      tags = {
        Name = "${local.owner}-public-subnet-3"
      }
    }
  }
}
```
このように、これまでの書き方だとtfファイルにいくつもResourceを書かないといけなくなります。
そうなると、後からSubnetを追加したいときもsubnet.tfとlocal.tfの両方を書き換えないといけないので大変です。

# Countを使った場合の記述方法
こういった **同じコンポーネントを複数作成したい場合** 、countを使うことでResourceの簡略化が可能です。
実際にSubnetの作成にてcountを使ってみましょう。

## subnet_count.tf
まず先にコードを見てみます。
```subnet_count.tf
resource aws_subnet subnet {
  vpc_id = aws_vpc.vpc.id
  count  = length(local.subnet)

  cidr_block        = values(local.subnet)[count.index].cidr_block
  availability_zone = values(local.subnet)[count.index].availability_zone
  tags              = values(local.subnet)[count.index].tags
}
```
順を追って説明します。

`count` （繰り返し処理の回数）は `length`という関数によって `local.subnet` オブジェクトの要素数（ここではsubnet1/subnet2/subnet3なので要素数は `3` ）を取得し指定しています。
そのため、このresourceは3回繰り返しSubnetの作成処理を行います。
しかしこの `count` を指定しただけでは、全く同じResourceが3つ作られるだけになってしまいます。
そこできちんと `local.tf` で記述したように、一つ目のSubnetは `local.subnet.subnet1` の値を参照し、二つ目のSubnetは `local.subnet.subnet2` の値を参照するように記述する必要があります。

その記述方法が `cidr_block`　にあるような`values(local.subnet)[count.index]`というコードになります。
* [values](https://www.terraform.io/docs/configuration/functions/values.html)：マップを取得し、そのマップ内の要素の値を含む**リスト**を取得します。
  * 実際にvalues(local.subnet)の値を取得すると、以下のような形になります。
```
values_subnet = [
    {
        availability_zone = "ap-northeast-1a"
        cidr_block        = "192.168.1.0/24"
        tags              = {
            Name = "tutorial-public-subnet-1"
        }
    },
    {
        availability_zone = "ap-northeast-1c"
        cidr_block        = "192.168.2.0/24"
        tags              = {
            Name = "tutorial-public-subnet-2"
        }
    },
    {
        availability_zone = "ap-northeast-1d"
        cidr_block        = "192.168.3.0/24"
        tags              = {
            Name = "tutorial-public-subnet-3"
        }
    },
]
``` 
* `count.index` ： `count` 数を表します。一回目の処理ではこの値が`0`、二回目の処理は`1` というようになります。

以上より、 `count` の1回目の処理における `values(local.subnet)[count.index].cidr_block` は `local.subnet[0].cidr_block` を指すことになります。（`subnet[0]=local.subnet.subnet1内の要素`）

ちなみに、`local.tf` はこのままでOKです。

## 実際にデプロイしてみる
百聞は一見にしかず、実際に `terraform apply` して挙動を確認してみましょう。 
ソースコードをgitにあげていますので、cloneしてきます。
```.コマンド
git clone　https://github.com/skitamura7446/terraform-tutorial.git
cd terraform-tutorial/tutorial-3
```

次に認証情報を修正します。
```provider.tf
provider aws {
  access_key = "XXXXXXXXXXXXXXXXXXXXXXXXX"　　←自分のアカウントに修正
  secret_key = "YYYYYYYYYYYYYYYYYYYYYYYYY"　　←自分のアカウントに修正
  region     = "ap-northeast-1"
}
```

ディレクトリ構成は以下の通りです。今回はVPCとSubnetだけ作成する簡単な構成になります。
```
.
├── local.tf
├── provider.tf
├── subnet.tf
├── versions.tf
└── vpc.tf
```

では `terraform plan` でデプロイされるリソースを確認してみましょう。
```.コマンド
terraform init
terraform plan
```
```.実行結果
---omit---
Terraform will perform the following actions:

  # aws_subnet.subnet[0] will be created
  + resource "aws_subnet" "subnet" {
      + arn                                            = (known after apply)

---omit---

  # aws_subnet.subnet[1] will be created
  + resource "aws_subnet" "subnet" {
      + arn                                            = (known after apply)

---omit---

  # aws_subnet.subnet[2] will be created
  + resource "aws_subnet" "subnet" {
      + arn                                            = (known after apply)

---omit---

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)

---omit---

Plan: 4 to add, 0 to change, 0 to destroy.
```
見てもらうとわかるように、 `count`を使うと作成されるリソースは `aws_subnet.subnet[0]` のように**配列**として管理されます。

ではこれをデプロイしてみます。
```
terraform apply
```
すると無事Subnetが3つ作成されました。
![ScreenShot 63.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/bacc5b0f-7dba-1ce1-19a1-14d832b86bed.png)

# Resource複数記述とCount利用時の違い
先述したとおり、`count` を利用すると該当のresourceを **配列(リスト)** として作成します。
そのため、もし `count` を利用して作成したリソースを参照したい場合は`<resource種別>.<resource名>[配列番号].<参照パラメータ>` というように記述する形になります。

例 : `aws_subnet.subnet[0].cidr_block`

では本当に上記の記述で参照できるか、 `Output` を使って実際に確認してみましょう。

## Output
`Output` とは、`terraform apply` によって作成されるリソースのパラメータ情報をコンソール上に出力したい時に利用されます。例えば、作成したEC2インスタンスのIPアドレスをコンソールに表示させることで、わざわざAWSのWebコンソールを開いてIPアドレスを確認しなくても、SSHアクセスができるようになります。
`Output` は他にも使い方がありますが、ここでは割愛します。
今回はこの `Output` を使って、 `aws_subnet.subnet[0].cidr_block` の値を取得してみましょう。

以下のようなtfファイルを作成して、 `terraform apply`　してみます。
`values` を見ると、`count` に対応するよう `aws_subnet.subnet[0].cidr_block` という形で参照しているのがわかります。
```output.tf
output "subnet1_cidr" {
  description = "values of subnet1 CIDR"
  value       = aws_subnet.subnet[0].cidr_block
}
```
```.コマンド
terraform apply
```
すると以下のように、subent1のCIDRが表示されます。
```.実施結果
---omit---
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

subnet1_cidr = "192.168.1.0/24"
```


# 環境のクリーンアップ
最後にこのセクションで作成した一連のリソースを削除します。
```.コマンド
terraform plan -destroy
terraform destroy
```

# おわりに
今回は `count`　を使ったリソースの繰り返し作成処理を学びました。
次のセクションでは、これまで学んだ内容をもとにTutorial-1よりも少し複雑なAWS環境のデプロイに挑戦します。