Terraformで簡単なAWS環境を構築する

# 概要
Terraformの理解を深めるため、下記のような簡単なAWS環境を構築しました。 
本記事はその記録となります。

![キャッチアップ_構成図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510470/4d3a0341-7a2c-b6fa-a1d9-ac1b01aec360.jpeg)


# 環境
Terraformバージョン：1.5.6

# 準備
* IAMユーザのアクセスキーとシークレットキーを用意します。
* 以下6つのファイルを準備します。
```bash:今回のディレクトリ構成
.
├── main.tf 
├── variables.tf
├── vpc.tf
├── ec2.tf
├── rds.tf
└── output.tf
```

## 各ファイルの詳細
### main.tf
使用するプロバイダについて定義します。
```bash
provider "aws" {
  region     = "ap-northeast-1"
}

# 自分のパブリックIP取得用
provider "http" {}
```

### variables.tf
他ファイルから参照するための変数を定義します。
```bash
variable "az_a" {
  default     = "ap-northeast-1a"
}

variable "az_d" {
  default     = "ap-northeast-1d"
}

variable "access_key" {
  default     = <アクセスキーを記載して下さい>
}

variable "secret_key" {
  default     = <シークレットキーを記載して下さい>
}
```

### vpc.tf
VPCとその関連リソースについて定義します。
```bash
# ---------------------------
# VPC
# ---------------------------
resource "aws_vpc" "practice_vpc"{
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = "terraform-practice-vpc"
  }
}

# ---------------------------
# Public Subnet
# ---------------------------
resource "aws_subnet" "practice_public_1a_sn" {
  vpc_id            = aws_vpc.practice_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.az_a}"

  tags = {
    Name = "terraform-practice-public-1a-sn"
  }
}

# ---------------------------
# Private Subnet(1a)
# ---------------------------
resource "aws_subnet" "practice_private_1a_sn" {
  vpc_id            = aws_vpc.practice_vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.az_a}"

  tags = {
    Name = "terraform-practice-private-1a-sn"
  }
}


# ---------------------------
# Private Subnet(1d)
# ---------------------------
resource "aws_subnet" "practice_private_1d_sn" {
  vpc_id            = aws_vpc.practice_vpc.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "${var.az_d}"

  tags = {
    Name = "terraform-practice-private-1d-sn"
  }
}

# ---------------------------
# Internet Gateway
# ---------------------------
resource "aws_internet_gateway" "practice_igw" {
  vpc_id            = aws_vpc.practice_vpc.id
  tags = {
    Name = "terraform-practice-igw"
  }
}

# ---------------------------
# Elastic IP
# ---------------------------
resource "aws_eip" "practice_eip" {
  vpc = true

  tags = {
    Name = "terraform-practice-eip"
  }
}

# ---------------------------
# NAT Gateway
# ---------------------------
resource "aws_nat_gateway" "practice_natgw" {
  subnet_id     = "${aws_subnet.practice_public_1a_sn.id}"
  allocation_id = "${aws_eip.practice_eip.id}"

  tags = {
    Name = "terraform-practice-natgw"
  }
}

# ---------------------------
# Route table(Public Subnet)
# ---------------------------
# Route table
resource "aws_route_table" "practice_public_rt" {
  vpc_id            = aws_vpc.practice_vpc.id
  route {
    cidr_block      = "0.0.0.0/0"
    gateway_id      = aws_internet_gateway.practice_igw.id
  }
  tags = {
    Name = "terraform-practice-public-rt"
  }
}

# SubnetとRoute tableの関連付け
resource "aws_route_table_association" "practice_public_rt_associate" {
  subnet_id      = aws_subnet.practice_public_1a_sn.id
  route_table_id = aws_route_table.practice_public_rt.id
}

# ---------------------------
# Route table(Private Subnet)
# ---------------------------
# Route table
resource "aws_route_table" "practice_private_rt" {
  vpc_id            = aws_vpc.practice_vpc.id
  route {
    cidr_block      = "0.0.0.0/0"
    gateway_id      = aws_nat_gateway.practice_natgw.id
  }
  tags = {
    Name = "terraform-practice-private-rt"
  }
}

# SubnetとRoute tableの関連付け
#Private Subnet(1a)
resource "aws_route_table_association" "practice_private_1a_rt_associate" {
  subnet_id      = aws_subnet.practice_private_1a_sn.id
  route_table_id = aws_route_table.practice_private_rt.id
}

#Private Subnet(1d)
resource "aws_route_table_association" "practice_private_1d_rt_associate" {
  subnet_id      = aws_subnet.practice_private_1d_sn.id
  route_table_id = aws_route_table.practice_private_rt.id
}

# ---------------------------
# Security Group
# ---------------------------
# 自分のパブリックIPを取得
data "http" "ifconfig" {
  url = "http://ipv4.icanhazip.com/"
}

variable "allowed_cidr" {
  default = null
}

locals {
  myip          = chomp(data.http.ifconfig.body)
  allowed_cidr  = (var.allowed_cidr == null) ? "${local.myip}/32" : var.allowed_cidr
}

# Security Group for EC2
resource "aws_security_group" "practice_ec2_sg" {
  name              = "terraform-practice-ec2-sg"
  description       = "For EC2 Linux"
  vpc_id            = aws_vpc.practice_vpc.id
  tags = {
    Name = "terraform-practice-ec2-sg"
  }

  # インバウンドルール
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [local.allowed_cidr]
  }

  # アウトバウンドルール
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Security Group for RDS
resource "aws_security_group" "practice_rds_sg" {
  name              = "terraform-practice-rds-sg"
  description       = "For RDS"
  vpc_id            = aws_vpc.practice_vpc.id
  tags = {
    Name = "terraform-practice-rds-sg"
  }

  # インバウンドルール
  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"]
  }
}
```

### ec2.tf
EC2とその関連リソースについて定義します。
```bash
# ---------------------------
# EC2 Key pair
# ---------------------------
variable "key_name" {
  default = "terraform-practice-keypair"
}

# 秘密鍵のアルゴリズム設定
resource "tls_private_key" "practice_private_key" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

locals {
  private_key_file = "<任意のディレクトリを記載して下さい>/${var.key_name}.id_rsa"
}

# 秘密鍵を作成
resource "local_file" "practice_private_key_pem" {
  filename = "${local.private_key_file}"
  content  = "${tls_private_key.practice_private_key.private_key_pem}"
}

# 公開鍵をAWSのKey pairにインポート
resource "aws_key_pair" "practice_keypair" {
  key_name   = "${var.key_name}"
  public_key = "${tls_private_key.practice_private_key.public_key_openssh}"
}

# ---------------------------
# EC2
# ---------------------------
# Amazon Linux 2 の最新版AMIを取得
data "aws_ssm_parameter" "amzn2_lapractice_ami" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

# EC2作成
resource "aws_instance" "practice_ec2"{
  ami                         = data.aws_ssm_parameter.amzn2_lapractice_ami.value
  instance_type               = "t2.micro"
  availability_zone           = "${var.az_a}"
  vpc_security_group_ids      = [aws_security_group.practice_ec2_sg.id]
  subnet_id                   = aws_subnet.practice_public_1a_sn.id
  associate_public_ip_address = "true"
  key_name                    = "${var.key_name}"
  tags = {
    Name = "terraform-practice-ec2"
  }
}

```

### rds.tf
RDSとその関連リソースについて定義します。
```bash
# ---------------------------
# DB Subnet Group
# ---------------------------
resource "aws_db_subnet_group" "practice-dbsg" {
    name        = "praivate-dbsg"
    subnet_ids  = ["${aws_subnet.practice_private_1a_sn.id}", "${aws_subnet.practice_private_1d_sn.id}"]
    tags = {
        Name = "terraform-practice-dbsg"
    }
}

# ---------------------------
# RDS
# ---------------------------
resource "aws_db_instance" "practice_rds" {
  identifier           = "practice-db"
  allocated_storage    = 20
  availability_zone    = "${var.az_a}"
  storage_type         = "gp2"
  engine               = "postgres"
  engine_version       = "14.7"
  instance_class       = "db.t3.micro"
  db_name              = <DB名を記載して下さい>
  username             = <ユーザ名を記載して下さい>
  password             = <PWを記載して下さい>
  vpc_security_group_ids  = ["${aws_security_group.practice_rds_sg.id}"]
  db_subnet_group_name = "${aws_db_subnet_group.practice-dbsg.name}"
  skip_final_snapshot = true
}
```

### output.tf
作成したEC2のパブリックIPアドレスを出力するようにします。
```bash
output "ec2_global_ips" {
  value = "${aws_instance.practice_ec2.*.public_ip}"
}
```

# 環境構築
準備ができたら、実際にterraformコマンドで構築していきます。

### 作業ディレクトリの初期化
terraform initを実行し、必要なプラグインを揃えます。
```bash:commnad
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Finding latest version of hashicorp/local...
- Finding latest version of hashicorp/tls...
- Finding latest version of hashicorp/http...
- Installing hashicorp/aws v5.17.0...
- Installed hashicorp/aws v5.17.0 (signed by HashiCorp)
- Installing hashicorp/local v2.4.0...
- Installed hashicorp/local v2.4.0 (signed by HashiCorp)
- Installing hashicorp/tls v4.0.4...
- Installed hashicorp/tls v4.0.4 (signed by HashiCorp)
- Installing hashicorp/http v3.4.0...
- Installed hashicorp/http v3.4.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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 has been successfully initialized!が表示されればOKです。

### 構文チェック
terraform validateを実行し、tfファイルの構文をチェックしておきます。
```bash:commnad
$ terraform validate

Success! The configuration is valid, but there were some validation warnings as shown above.
```
Success! The configuration is validが表示されればOKです。

### 作成されるリソースの確認
terraform planを実行し、どのようなリソースが作成されるかを確認します。
```bash:commnad
$ terraform plan

data.http.ifconfig: Reading...
data.http.ifconfig: Read complete after 0s [id=http://ipv4.icanhazip.com/]
data.aws_ssm_parameter.amzn2_lapractice_ami: Reading...
data.aws_ssm_parameter.amzn2_lapractice_ami: Read complete after 0s [id=/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2]

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_db_instance.practice_rds will be created
  + resource "aws_db_instance" "practice_rds" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = false
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = "ap-northeast-1a"
      + backup_retention_period               = (known after apply)
      + backup_target                         = (known after apply)

-------長いため途中省略-------

Plan: 20 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + ec2_global_ips = [
      + (known after apply),
    ]

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

```
planの結果は大きく以下の3つに分けられます。
* 作成：リソース名の後に「will be created」と表記されます。
* 変更：リソース名の後に、「will be updated inplace」と表記されます。
* 削除：リソース名の後に、「will be destoryed」と表記されます。  

今回は、新規作成20件、変更0件、削除0件だとわかりました。  
問題なければ、実際にリソースを作成します。

### リソース作成
terraform applyを実行すると、実際にリソースが作成されます。
```bash:commnad
$ terraform apply

data.http.ifconfig: Reading...
data.http.ifconfig: Read complete after 0s [id=http://ipv4.icanhazip.com/]
data.aws_ssm_parameter.amzn2_lapractice_ami: Reading...
data.aws_ssm_parameter.amzn2_lapractice_ami: Read complete after 0s [id=/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2]

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_db_instance.practice_rds will be created
  + resource "aws_db_instance" "practice_rds" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + apply_immediately                     = false
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = "ap-northeast-1a"
      + backup_retention_period               = (known after apply)
      + backup_target                         = (known after apply)

-------長いため途中省略-------

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 #yesを入力します。


local_file.practice_private_key_pem: Creating...
aws_key_pair.practice_keypair: Creating...
aws_eip.practice_eip: Creating...
aws_vpc.practice_vpc: Creating...

-------長いため途中省略-------

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

Outputs:

ec2_global_ips = [
  "xx.xx.xx.xx",
]

```
Apply complete!が表示されればOKです。

マネジメントコンソール画面から、リソースが作成されているか確認してみます。  
※terraform showコマンドでも確認可能です。  
以下はEC2の例ですが、無事作成されていることが確認できました。

![スクリーンショット 2023-10-02 105349.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510470/9982b4dd-11c9-ac5b-9590-b54af21ebb3f.png)


# 環境削除
このまま放置しておくと課金が発生するので、削除までやっておきます。 
削除する場合は、terraform destroyを実行します。 
※先ほど作成したリソースが全て削除されるので、十分に注意した上で実行して下さい。

```bash:commnad
$ terraform destroy

data.http.ifconfig: Reading...
tls_private_key.practice_private_key: Refreshing state... [id=xxx]
local_file.practice_private_key_pem: Refreshing state... [id=xxx]
data.http.ifconfig: Read complete after 0s [id=http://ipv4.icanhazip.com/]
data.aws_ssm_parameter.amzn2_lapractice_ami: Reading...

-------長いため途中省略-------

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes #yesを入力

local_file.practice_private_key_pem: Destroying... [id=xxx]
local_file.practice_private_key_pem: Destruction complete after 0s
aws_route_table_association.practice_private_1d_rt_associate: Destroying... [id=xxx]
aws_route_table_association.practice_private_1a_rt_associate: Destroying... [id=xxx]

-------長いため途中省略-------

Destroy complete! Resources: 20 destroyed.
```
Destroy complete!が表示されればOKです。  
最後にterraform showで削除されたかどうかを確認します。

```bash:commnad
$ terraform show
The state file is empty. No resources are represented.
```
無事削除されました。

# 参考
こちらを参考にさせていただきました。

* https://kacfg.com/terraform-vpc-ec2/
* https://y-ohgi.com/introduction-terraform/handson/vpc/
* https://qiita.com/Brutus/items/cd5aab062ea6cebe436c