Terraformはじめてみた

# Terraformとは

Terraformを使うとAWSやGCPなどのクラウド上のリソースをコードで管理することができる。"Infrastructure as a Code"を実現するツール。
Webコンソールでポチポチ作成しなくても、コードを書いて叩けば一発でインフラを構築することができる。
Terraformのコード自体バージョン管理することができる。それはつまり**インフラをバージョン管理することができる**ということを意味する。
あとはコードを読むだけでインフラの構成を確認することができる。

# この記事について

Terraformを初めて使ってみたので、そのメモです。
※筆者はAWS・Terraformに長けている訳ではなく、あくまでもメモなので、間違っているところがありましたらご指摘ください

Terraformを用いてAWSを使った以下のようなシステムを構成します。

![名称未設定.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385478/11ed9b14-882c-3061-3016-2a5861deb4af.jpeg)



# Terraformのインストール

macであればHomebrewでインストールすることができる

```
$ brew update
$ brew install terraform
$ terraform --version
Terraform v1.0.9
```

# システム構築

## 準備

まずはAWS CLIをインスールする必要がある。
AWS公式を参照しましょう（参照：[AWS CLI バージョン 2 のインストール、更新、アンインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2.html)）。

```
$ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
$ sudo installer -pkg AWSCLIV2.pkg -target /
$ aws --version
aws-cli/2.2.46 Python/3.8.8 Darwin/20.6.0 exe/x86_64 prompt/off
```

Terraformを使ってAWSの構成を管理するためには、それに使用するIAMを手動で登録する必要がある。
IAMはAWSのWebコンソールから作成する（IAM > 「ユーザーを追加」から作成可能）。
**ただし途中「プログラムによるアクセス」にチェックすることを忘れずに！**
最後、作成後に表示されるアクセスキーIDとシークレットアクセスキーを控えておく。

作成したIAMをAWS CLIに設定する。
これもAWS公式を参照（[設定ファイルと認証情報ファイルの設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-files.html)）。

```
$ aws configure set aws_access_key_id [先程取得したアクセスキーID]
$ aws configure set aws_secret_access_key [先程取得したシークレットアクセスキー]
$ aws configure set region ap-northeast-1
$ aws configure set output json
```

これらの設定情報は`$ aws configure`で確認可能。また、`~/.aws/credentials`と`~/.aws/config`に登録情報が保存される。

## Terraformの基本的な構文

基本的な構文はこんな感じ（参照：[Terraform Language Documentation](https://www.terraform.io/docs/language/index.html)）。
以下は「<リソース>というresourceを<ラベル>という名前で作成する」ということを意味する。resourceはVPCとかルートテーブルとかのことです。

```terraform
resource "<リソース>" "<ラベル>" {
  //詳細設定
}
```

別のリソースで上記のリソースのIDを参照するときは`<リソース>.<ラベル>.id`みたいな感じで参照できる。

## プロバイダーの構築

まずはプロバイダーの構築を行う。
今回はAWSを用いるので、`aws`を指定する。GCPとかAzureを使うならそっちを指定する。
`region = "ap-northeast-1"`でリージョンを指定して、そこにリソースを作成していく。

```terraform
provider "aws" {
  region = "ap-northeast-1"
}
```

## VPCの構築

- [Resource: aws_vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc)

```terraform
resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"
  instance_tenancy = "default"
  enable_dns_hostnames = true #作成時にDNSホスト名を設定するように指定
  tags = {
    Name = "VPC Area"
  }
}
```

## サブネットの構築

- [Resource: aws_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet)

```terraform
# パブリックサブネットの構築
resource "aws_subnet" "public_subnet" {
  vpc_id = aws_vpc.vpc.id #VPCの指定
  cidr_block = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "Public Subnet"
  }
}

# プライベートサブネット
resource "aws_subnet" "private_subnet" {
  vpc_id = aws_vpc.vpc.id #VPCの指定
  cidr_block = "10.0.2.0/24"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "Private Subnet"
  }
}
```

## サーバーの構築

- [Resource: aws_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance)

以下では予めAMIのIDを調べておき、それを直接指定していますが、[Resource: aws_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ami)を使って指定することもできます。

```terraform
# Webサーバー
resource "aws_instance" "web" {
  ami = "ami-0701e21c502689c31"
  instance_type = "t2.micro"
  associate_public_ip_address = true #作成時にパブリックIPアドレスを紐付ける
  subnet_id = aws_subnet.public_subnet.id #サーバーをパブリックサブネットに置く
  key_name = "my-key" #キーペア
  vpc_security_group_ids = [
    aws_security_group.security_group_web.id #セキュリティグループの設定（後述）
  ]
  tags = {
    Name = "WEB SERVER"
  }
}

# DBインスタンス
resource "aws_instance" "db" {
  ami = "ami-0701e21c502689c31"
  instance_type = "t2.micro"
  associate_public_ip_address = false #インターネットに接続しないのでパブリックIPはいらない
  subnet_id = aws_subnet.private_subnet.id #サーバーをプライベートサブネットに置く
  key_name = "my-key" #キーペア（WEBサーバーと同じものを使っているが、別でもOK）
  vpc_security_group_ids = [
    aws_security_group.security_group_db.id #セキュリティグループの設定（後述）
  ]
  tags = {
    Name = "DB SERVER"
  }
}
```

### キーペアについて

サーバーに接続する際は、キーペアを登録しておく必要がある。
Webコンソールからサーバーを立ち上げるときは作成時にキーをダウンロードできるが、Terraformでは作成時にキーが自動的には作成されない。

なのでAWS CLIを使ってキーペアを予め登録しておく。
キーペアの作成についてもAWS公式を参照しましょう（参照：[Amazon EC2 キーペアの作成、表示、削除](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-services-ec2-keypairs.html)）。

```
$ aws ec2 create-key-pair --key-name my-key --query 'KeyMaterial' --output text > my-key.pem
```

このコマンドにより"my-key"という名前のキーペアを登録できる（webコンソールからも確認可能）。
この`my-key`をTerraformでEC2を立ち上げるときに指定する。
また、プライベートキーはmy-key.pemとしてダウンロードされる。

## ルートテーブルの作成

- [Resource: aws_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table)
- [Resource: aws_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route)
- [Resource: aws_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association)

ルートテーブルの「ハコ」を作り、ルートをそれぞれ作成して紐付け、加えてテーブルをサブネットに関連付けする、という形で構築する。

```terraform
# ルートテーブルの作成
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "Public Route Table"
  }
}

# ルートテーブルにルートを挿入
resource "aws_route" "r" {
  route_table_id = aws_route_table.public_route_table.id
  gateway_id = aws_internet_gateway.gw.id #インターネットゲートウェイに開放
  destination_cidr_block = "0.0.0.0/0"
}

# ルートテーブルとサブネットの関連付け
resource "aws_route_table_association" "public" {
  subnet_id = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route_table.id
}
```

## セキュリティグループの作成

- [Resource: aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)
- [Resource: aws_security_group_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule)

こちらも同様で、セキュリティグループという「ハコ」をつくり、ルールを１つずつ作って紐付けていく。

```terraform
# WEBサーバーのセキュリティグループ
resource "aws_security_group" "security_group_web" {
  name = "WEB-SG"
  description = "Allow SSH connection by developers"
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "WEB-SG"
  }
}

# インバウンドルールの設定①
resource "aws_security_group_rule" "inbound_ssh" {
  type = "ingress" #インバウンドルールであることを示す
  description = "SSH"
  from_port = 22
  to_port = 22
  protocol = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group_web.id #セキュリティグループに紐付け
}

# インバウンドルールの設定②
resource "aws_security_group_rule" "inbound_http" {
  type = "ingress" #インバウンドルールであることを示す
  description = "http"
  from_port = 80
  to_port = 80
  protocol = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group_web.id #セキュリティグループに紐付け
}

# アウトバウンドルールの設定
resource "aws_security_group_rule" "egress" {
  type = "egress" #アウトバウンドルールであることを示す
  from_port = 0
  to_port = 0
  protocol = "-1" #「すべてのプロトコル」を意味する
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group_web.id #セキュリティグループに紐付け
}

#
# DBサーバーのセキュリティグループもほとんど同じなので割愛
#
```

## NATゲートウェイの構築

```terraform
# NATゲートウェイ
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat_eip.id #NATのIPアドレスを指定
  subnet_id = aws_subnet.public_subnet.id #どのサブネットに紐付けるか指定
  tags = {
    Name = "gw GW"
  }
}

# NATで使用するIPアドレス（EIPを使ってIPアドレスを固定化する）
resource "aws_eip" "nat_eip" {
  vpc = true
  tags = {
    Name = "NAT EIP"
  }
}

# メインルートテーブルのデフォルトゲートウェイをNATに向ける
resource "aws_route" "nat_route" {
  route_table_id = aws_vpc.vpc.default_route_table_id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id = aws_nat_gateway.nat.id
}
```

# Terraformの実行

まずは上記のコードを`.tf`ファイルにまとめ、どこかのディレクトリに置く。

```
$ terraform init
```
`init`コマンドによって、Terraformの作業ディレクトリを初期化する。これをやってから`plan`や`apply`のコマンドも叩けるようになる。

```
$ terraform plan
```

`plan`コマンドを叩くと、ファイル適用時の差分を表示する（どのリソースを作成・削除するかなどが表示される）。同時にファイルの整合性も確認するので、この時点でエラーが出れば修正する。

```
$ terraform apply
```

`apply`コマンドによって実際にシステムを構築する。

```
$ terraform destroy
```

`destroy`コマンドはTerraformで構築したインフラを一気に削除することができる。
一部のリソース削除し忘れで数ヶ月料金払っていた自分みたいな人間には本当に助かる。

#　参照
- [Terraform Registry](https://registry.terraform.io/)
- [Terraformで構築するAWS](https://y-ohgi.com/introduction-terraform/)
- [Terraform AWS EC2編](https://www.se-from30.com/aws/terraform-ec2-01/)
- [Terraform 入門](https://qiita.com/kobayashi-m42/items/247cf9708044db8a234e)