TerraformでVPC・EC2インスタンスを構築してssh接続する

# 環境

- Terraform 0.12.16
  - provider.aws v2.41.0

# Terraformでインフラを構築する

## 秘密鍵・公開鍵を作成する

以下のコマンドで2つのファイルが作成されます。
EC2にssh接続する際に必要になるので、先に作成しておきましょう。

- `example`
- `example.pub`

```bash
$ ssh-keygen -t rsa -f example -N ''
```

出力

```
Generating public/private rsa key pair.
Your identification has been saved in example.
Your public key has been saved in example.pub.
The key fingerprint is:
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxx@xxxxx-xxx
The key's randomart image is:
+---[RSA 2048]----+
|X=oo...o   .*+   |
| +.=o.o . .+++   |
| oB.++oo...o+    |
|o..=++ =oo       |
| o..o + S .      |
|.... . .         |
|o.. E            |
|..               |
|                 |
+----[SHA256]-----+
```

## サンプルコード

わかりやすくするために今回はモジュールや変数といった機能は使用しません。

### Provider

```terraform:provider.tf
# ====================
#
# Provider
#
# ====================
provider "aws" {
  # アクセスキーID・シークレットアクセスキーを直接指定しているサンプルコードをたまに見かけますが、
  # 流出の危険等もあるため、基本的には`aws configure`コマンドでprofileを設定して使うようにしましょう
  profile = "default"
  region  = "ap-northeast-1"
}
```

### EC2

```terraform:ec2.tf
# ====================
#
# AMI
#
# ====================
# 最新版のAmazonLinux2のAMI情報
data "aws_ami" "example" {
  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" {
  ami                    = data.aws_ami.example.image_id
  vpc_security_group_ids = [aws_security_group.example.id]
  subnet_id              = aws_subnet.example.id
  key_name               = aws_key_pair.example.id
  instance_type          = "t2.micro"

  tags = {
    Name = "example"
  }
}

# ====================
#
# Elastic IP
#
# ====================
resource "aws_eip" "example" {
  instance = aws_instance.example.id
  vpc      = true
}

# ====================
#
# Key Pair
#
# ====================
resource "aws_key_pair" "example" {
  key_name   = "example"
  public_key = file("./example.pub") # 先程`ssh-keygen`コマンドで作成した公開鍵を指定
}
```

### VPC

```tf:vpc.tf
# ====================
#
# VPC
#
# ====================
resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true # DNS解決を有効化
  enable_dns_hostnames = true # DNSホスト名を有効化

  tags = {
    Name = "example"
  }
}

# ====================
#
# Subnet
#
# ====================
resource "aws_subnet" "example" {
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  vpc_id            = aws_vpc.example.id

  # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる
  map_public_ip_on_launch = true

  tags = {
    Name = "example"
  }
}

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

  tags = {
    Name = "example"
  }
}

# ====================
#
# Route Table
#
# ====================
resource "aws_route_table" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}

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

resource "aws_route_table_association" "example" {
  subnet_id      = aws_subnet.example.id
  route_table_id = aws_route_table.example.id
}

# ====================
#
# Security Group
#
# ====================
resource "aws_security_group" "example" {
  vpc_id = aws_vpc.example.id
  name   = "example"

  tags = {
    Name = "example"
  }
}

# インバウンドルール(ssh接続用)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.example.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.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.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}
```

### Output

```terraform.output.tf
# ====================
#
# Output
#
# ====================
# apply後にElastic IPのパブリックIPを出力する
output "public_ip" {
  value = aws_eip.example.public_ip
}
```

## リソースを作成する

### init

```bash
$ terraform init
```

```
Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.41.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.41"

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.
```

### apply

```bash
$ terraform apply
```

途中で入力を求められるため、`yes`と入力してEnterキーを押しましょう。

```
...
  Enter a value: yes
...
Apply complete! Resources: 13 added, 0 changed, 0 destroyed.

Outputs:

public_ip = 18.176.43.22
```

リソースの作成に成功したら最後にElastic IPのパブリックIPが出力されるので控えておきましょう。
今回は`18.176.43.22`ですね。

## pingコマンドが通るか確認

ICMPのインバウンドルールを作成している場合は`ping`が通るか確認してみましょう。

```bash
$ ping 18.176.43.22
```

```
PING 18.176.43.22 (18.176.43.22): 56 data bytes
64 bytes from 18.176.43.22: icmp_seq=0 ttl=233 time=25.536 ms
64 bytes from 18.176.43.22: icmp_seq=1 ttl=233 time=25.601 ms
64 bytes from 18.176.43.22: icmp_seq=2 ttl=233 time=23.365 ms
64 bytes from 18.176.43.22: icmp_seq=3 ttl=233 time=18.041 ms
64 bytes from 18.176.43.22: icmp_seq=4 ttl=233 time=16.485 ms
64 bytes from 18.176.43.22: icmp_seq=5 ttl=233 time=23.434 ms
64 bytes from 18.176.43.22: icmp_seq=6 ttl=233 time=17.514 ms
```

## ssh接続

`ssh -i 秘密鍵 ec2-user@パブリックIP`でssh接続してみましょう。

```bash
$ ssh -i ./example ec2-user@18.176.43.22
```

最初は`Are you sure you want to continue connecting (yes/no)?`と聞かれますが、`yes`と入力してEnter押しておけば大丈夫です。

```
The authenticity of host '18.176.43.22 (18.176.43.22)' can't be established.
ECDSA key fingerprint is SHA256:l5ZQdCH2Axyjb+pWybQeQGT2S06w/QyEVNOREFjhJSk.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '18.176.43.22' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
5 package(s) needed for security, out of 13 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-1-14 ~]$
```

できた！

# 後片付け

作ったリソースたちを削除しましょう。

```bash
$ terraform destroy
```

```
...
  Enter a value: yes
...
Destroy complete! Resources: 13 destroyed.
```

# あとがき

Terraform、本当に便利ですね。

# 参考

- [Provider: AWS \- Terraform by HashiCorp](https://www.terraform.io/docs/providers/aws/)
- [AWS CLIのインストールから初期設定メモ \- Qiita](https://qiita.com/n0bisuke/items/1ea245318283fa118f4a#aws-cli%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)