AWSで学ぶTerraform実践②〜Local Valuesとリソース参照の活用

# チュートリアルのセクション
Tutorial-1. [AWSリソースを作成する（初級編）](https://qiita.com/shin7446/items/9ce36f1eb0846d95fa73)
Tutorial-2. [Local Valuesとリソース参照の活用](https://qiita.com/shin7446/items/9b94bdd2087aa7b5512d)  ←本記事
Tutorial-3. countを使って複数のリソースを一気に作る
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実践①〜AWSリソースを作成する（初級編）](https://qiita.com/shin7446/items/9ce36f1eb0846d95fa73)」で作成したtfファイルを改良して、より利便性を高めるための方法をまとめます。

# 本セクションのゴール
* 「[AWSで学ぶTerraform実践①〜AWSリソースを作成する（初級編）](https://qiita.com/shin7446/items/9ce36f1eb0846d95fa73)」と同様の構成を一度の `terraform apply` で構築できるようにする
*  `Local Values` を使って、値を編集するファイルを統合する

# 前提条件
「[AWSで学ぶTerraform実践①〜AWSリソースを作成する（初級編）](https://qiita.com/shin7446/items/9ce36f1eb0846d95fa73)」が完了していること


# 前回のコードの問題点
前回のセクションのtfファイルには2つの課題点があります。

## 1. 設定値がファイルに点在しており、メンテナンスが大変
それぞれのファイルに直接設定値を記載しているため、ひとつのパラメータを変えるといろんなファイルを編集しないといけません。
例） VPCの作り直しなどで `vpc_id` を変更すると、以下の3つのファイルを編集しないといけない

```route_table.tf
resource aws_route_table route_table {
  vpc_id = "vpc-04fb5154d22486542"  ←修正
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "igw-0e204fdaad34cae29"
  }
  tags = {
    Name = "tutorial-route-table"
  }
}
```

```security_group.tf
resource "aws_security_group" "sg" {
  name        = "tutorial-security-group"
  description = "Allow SSH inbound traffic from XXX"
  vpc_id      = "vpc-04fb5154d22486542"  ←修正

---omit---
```

```public_subnet.tf
resource aws_subnet subnet {
  vpc_id            = "vpc-04fb5154d22486542"  ←修正
  cidr_block        = "192.168.1.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "tutorial-public-subnet"
  }
}
```

## 2. 参照リソースのIDを直書きしている

前回の記載方法では、あるtfファイルでデプロイしたリソースのIDを別のtfファイルで参照するときには、一度 `terraform apply` でデプロイしてからそのリソースのIDを調べないといけませんでした。

例）Route TableとSubnetを紐づけるためのtfファイル

```route_table_association.tf
resource aws_route_table_association rta {
  subnet_id      = "subnet-0bf5661c57db0d425"    ←subnet.tfで作成したリソースのIDを記載
  route_table_id = "rtb-072ca46273b6a88e6"       ←route_table.tfで作成したリソースのIDを記載
}
```

この記載方法では、他の環境で使いまわそうとした時に、毎回ひとつずつtfファイルをデプロイしていかないといけなくなり、大変非効率です。

# Local Valuesを使った設定値の統合
## Local Valuesについて
Local Valuesとは、プログラムでいう関数の一時的なローカル変数のようなものです。
Local Valuesで定義したkeyをtfファイル内で指定すれば、その値を参照してくれます。
詳細は[公式ページ](https://www.terraform.io/docs/configuration/locals.html)に記載がありますが、ここでは実際に挙動を確認してみます。

## Local Valuesの使い方
Local Valuesは、 `resource` ではなく `locals` で定義します。
例えば、`vpc_id`をLocal Valuesで定義すると以下のようなファイルを作成することになります。

```local.tf
locals {
  vpc_id = "vpc-04fb5154d22486542"
}
```

`locals` 内で定義した値をresourceで参照する際は、以下のような記載になります。

例：subnet.tfの場合
* 参照時は `local.<key-name>` で記載する（ `locals.` でないことに注意！）

```public_subnet.tf
resource aws_subnet subnet {
  vpc_id            = local.vpc_id
  cidr_block        = "192.168.1.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "tutorial-public-subnet"
  }
}
```
このように共通的な値をLocal Valuesに切り出せば、 `local.tf` 内の値のみ変更すればOKです。
ちなみにLocal Valuesはネストにも対応しているため、例えばこんな書き方もできます。

```local.tf
locals {
  vpc_id = "vpc-04fb5154d22486542"
  availability_zone = "ap-northeast-1a"

  subnet = {
    cidr_block = "192.168.1.0/24"
    tags = {
      Name = "tutorial-public-subnet"
    }
  }
}
```

```public_subnet.tf
resource "aws_subnet" "subnet" {
  vpc_id            = local.vpc_id
  cidr_block        = local.subnet.cidr_block
  availability_zone = local.availability_zone

  tags = local.subnet.tags
}

```

また、以下の `Name` のように値の一部に変数値を使いたい場合は、${}で括ると利用可能です。

```local.tf
locals {
  owner             = "testuser"
  vpc_id            = "vpc-04fb5154d22486542"
  availability_zone = "ap-northeast-1a"

  subnet = {
    cidr_block = "192.168.1.0/24"

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

```
これで `testuser-publc-subnet` という名前のSubnetがデプロイされることになります。

# リソースの参照
リソースの参照を使うことで、デプロイ前の情報を参照して、その値をインプット情報として利用することができます。

例：ルートテーブルとSubnetの紐付けの場合
まず以下の2つのファイルでRoute TableとSubnetを作成します。

```route_table.tf
resource "aws_route_table" "route_table" {
  vpc_id = local.vpc_id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "igw-0e204fdaad34cae29"
  }
  tags = {
    Name = "tutorial-route-table"
  }
}
```

```public_subnet.tf
resource "aws_subnet" "subnet" {
  vpc_id            = local.vpc_id
  cidr_block        = "192.168.1.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "tutorial-public-subnet"
  }
}
```

これらの2つのresourceから作成されるルートテーブルとSubnetを紐づけるresourceは以下のような記載になります。
```route_table_association.tf
resource "aws_route_table_association" "rta" {
  subnet_id      = aws_subnet.subnet.id
  route_table_id = aws_route_table.route_table.id
}
```
記述のポイント
* 参照時は、 `<resource種別>.<resource名>.<参照パラメータ>` の方式で記載します。
* `<参照パラメータ>` として取得可能なパラメータやその指定方法は、参照元Resourceの公式ページの`Attributes Reference`に記載されています。
    - aws_subnetのAttributes Referenceは[こちら](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet#attributes-reference)
    - aws_route_tableのAttributes Referenceは[こちら](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet#attributes-reference)

# それぞれの要素を使ってコードを改修する

上記2つの改修方法を活用して、前回のtfファイルを更新すると以下のようになります。
なお、こちらのtfファイルは以下のgitにあるため、cloneしてご確認ください。
```.コマンド
git clone https://github.com/skitamura7446/terraform-tutorial.git
cd terraform-tutorial/tutorial-2
```

<details><summary>local.tf</summary><div>

```local.tf
locals {
  owner             = "tutorial"
  availability_zone = "ap-northeast-1a"

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

  internet_gateway = {
    tags = {
      Name = "${local.owner}-igw"
    }
  }

  subnet = {
    cidr_block        = "192.168.1.0/24"
    availability_zone = local.availability_zone
    tags = {
      Name = "${local.owner}-public-subnet"
    }
  }

  route_table = {
    route = {
      cidr_block = "0.0.0.0/0"
    }

    tags = {
      Name = "${local.owner}-route-table"
    }
  }

  security_group = {
    name        = "${local.owner}-security-group"
    description = "Allow SSH inbound traffic from XXX"

    ingress = {
      description = "SSH from XXX"
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = [
        "XXX.XXX.XXX.XXX/32",
      ]
    }

    egress = {
      from_port = 0
      to_port   = 0
      protocol  = "-1"
      cidr_blocks = [
        "0.0.0.0/0",
      ]
    }

    tags = {
      Name = "${local.owner}-security-group"
    }
  }

  key = {
    key_name   = "${local.owner}-key"
    public_key = "ssh-rsa XXXXX"
  }

  instance = {
    ami                         = "ami-02a2700d37baeef8b"
    instance_type               = "t2.micro"
    associate_public_ip_address = true

    tags = {
      Name = "${local.owner}-instance"
    }
  }
}
```  

</div></details>

<details><summary>vpc.tf</summary><div>

```vpc.tf
resource "aws_vpc" "vpc" {
  cidr_block           = local.vpc.cidr_block
  enable_dns_hostnames = local.vpc.enable_dns_hostnames

  tags = local.vpc.tags
}
```  

</div></details>

<details><summary>internet_gateway.tf</summary><div>

```internet_gateway.tf
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id

  tags = local.vpc.tags
}
```  

</div></details>


<details><summary>subnet.tf</summary><div>

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

  tags = local.subnet.tags
}
```  

</div></details>


<details><summary>route_table.tf</summary><div>

```route_table.tf
resource "aws_route_table" "route_table" {
  vpc_id = aws_vpc.vpc.id

  route {
    cidr_block = local.route_table.route.cidr_block
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = local.route_table.tags
}
```


</div></details>

<details><summary>route_table_association.tf</summary><div>

```route_table_association.tf
resource "aws_route_table_association" "rta" {
  subnet_id      = aws_subnet.subnet.id
  route_table_id = aws_route_table.route_table.id
}
```

</div></details>

<details><summary>security_group.tf</summary><div>

```security_group.tf
resource "aws_security_group" "sg" {
  name        = local.security_group.name
  description = local.security_group.description
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = local.security_group.ingress.description
    from_port   = local.security_group.ingress.from_port
    to_port     = local.security_group.ingress.to_port
    protocol    = local.security_group.ingress.protocol
    cidr_blocks = local.security_group.ingress.cidr_blocks
  }

  egress {
    from_port   = local.security_group.egress.from_port
    to_port     = local.security_group.egress.to_port
    protocol    = local.security_group.egress.protocol
    cidr_blocks = local.security_group.egress.cidr_blocks
  }

  tags = local.security_group.tags
}
```

</div></details>

<details><summary>key.tf</summary><div>

```key.tf
resource "aws_key_pair" "key" {
  key_name   = local.key.key_name
  public_key = local.key.public_key
}
```

</div></details>

<details><summary>instance.tf</summary><div>

```instance.tf
resource "aws_instance" "instance" {
  ami                         = local.instance.ami
  instance_type               = local.instance.instance_type
  subnet_id                   = aws_subnet.subnet.id
  associate_public_ip_address = local.instance.associate_public_ip_address
  key_name                    = aws_key_pair.key.key_name

  vpc_security_group_ids = [
    aws_security_group.sg.id,
  ]

  tags = local.instance.tags
}
```

</div></details>


# デプロイしてみる
デプロイ実行前に、実際に `local.tf` を自分の環境に合わせて修正してみましょう。

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

## local.tfを修正する
以下の値を変更します。これらはすべて別のtfファイルから参照されているため、Local Valuesに切り出していないと、全てのtfファイルを開いてひとつずつ修正していく必要があります。
今回は変更するべき箇所を `local.tf` にまとめているため、こちらのファイルひとつを変更すればOKになります。
* owner
  * デフォルトは `tutorial` にしていますが、自分の好きな文字列に変えてOKです。
* security_group.ingress.cidr_blocks[]
  * EC2インスタンスにアクセスする端末のIPアドレスに修正します。 
* key.public_key
  * こちらも前回のセクションで作成した公開鍵の値に書き換えます。

## init→applyしてデプロイする

```.コマンド
terraform init
terraform plan
terraform apply
```

<details><summary>実行結果</summary><div>

```.実行結果
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_instance.instance will be created
  + resource "aws_instance" "instance" {
      + ami                                  = "ami-02a2700d37baeef8b"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = "tutorial-key"
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "tutorial-instance"
        }
      + tags_all                             = {
          + "Name" = "tutorial-instance"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

  # aws_internet_gateway.igw will be created
  + resource "aws_internet_gateway" "igw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "tutorial-vpc"
        }
      + tags_all = {
          + "Name" = "tutorial-vpc"
        }
      + vpc_id   = (known after apply)
    }

  # aws_key_pair.key will be created
  + resource "aws_key_pair" "key" {
      + arn             = (known after apply)
      + fingerprint     = (known after apply)
      + id              = (known after apply)
      + key_name        = "tutorial-key"
      + key_name_prefix = (known after apply)
      + key_pair_id     = (known after apply)
      + key_type        = (known after apply)
      + public_key      = "ssh-rsa A__omit__XXX"
      + tags_all        = (known after apply)
    }

  # aws_route_table.route_table will be created
  + resource "aws_route_table" "route_table" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + carrier_gateway_id         = ""
              + cidr_block                 = "0.0.0.0/0"
              + core_network_arn           = ""
              + destination_prefix_list_id = ""
              + egress_only_gateway_id     = ""
              + gateway_id                 = (known after apply)
              + instance_id                = ""
              + ipv6_cidr_block            = ""
              + local_gateway_id           = ""
              + nat_gateway_id             = ""
              + network_interface_id       = ""
              + transit_gateway_id         = ""
              + vpc_endpoint_id            = ""
              + vpc_peering_connection_id  = ""
            },
        ]
      + tags             = {
          + "Name" = "tutorial-route-table"
        }
      + tags_all         = {
          + "Name" = "tutorial-route-table"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table_association.rta will be created
  + resource "aws_route_table_association" "rta" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_security_group.sg will be created
  + resource "aws_security_group" "sg" {
      + arn                    = (known after apply)
      + description            = "Allow SSH inbound traffic from XXX"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "66.187.238.66/32",
                ]
              + description      = "SSH from XXX"
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
        ]
      + name                   = "tutorial-security-group"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "tutorial-security-group"
        }
      + tags_all               = {
          + "Name" = "tutorial-security-group"
        }
      + vpc_id                 = (known after apply)
    }

  # aws_subnet.subnet will be created
  + resource "aws_subnet" "subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-1a"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "192.168.1.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Name" = "tutorial-public-subnet"
        }
      + tags_all                                       = {
          + "Name" = "tutorial-public-subnet"
        }
      + vpc_id                                         = (known after apply)
    }

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "192.168.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                 = true
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "tutorial-vpc"
        }
      + tags_all                             = {
          + "Name" = "tutorial-vpc"
        }
    }

Plan: 8 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_key_pair.key: Creating...
aws_vpc.vpc: Creating...
aws_key_pair.key: Creation complete after 1s [id=tutorial-key]
aws_vpc.vpc: Still creating... [10s elapsed]
aws_vpc.vpc: Creation complete after 12s [id=vpc-027e481cba0042dd5]
aws_internet_gateway.igw: Creating...
aws_subnet.subnet: Creating...
aws_security_group.sg: Creating...
aws_subnet.subnet: Creation complete after 0s [id=subnet-0c890bfea67012ae0]
aws_internet_gateway.igw: Creation complete after 0s [id=igw-077dc16485363b5ef]
aws_route_table.route_table: Creating...
aws_route_table.route_table: Creation complete after 2s [id=rtb-0943547a605c8f80a]
aws_security_group.sg: Creation complete after 2s [id=sg-01bca38bb25895b79]
aws_route_table_association.rta: Creating...
aws_instance.instance: Creating...
aws_route_table_association.rta: Creation complete after 0s [id=rtbassoc-0823aca40c5a362aa]
aws_instance.instance: Still creating... [10s elapsed]
aws_instance.instance: Still creating... [20s elapsed]
aws_instance.instance: Creation complete after 21s [id=i-0233e04e7d790c9eb]

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

</div></details>


無事にデプロイができたかと思います。このようにリソースの参照を使うことで、毎回手動でIDを確認しなくてもTerraformがよしなに連携し、複数リソースを一気に作成できます。またLocal Valuesで変数を切り出しておけば、今回のように他の人が作成したtfファイルも簡単に流用ができるようになります。

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

# おわりに
前回のセクションと合わせて、ここまでがTerraformの最初の一歩といった形になります。
次回以降はリソース作成のための便利な機能を紹介していきます。