Terraformで作るAWSシステムパターン① インスタンス最小構成

#本記事の目的
Terraformを用いて、AWS上でよく見るシステムパターンを作成し、アウトプットとして記録する。
今回はインスタンスに接続するための最小構成を作成してみる。

#設計
###システム要件
インターネットからssh経由で接続可能な単一のサーバーを構築する。

###構成図
![minimum_configuration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559588/ceb0bdcb-cc50-3271-3abf-c7623858b329.png)


#事前準備
###ソフトウェア要件
- [AWS CLI](https://aws.amazon.com/cli) - v2.1.26　※1
- [Terraform](https://www.terraform.io/downloads.html) - v1.0.0
- [jq](https://stedolan.github.io/jq/) -v1.6 ※2

※1 今回はTerraform用のAWS CLIプロファイルを、`tf-demo`という名前で作成し使用する。
※2 Terraform実施時に出力されるjsonデータ加工用のコマンド。

###EC2キーペア用の公開鍵/秘密鍵の作成
```
$ ssh-keygen -t rsa -f ~/.ssh/minimum_configuration.pem
```

#ファイル構成
```
$ tree
minimum_configuration/
├── compute.tf
├── config.tf
├── firewall.tf
├── network.tf
├── output.tf
├── variable.tf
└── src
    └── user_data.sh
```

#ファイル内容
###computeファイル
AMI、EC2インスタンス、EC2キーペアの設定を定義する。


```compute.tf
# ====================
#
# AMI
#
# ====================
# 
data "aws_ami" "test_ami" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }

  filter {
    name   = "state"
    values = ["available"]
  }
}


# ====================
#
# EC2 Instance
#
# ====================

resource "aws_instance" "example_instance" {
  ami                    = data.aws_ami.example_ami.image_id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.example_subnet.id
  associate_public_ip_address = true
  vpc_security_group_ids = [aws_security_group.example_sg.id]

  root_block_device {
    volume_type           = var.volume_type
    volume_size           = var.volume_size
    delete_on_termination = true
  }

  key_name               = aws_key_pair.example_key.id
  user_data              = file(var.user_data_file)

  tags = {
    Name    = "${var.project}-${var.environment}-ec2"
    Project = var.project
    Env     = var.environment
  }
}

# ====================
#
# Key Pair
#
# ====================

resource "aws_key_pair" "example_key" {
  key_name   = var.key_name
  public_key = file(var.public_key_file)
  tags = {
    Name    = "${var.project}-${var.environment}-keypair"
    Project = var.project
    Env     = var.environment
  }
}

```

###firewallファイル
EC2用セキュリティグループの設定を定義する。

```firewall.tf
# ====================
#
# Security Group
#
# ====================
resource "aws_security_group" "example_sg" {
  name   = "example_sg"
  vpc_id = aws_vpc.example_vpc.id

  tags = {
    Name    = "${var.project}-${var.environment}-sg"
    Project = var.project
    Env     = var.environment
  }
}

# インバウンドルール(ssh接続用)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.example_sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
}

# インバウンドルール(ping疎通確認用)
resource "aws_security_group_rule" "in_icmp" {
  security_group_id = aws_security_group.example_sg.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = -1
  to_port           = -1
  protocol          = "icmp"
}

# アウトバウンドルール(全開放)
resource "aws_security_group_rule" "out_all" {
  security_group_id = aws_security_group.example_sg.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}

```

###networkファイル
VPC/サブネット/インターネットゲートウェイ/ルーティングの設定を定義する。

```network.tf
# ====================
#
# VPC
#
# ====================

resource "aws_vpc" "example_vpc" {
  cidr_block           = var.vpc_cidr_block
  enable_dns_support   = true # DNS解決有効化
  enable_dns_hostnames = true # DNSホスト名有効化

  tags = {
    Name    = "${var.project}-${var.environment}-vpc"
    Project = var.project
    Env     = var.environment
  }
}

# ====================
#
# Subnet
#
# ====================

resource "aws_subnet" "example_subnet" {
  vpc_id                  = aws_vpc.example_vpc.id
  cidr_block              = var.subnet_cidr_block
  map_public_ip_on_launch = true #インスタンス起動時におけるパブリックIPアドレスの自動割り当ての有効化
  availability_zone       = var.availability_zone

  tags = {
    Name    = "${var.project}-${var.environment}-subnet"
    Project = var.project
    Env     = var.environment
  }
}

# ====================
#
# Internet Gateway
#
# ====================
resource "aws_internet_gateway" "example_igw" {
  vpc_id = aws_vpc.example_vpc.id

  tags = {
    Name    = "${var.project}-${var.environment}-igw"
    Project = var.project
    Env     = var.environment
  }
}

# ====================
#
# Route Table
#
# ====================
resource "aws_route_table" "example_rt" {
  vpc_id = aws_vpc.example_vpc.id
  tags = {
    Name    = "${var.project}-${var.environment}-rt"
    Project = var.project
    Env     = var.environment
  }
}

resource "aws_route" "example_route" {
  route_table_id         = aws_route_table.example_rt.id
  gateway_id             = aws_internet_gateway.example_igw.id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route_table_association" "example_subrt" {
  subnet_id      = aws_subnet.example_subnet.id
  route_table_id = aws_route_table.example_rt.id
}

```


###variableファイル
Terraform実施時のグローバル変数を定義する。必要に応じてterraform.tfvarsファイルを作成する。

```variable.tf
# ====================
#
# Variables
#
# ====================


variable "aws_region" {
  default = "ap-northeast-1"
}

variable "project" {
  default = "minimum"
}

variable "environment" {
  default = "test"
}

# IAM #
variable "aws_profile" {
  default = "tf-demo" # AWSプロファイル
}

# EC2 #
variable "instance_type" {
  default = "t2.micro"
}

variable "volume_type" {
  default = "gp2"
}

variable "volume_size" {
  default = "8"
}

variable "user_data_file" {
  default = "./src/user_data.sh"
}

variable "key_name" {
  default = "minimum"
}

variable "public_key_file" {
  default = "~/.ssh/minimum.pem.pub"
}

# VPC #
variable "vpc_cidr_block" {
  default = "10.0.0.0/16"
}

variable "subnet_cidr_block" {
  default = "10.0.1.0/24"
}

variable "availability_zone" {
  default = "ap-northeast-1a"
}

```

###outputファイル
`terraform apply`コマンド実行時の出力内容を定義する。

```output.tf
# ====================
#
# Output
#
# ====================

output "public_ip" {
  value       = aws_instance.example_instance.public_ip
  description = "EC2インスタンスのパプリックIP"
}
```

###configファイル
Terraformのプロバイダー及びバージョン設定を定義する。

```config.tf
# ====================
#
# Terraform
#
# ====================

terraform {
  required_version = ">=1.0.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# ====================
#
# Provider
#
# ====================

provider "aws" {
  profile = var.aws_profile
  region  = var.aws_region
}

```

###user_dataファイル
EC2用ユーザデータの内容を記述する。

```user_data.sh
#!/bin/bash
yum update -y
```

#リソース作成
###Terraform実施
```
$ terraform init
$ terraform plan
$ terraform apply
```

###ping疎通確認
```
$ ping -c4 `terraform output -json | jq -r .public_ip.value`
```

###EC2インスタンスにssh接続
```
$ ssh -i ~/.ssh/minimum_configuration.pem ec2-user@`terraform output -json | jq -r .public_ip.value`
```

#感想
Terraformは作成もさる事ながら、削除するにも非常に便利なツールであり、作成と削除を簡単に繰り返せる事が利点であるように感じる。
次回は冗長性を意識したシステム構成を作ってみたい。

#Githubリポジトリ
https://github.com/kuraboshi/iac/tree/main/terraform/minimum_configuration

#参考文献
- [実践Terraform　AWSにおけるシステム設計とベストプラクティス](https://www.amazon.co.jp/dp/B07XT7LJLC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)