Terraform入門（AWSのシンプルな環境をTerraformで作成してみた）

### はじめに


最近SREエンジニアとして働き始めました。私の職場ではAWSインフラの構成をTerraformでコード管理しています。
TerraformでシンプルなAWSの環境を構築しながら、Terraformの使い方を簡単に紹介します。


本記事は以下のような人向けに書きました。

- Terraformを触ったことがないので何なのか知りたい
- とりあえずTerraformを触って簡単なAWSの構成を作成してみたい


### Terraformとは

Terraformとはインフラをコードで管理できるツールです。
インフラの初期構築、更新、破棄、いずれもTerraformではコードにより宣言し、実行します。
HashiCorp社によりオープンソースとして公開されており、GitHub上で開発が進められています。
インフラをコードで管理することにより、手作業での構築でよく発生してしまうパラメーターの設定ミスなどを減らすことができます。
また、ソフトウェア開発同様にバージョン管理を行うこともできますし、AWSやGCPの専用のGUIから構築するよりも大幅に構築にかける工数を減らすことができます。
AWS,GCP,Azureなど様々なインフラに対応しています。

参考 : [Introduction to Terraform](https://www.terraform.io/intro/index.html)
参考 : [Lists of Terraform Providers](https://www.terraform.io/docs/providers/index.html)


### 構築する環境

本記事では以下の環境を構築します。

![aws環境002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606434/34111034-794e-f52e-b166-f41e1394e660.png)



### 作業環境の作成

前提として以下の環境で実行していきます。

```
macOS 10.15.6

```


- AWS CLIのインストール

以下の記事を参考にAWS CLIをインストールしてください。

参考 : [AWS CLIのインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html)


- AWSクレデンシャルの登録

AWSマネジメントコンソールからアクセスキーIDとシークレットアクセスキーをコピーし、環境変数に設定します。

```
$ export AWS_ACCESS_KEY_ID=**************
$ export AWS_SECRET_ACCESS_KEY=*************
```

参考 : [AWSアカウントとアクセスキー](https://docs.aws.amazon.com/ja_jp/powershell/latest/userguide/pstools-appendix-sign-up.html)

- tfenvのインストール

Terraformのバージョンマネージャーであるtfenvをインストールします。
バージョンを確認し、インストールできていることを確かめます。

```
$ brew install tfenv
$ tfenv -v
tfenv 2.0.0
```

tfenv list-remoteコマンドでインストールできるバージョンの一覧を取得します。

```
$ tfenv list -remote
0.14.0-alpha20201007
0.14.0-alpha20200923
0.14.0-alpha20200910
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.13.0-rc1
0.13.0-beta3
0.13.0-beta2
0.13.0-beta1
0.12.29
0.12.28
0.12.27
0.12.26
0.12.25
・
・
・
0.1.0
```

tfenv installコマンドを使って今回は0.13.4をインストールします。

```
$ tfenv install 0.13.4
```

### 基本操作

- .tfファイルの作成

Terraform作業用ディレクトリを作成し、移動します。
作成するAWSのリソースのコードを書く.tfファイルを作成します。

```
$ mkdir terraform
$ cd terraform
$ touch main.tf
```

main.tfファイルに作成するAWSリソース実装します。
最初にプロバイダーはAWSを使用することを宣言します。
リソースは`resource "リソース種類" "リソース名（任意の値）"{}`といった形で実装していきます。
また、`output "アウトプット名（任意の値）"{value = "アウトプットしたい属性"}`でコードを実行し、リソースの作成後に表示させたい内容を宣言できます「。

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

resource "aws_vpc" "dev-vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "dev-public_subnet" {
  vpc_id     = aws_vpc.dev-vpc.id
  cidr_block = "10.0.1.0/24"

}

resource "aws_internet_gateway" "dev-gw" {
    vpc_id = aws_vpc.dev-vpc.id
}

resource "aws_route_table" "dev-public_rtb" {
    vpc_id = aws_vpc.dev-vpc.id
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.dev-gw.id
    }
}

resource "aws_route_table_association" "public_a" {
    subnet_id      = aws_subnet.dev-public_subnet.id
    route_table_id = aws_route_table.dev-public_rtb.id
}

resource "aws_security_group" "ec2_app" {
    vpc_id      = aws_vpc.dev-vpc.id
}

resource "aws_security_group_rule" "ssh" {
    type              = "ingress"
    from_port         = 22
    to_port           = 22
    protocol          = "tcp"
    cidr_blocks       = ["0.0.0.0/0"]
    security_group_id = aws_security_group.ec2_app.id
}

resource "aws_security_group_rule" "web" {
    type              = "ingress"
    from_port         = 80
    to_port           = 80
    protocol          = "tcp"
    cidr_blocks       = ["0.0.0.0/0"]
    security_group_id = aws_security_group.ec2_app.id
}

resource "aws_security_group_rule" "all" {
    type              = "egress"
    from_port         = 0
    to_port           = 65535
    protocol          = "tcp"
    cidr_blocks       = ["0.0.0.0/0"]
    security_group_id = aws_security_group.ec2_app.id
}

resource "aws_instance" "dev-ec2" {
  ami           = "ami-02f6f2104cbb962f4"
  subnet_id     = aws_subnet.dev-public_subnet.id
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.ec2_app.id]
  associate_public_ip_address = "true"
}

output "ec2-ip"{
    value = aws_instance.dev-ec2.public_ip
}

```

- 実行の流れ

`terraform init`を実行することでワークスペースの初期化、実行に必要なバイナリファイルをダウンロードします。

```
$ terraform init
```

`terraform plan`コマンドで実行される内容を把握することができます。
コードを更新した際などは既存環境からの変更内容や既存リソースの変更か再作成かを確認することができるため大変便利です。

```
$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.dev-ec2 will be created
  + resource "aws_instance" "dev-ec2" {
      + ami                          = "ami-02f6f2104cbb962f4"
      + 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)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (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                     = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (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)
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_internet_gateway.dev-gw will be created
  + resource "aws_internet_gateway" "dev-gw" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + vpc_id   = (known after apply)
    }

  # aws_route_table.dev-public_rtb will be created
  + resource "aws_route_table" "dev-public_rtb" {
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + cidr_block                = "0.0.0.0/0"
              + 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_peering_connection_id = ""
            },
        ]
      + vpc_id           = (known after apply)
    }

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

  # aws_security_group.ec2_app will be created
  + resource "aws_security_group" "ec2_app" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = (known after apply)
    }

  # aws_security_group_rule.all will be created
  + resource "aws_security_group_rule" "all" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 0
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 65535
      + type                     = "egress"
    }

  # aws_security_group_rule.ssh will be created
  + resource "aws_security_group_rule" "ssh" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 22
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

  # aws_security_group_rule.web will be created
  + resource "aws_security_group_rule" "web" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 80
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 80
      + type                     = "ingress"
    }

  # aws_subnet.dev-public_subnet will be created
  + resource "aws_subnet" "dev-public_subnet" {
      + arn                             = (known after apply)
      + assign_ipv6_address_on_creation = false
      + availability_zone               = (known after apply)
      + availability_zone_id            = (known after apply)
      + cidr_block                      = "10.0.1.0/24"
      + id                              = (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      + map_public_ip_on_launch         = false
      + owner_id                        = (known after apply)
      + vpc_id                          = (known after apply)
    }

  # aws_vpc.dev-vpc will be created
  + resource "aws_vpc" "dev-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)
    }

Plan: 10 to add, 0 to change, 0 to destroy.
```

`terraform apply`コマンドで実際にリソースを作成することができます。

```
$ terraform apply
・
・
・

Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

Outputs:

ec2-ip = *.*.*.*

```

参考 : [公式ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs)

### まとめ
今回はterraformを実行してみて、AWSのリソースを作成してみました。
チーム開発の場合はデフォルトではTerraformを実行したディレクトリに保存されるtfstateファイル（インフラの状態を管理するためのファイル）をS3に保存するなど運用として考える必要があることが増えますが、今回はとりあえず実行することを優先しました。

前職ではIaCのツールを使っていなかったので、terraformを使うことにより、
手動よりも構築にかける工数や構築の際の人為的ミスが大幅に減るなというのが感想です。