AWSのEC2とRDSをTerraformで構築する　Terraform３分クッキング

# はじめに
本記事はAWSのEC2とRDSをTerraformをで構築する方法について記載しています。

`terraform apply`で実行にかかる時間は、約3分程度です。カップラーメンの待ち時間で構築できます。

デプロイするのは以下の環境（※）になります。また、あわせてEC2は最低限のOSセットアップも行います。

![terraform.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/f29b39c4-1026-9292-5d3e-4c7c72469762.png)


Terraformの基本から入りたい方は、以前書いた[Oracle Cloudで始めるTerraform　自動化の真骨頂](https://qiita.com/Brutus/items/ed2ddb74fa8a13369d9f)を参照ください。

（※）本記事で記載しているtfファイルは[GitHub](https://github.com/Brutus03/Terraform-aws)で公開しています。

## Terraform構築
AWS環境におけるTerraformによる構築を行うためには、IAMでユーザ作成を行い、必要なクレデンシャル情報を用意します。あとはTerraformをインストールし、Terraform実行に必要なtfファイルを用意します。

AWS環境の前提条件を以下に記載します。

- [x] IAMでユーザの作成
- [x] 必要な権限を付与していること
- [x] SSHで使用するキーペアを作成していること

### tfファイルの作成
tfファイルについて解説します。

はじめに任意の作業ディレクトリに移動します。
本記事では以下のディレクトリ構成になり、カレントディレクトリはcommonディレクトリとします。なお、sshのディレクトリの場所については他のディレクトリでも問題ありません。

- ディレクトリ構成

```text
.
|-- common
|   |--  userdata
|        |-- cloud-init.tpl
|   |-- ec2.tf
|   |-- env-vars
|   |-- network.tf
|   |-- provider.tf
|   |-- rds.tf
`-- ssh
    |-- id_rsa
    |-- id_rsa.pub
```

- 各種ファイル説明

| ファイル名       | 役割       |
|:-----------------|:------------------|
| cloud-init.tpl             | EC2用の初期構築スクリプト       |
| ec2.tf             | EC2のtfファイル       |
| env-vars             | プロパイダーで使用する変数のtfファイル       |
| network.tf             | ネットワークのtfファイル       |
| provider.tf             | プロパイダーのtfファイル       |
| rds.tf             | RDSのtfファイル       |
| id_rsa             | SSH秘密鍵       |
| id_rsa.pub             | SSH公開鍵       |

- env-vars

```text
### Authentication
export TF_VAR_aws_access_key="＜access_keyの中身をペースト＞"
export TF_VAR_aws_secret_key="＜secret_keyの中身をペースト＞"
```

（※）引用符の中にはそれぞれaccess_keyとsecret_keyの中身をペーストします。

- provider.tf

```
# Variable
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "region" {
    default = "ap-northeast-1"
}

# Provider
provider "aws" {
    access_key = var.aws_access_key
    secret_key = var.aws_secret_key
    region = "ap-northeast-1"
}
```

- network.tf

```text
# vpc
resource "aws_vpc" "dev-env" {
    cidr_block = "10.0.0.0/16"
    instance_tenancy = "default"
    enable_dns_support = "true"
    enable_dns_hostnames = "false"
    tags = {
      Name = "dev-env"
    }
}

# subnet
## public
resource "aws_subnet" "public-web" {
    vpc_id = "${aws_vpc.dev-env.id}"
    cidr_block = "10.0.1.0/24"
    availability_zone = "ap-northeast-1a"
    tags = {
      Name = "public-web"
    }
}

## praivate
resource "aws_subnet" "private-db1" {
    vpc_id = "${aws_vpc.dev-env.id}"
    cidr_block = "10.0.2.0/24"
    availability_zone = "ap-northeast-1a"
    tags = {
      Name = "private-db1"
    }
}

resource "aws_subnet" "private-db2" {
    vpc_id = "${aws_vpc.dev-env.id}"
    cidr_block = "10.0.3.0/24"
    availability_zone = "ap-northeast-1c"
    tags = {
      Name = "private-db2"
    }
}

# route table
resource "aws_route_table" "public-route" {
    vpc_id = "${aws_vpc.dev-env.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.dev-env-gw.id}"
    }
    tags = {
      Name = "public-route"
    }
}

resource "aws_route_table_association" "public-a" {
    subnet_id = "${aws_subnet.public-web.id}"
    route_table_id = "${aws_route_table.public-route.id}"
}

# internet gateway
resource "aws_internet_gateway" "dev-env-gw" {
    vpc_id = "${aws_vpc.dev-env.id}"
    depends_on = [aws_vpc.dev-env]
    tags = {
      Name = "dev-env-gw"
    }
}
```

- ec2.tf

```text
# Security Group
resource "aws_security_group" "public-web-sg" {
    name = "public-web-sg"
    vpc_id = "${aws_vpc.dev-env.id}"
    ingress {
        from_port = 22
        to_port = 22
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    ingress {
        from_port = 80
        to_port = 80
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    tags = {
      Name = "public-web-sg"
    }
}

resource "aws_security_group" "praivate-db-sg" {
    name = "praivate-db-sg"
    vpc_id = "${aws_vpc.dev-env.id}"
    ingress {
        from_port = 5432
        to_port = 5432
        protocol = "tcp"
        cidr_blocks = ["10.0.1.0/24"]
    }

    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    tags = {
      Name = "public-db-sg"
    }
}

# EC2 Key Pairs
resource "aws_key_pair" "common-ssh" {
  key_name   = "common-ssh"
  public_key = "＜公開鍵の中身をペースト＞"
}

# EC2
resource "aws_instance" "webserver" {
    ami = "ami-011facbea5ec0363b"
    instance_type = "t2.micro"
    key_name   = "common-ssh"
    vpc_security_group_ids = [
      "${aws_security_group.public-web-sg.id}"
    ]
    subnet_id = "${aws_subnet.public-web.id}"
    associate_public_ip_address = "true"
    ebs_block_device {
      device_name    = "/dev/xvda"
      volume_type = "gp2"
      volume_size = 30
      }
    user_data          = "${file("./userdata/cloud-init.tpl")}"
    tags  = {
        Name = "webserver"
    }
}

# Output
output "public_ip_of_webserver" {
  value = "${aws_instance.webserver.public_ip}"
}
```

（※）Security Groupで記載しているcidr_blocksは例になります。実際に使用する場合はセキュリティを充分に配慮し、特にSSHは送信元を制限しましょう。

- rds.tf

```text
# RDS
resource "aws_db_subnet_group" "praivate-db" {
    name        = "praivate-db"
    subnet_ids  = ["${aws_subnet.private-db1.id}", "${aws_subnet.private-db2.id}"]
    tags = {
        Name = "praivate-db"
    }
}

resource "aws_db_instance" "test-db" {
  identifier           = "test-db"
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "postgres"
  engine_version       = "11.5"
  instance_class       = "db.t3.micro"
  name                 = "testdb"
  username             = "test"
  password             = "test"
  vpc_security_group_ids  = ["${aws_security_group.praivate-db-sg.id}"]
  db_subnet_group_name = "${aws_db_subnet_group.praivate-db.name}"
  skip_final_snapshot = true
}
```

（※）passwordの値は例として記載。使用不可な文字列もあります。


- cloud-init.tpl

```text
#cloud-config

runcmd:
# ホスト名の変更
  - hostnamectl set-hostname webserver

# パッケージのインストール
## セキュリティ関連の更新のみがインストール
  - yum update --security -y

## PostgreSQL client programs
  - yum install -y postgresql.x86_64

# タイムゾーン変更
## 設定ファイルのバックアップ
  - cp  -p /etc/localtime /etc/localtime.org

## シンボリックリンク作成
  - ln -sf  /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

### Terraform構築
はじめに、以下の準備作業を行います。

- 環境変数の有効化  
`$ source env-vars`
- 環境変数の確認  
`$ env`

準備作業完了後、いよいよTerraform構築です。
Terraform構築作業は次の3Stepです！

1. `terraform init`で初期化
2. `terraform plan`で確認
3. `terraform apply`で適用

`terraform`コマンドの説明については割愛します。

`terraform apply`実行後、**Apply complete!**のメッセージが出力されると各リソースが作成されます。
　
RDSへはEC2インスタンスからpsqlを実行して接続するか、SSH経由でDBeaverなどのSQLクライアントから接続することができます。

#### terraform apply

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

Terraform will perform the following actions:

  # aws_db_instance.test-db will be created
  + resource "aws_db_instance" "test-db" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = (known after apply)
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = (known after apply)
      + backup_window                         = (known after apply)
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = false
      + db_subnet_group_name                  = "praivate-db"
      + endpoint                              = (known after apply)
      + engine                                = "postgres"
      + engine_version                        = "11.5"
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = "test-db"
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t3.micro"
      + kms_key_id                            = (known after apply)
      + license_model                         = (known after apply)
      + maintenance_window                    = (known after apply)
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = (known after apply)
      + name                                  = "testdb"
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = (known after apply)
      + password                              = (sensitive value)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = false
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = true
      + status                                = (known after apply)
      + storage_type                          = "gp2"
      + timezone                              = (known after apply)
      + username                              = "test"
      + vpc_security_group_ids                = (known after apply)
    }

/*中略*/

aws_db_instance.test-db: Still creating... [3m0s elapsed]
aws_db_instance.test-db: Creation complete after 3m5s [id=test-db]

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

Outputs:

public_ip_of_webserver = ＜IPアドレス（※）＞
```

（※）EC2のパブリックIPが出力されます。

## ナレッジ
AWS環境におけるリソース作成時の留意事項について以下に記載します。

- アベイラビリティゾーン  
RDSの作成は複数のアベイラビリティゾーンの指定が必要です。単一では作成できません。本記事執筆時点で日本リュージョンの場合、以下から2つのアベイラビリティゾーンを指定する必要があります。

```text
zones: ap-northeast-1c, ap-northeast-1a, ap-northeast-1d.
```

- `terraform destroy`実行時にRDSを削除したい場合
RDSはリソース削除時にデフォルトでスナップショットの作成が求められるため、`terraform destroy`を行うためにはtfファイルに`skip_final_snapshot`のオプションをtrueに指定する必要があります。デフォルトはfalse。本番環境で行う場合は注意しましょう。

- RDSのPostgreSQL仕様
RDSにおけるPostgreSQLの照合順序及びCtypeのデフォルトは、**en_US.UTF-8**になっています。性能を考慮する場合はpsqlで接続してdropして再作成した方が良さそうです。

- OpensshでSSHキーペアを作成した場合
OpensshでSSHキーペアを作成し、DBeaverなどのSQLクライアントを使用してSSH経由で接続する場合は、鍵の形式を変更する必要があります。

# おわりに
以上、Terraform３分クッキング:cooking:でした。