【1時間で作る】TerraformでAWSインフラをコード化して本番環境を構築(VPC+EC2+RDS+S3)

## 前提条件

- AWSにて環境を一度構築したことがある
- AIMアカウントを作成し、accesskeyとsecret_keyを発行している

今回はAWSでの環境構築をコード化するため、AWSでまだインフラを作ったことがないという方は一度構築してからご利用下しさい。

# Terraformのインストール

自分はWindowsを使っているため、Windowsを例に説明します。Macをお使いの方は適宜読み替えて読み進めて下しださい。

インストーラーをダウンロード
[>> Terraform](https://www.terraform.io/downloads.html)

そのまま表示されてる画面の説明に沿って、Terraformをインストールします。

その後PATHの通っている環境に**.terraform.exe**を配置し準備完了です。

# Terraformの設定

それでは実際にコードでインフラを構築ししていきます。

Terraformではディレクトは以下の**.tf**ファイルを自動で読み込んでくれるため、構築するリソースごとに別ファイルを用意すると管理がしやすいです。(どうしても長くなりがちなので)

今回は以下のように作成しました。

- terraform
 - main.tf
 - terraform.tfvars
 - vpc.tf
 - ec2.tf
 - rds.tf
 - s3.tf

それぞれの解説です。

## main.tf

最初にsshキーペアを生成しておきましょう。のちにEC2に設定時に使用します。
今回は"sample"という名前で作成します。

```
ssh-keygen -t rsa -f sample

---------------------------------------------------------------------

The key's randomart image is:
+---[RSA 2048]----+
|.o.              |
|. o              |
|.  o E.          |
|.. .o+o          |
|+.o+.+++S        |
|oo=+o=o*.        |
|o=o== + o        |
|B== +..+         |
|B=.o ...o        |
+----[SHA256]-----+

```

それではAWSリソースの設定に移りましょう！
main.tfは変数設定やプロバイダーを構築します。

```:main.tf
# variable "変数名" {} で変数を定義します
# 定義した変数は.varvarsファイルから参照可能です
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "region" {}
variable "aws_db_username" {}
variable "aws_db_password" {}

# providerはAWSで設定
provider "aws" {
　# 認証キーとリージョンもここで設定しておかないと、コマンドを実行するごとに入力しなければいけないので面倒です
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region     = "us-east-1"
}

#EC2インスタンスとの通信に必要なキーペアです。
resource "aws_key_pair" "sample" {
  key_name   = "sample"
  public_key = file("./sample.pub")
}

# outputを設定しておくことで、環境作成後に特定の変数を表示させます
output "public_ip" {
  value = "${aws_instance.ec2.public_ip}"
}
# 今回は作成したインスタンスのElasticIPを表示
```

キーペアに関しては後ほど解説致します。

## terraform.tfvars

```:terraform.tfvars
aws_access_key  = "YOUR_AWS_ACCESS_KEY"
aws_secret_key  = "YOUR_AWS_SECRET_KEY"
region = "YOUR_REGION"
aws_db_username = "DATABASE_USER_NAME"
aws_db_password = "DATABASE_PASSWORD"
```
.tfvarsに記載された変数は自動でterraformに読み取られます。

github等のリモートリポジトリにアップする際は**.gitignore**を使って情報が公開されないよう対策が必要です。

```:.gitignore
# 変数を格納しているファイル
terraform.tfvars
# EC2キーペア
sample
sample.pub
```

## vpc.tf(VPC関連)

### VPC

まずプライベートな仮想ネットワークであるVPCの作成です。

terraformファイルの書き方を見ていきましょう。

```:vpc.tf
resource "aws_vpc" "vpc" {
　# cidrは10.0で作成
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = "false"
  enable_dns_hostnames = "false"
  tags = {
    Name = "sample_VPC"
  }
}
```
**resource** でawsのリソースに対して設定ができます。
第2引数では変数名を定義することで、後ほどこのリソースを呼び出す際に活用できます。

### インターネットゲートウェイ

```:vpc.tf
resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.vpc.id}"
}
```
定義した変数を呼び出すには**"${resource.var}"**で記載します。
先ほどのVPCのidを呼ぼ出すには${aws_vpc.vpc.id}で呼びだし可能です。

このまま書くリソースの設定を進めていきましょう！
### サブネット

```:vpc.tf
resource "aws_subnet" "public" {
  vpc_id            = "${aws_vpc.vpc.id}"
　# ここでVPCのネットワーク外のサブネットを作成するとエラーが出るので必ず10.0~で作成します
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_subnet" "private1" {
  vpc_id            = "${aws_vpc.vpc.id}"
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1a"
  tags = {
    Name = "private1"
  }
}

resource "aws_subnet" "private2" {
  vpc_id            = "${aws_vpc.vpc.id}"
  cidr_block        = "10.0.3.0/24"
  availability_zone = "us-east-1b"
  tags = {
    Name = "private2"
  }
}

```

サブネットは

- public用
- RDS用1
- RDS用2

の3つを作成します。

RDSの作成には2つ以上の異なるAZ(availability zone)のサブネットが必要になるためここでは2種類作成しております。

### ルートテーブル

```:vpc
resource "aws_route_table" "route" {
  vpc_id = "${aws_vpc.vpc.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }
}
```

### ルートテーブルの関連付け

```:vpc.tf
resource "aws_route_table_association" "ass"{
  subnet_id ="${aws_subnet.pubnet.id}"
  route_table_id = "${aws_route_table.route.id}"
}
```


### セキュリティグループ

```:vpc.tf
resource "aws_security_group" "main" {
  name   = "main"
  vpc_id = "${aws_vpc.vpc.id}"
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

resource "aws_security_group" "db" {
  name   = "SG_for_db"
  vpc_id = "${aws_vpc.vpc.id}"
  ingress {
    from_port   = 3306
    to_port     = 3306
    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"]
  }
}

```


コンソールからは特にアウトバウンドルールは特に定義しなくても問題ありませんでしたが、Terraformでは明記する必要があるので注意して下しさい。
### elastic IP

```:vpc.tf
resource "aws_eip" "eip" {
  instance = "${aws_instance.ec2.id}"
  vpc      = true
}
```

## ec2.tf(EC2の作成)

```:ec2.tf
resource "aws_instance" "ec2" {
  ami           = "ami-00e58f8044c7f8ad1"
  instance_type = "t2.micro"
  vpc_security_group_ids = [
    "${aws_security_group.main.id}"
  ]
  subnet_id                   = "${aws_subnet.public.id}"
  associate_public_ip_address = "true"
  key_name                    = "${aws_key_pair.sample.id}"

}
```

main.tfにて定義した変数からキーペアを呼びだすことで、コマンドラインから生成したキーをEC2に設定します。

## rds.tf(RDSの作成)

```:rds.tf
resource "aws_db_subnet_group" "db" {
  name        = "tf_dbsubnet"
  description = "It is a DB subnet group on tf_vpc."
  subnet_ids  = ["${aws_subnet.private1.id}", "${aws_subnet.private2.id}"]
  tags = {
    Name = "tf_dbsubnet"
  }
}

resource "aws_db_instance" "db" {
  identifier             = "sample-rds"
  skip_final_snapshot = true
  allocated_storage      = 20
  engine                 = "mysql"
  engine_version         = "5.7.26"
  instance_class         = "db.t2.micro"
  storage_type           = "gp2"
  username               = "${var.aws_db_username}"
  password               = "${var.aws_db_password}"
  vpc_security_group_ids = ["${aws_security_group.db.id}"]
  db_subnet_group_name   = "${aws_db_subnet_group.db.name}"
}
```

## s3.tf(S3バケットの作成)

```:s3.tf
resource "aws_s3_bucket" "bucket" {
  #バケット名は世界で唯一である必要があります
  bucket = "ichihara-sample-backet"
  acl = "public-read"

  tags = {
    Nmae = "purchase-app-backet"
    Environment = "Production"
  }
}

```

# 環境の作成

初めて実行す場合は**terraform init**を入力します。

これにより最初に設定したproviderに必要なプラグインをインストールし、構築の準備が整います。


```
terraform init

-------------------------------------------------------------------------------------------
Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```
またディレクトリに**.terraform**というフォルダも作成されます。

```
terraform plan
```

teraform paln で作成されるリソースを確認できます。


ただし、サブネットやVPCが上手く接続されているか、認証キーが正しいかといったことはわからないので、実際に作成してから確かめる必要があります。

```
terraform apply
```
terraform applでリソースの設定を適応します。

```
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.
```

と聞かれるので、どや顔で**yes**と打ち込みましょう。

```
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
public_ip = **.**.**.**
```

と設定したリソース数とIPが表示されていれば成功です。


# その他コマンド

```
terraform show
```
現在存在する環境を確認できます。

```
terraform destroy
```

作成した環境を削除します。

**継続的に使用する環境以外は必ず削除しましょう。**

AWSでは使用しているリソースに対して時間ごとに課金されるシステムになっている為、**作成だけしてdestroyを行わないとクレジットカードの支払いがえげつないことになります**…(*_*;

# 上手くいかない場合

## rror using credentials to get account ID: error calling sts:GetCallerIdentityteity:…

aws認証キーが間違っているためAWSに接続できておりません。

コンソールからログインしてIAM画面から認証キーを確認し、再度tfvarに設定してみてください。

## Error creating subnet: InvalidSubnet.Range: The CIDR '10.1.3.0/24' is invalid.status code: 400…

サブネットがVPCのネットワーク内に設定されていないため起きるエラーです。

サブネットマスク表記についてはこちらの記事が参考になりますので、ご一読ください。
[>> 勘違いしてない？IPアドレスとサブネットマスクの仕組みを図で再確認](https://xtech.nikkei.com/atcl/nxt/column/18/00780/052700002/)


# 使ってみた感想

コンソール画面で作成すると何度も何度も画面を移動しつつ、設定した値が間違っていないかを確認しなければいけないが、Terraformだと構築の時点でそれが見られるので素晴らしいと思います。

特にサブネットが有効でない、ec2が機能していないといったエラーをコマンドラインで見られるのはかなり使い勝手が良かったです！

ワンコマンドで環境作成、削除ができる点も様々なリソースを試せて便利だったので、これからはTerraformを使って環境構築を進めていこうと思いました。