Terraform 入門

# 概要
職場でTerraform勉強会を開催することになったので、勉強会の資料のベースとなる内容を記事にしました。
これからTerraformを触りはじめる方が最低限知っておけば良さそうだと思った内容をまとめています。この記事では、構築対象のリソースはAWSを想定しています。

---

# 対象読者
- Terraformを使ったことがなく、これから触ってみたい方
- AWSを手動で構築したことがあるけれど、コードで管理したことがない方

---

# この記事のゴール
- Terraformの概要を把握する
- 基本的な文法を覚える
- 必要なドキュメントから自力で情報を探せるようになる

この記事では、0からTerraformプロジェクトを構築できるようになることは目指していません。
(ディレクトリ構成の話などは長くなってしまうので、それはまた別の記事にまとめたい)

---

# 導入

---

## Terraformとは
[HashiCorp](https://www.hashicorp.com/)が開発しているインフラ構成をコードを使って管理するためのツールです。
HCLという記法でコードを記述し、インフラの状態を管理することができます。

この記事で取り上げるAWSだけでなく、複数のプロバイダーに対応しているのが特徴です。
[Providers - Terraform by HashiCorp](https://www.terraform.io/docs/providers/index.html)

- AWS
- Azure
- GCP
- Datadogなど

---

また、Terraform以外にもAWSリソースをコードで管理するためのツールとしてAWS公式が出しているものもあります。

- AWS CloudFormation
    - AWS専用のツール
    - JSONまたはYAML形式でテンプレートを作成

- AWS Cloud Development Kit (AWS CDK)
    - AWS専用のツール
    - プログラミング言語でリソースを定義(TypeScript, JavaScript, Python, Java, C#)
    - コードからCloudFormationのテンプレートが作成され実行される  

---

## Terraformを使うと何が嬉しいのか

- インフラの構築や変更を自動化できる
    - 操作ミスを防ぐことができる
    - 複数環境に構築する場合など、作業を減らすことができる
- インフラの状態をコードで管理できる
  - Gitなどでバーション管理されている場合、過去の変更内容まで管理できる
- リモートワークしていてもレビューしやすい

---

## Terraform 実行環境の構築
Terraformを使ってAWSリソースを構築するために必要な環境構築です。

- AWS CLIのインストール

[AWS CLI のインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html)を参考にインストールしてください。
v1とv2が存在しますが、プロジェクトに合わせて選択してください。

- AWS クレデンシャルの登録

今回は、名前付きプロファイルとして設定してください。Terraformでprofileを指定することで複数環境への構築が簡単にできます。
[設定ファイルと認証情報ファイルの設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-files.html)を参考に登録します。

`sample-dev`,`sample-prod`というprofileを設定した例です。

```:~/.aws/credentials
[sample-dev]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[sample-prod]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
```

- tfenvを使ってterraformをインストール

[tfenv](https://github.com/tfutils/tfenv)はTerraformの複数のバージョン管理することができるツールです。
使用方法は、下記の記事にまとまっていたので参考に載せさせて頂きます。
[tfenvでTerraformのバージョン管理をする](https://qiita.com/kamatama_41/items/ba59a070d8389aab7694)

tfenvを使わずにDockerを使用してもいいです。Terraformはマイナーバージョンでも破壊的な変更を行う事があるのでバージョンを気軽に切り替えられるようになっていればOKです。

---

ここまでで導入はおしまいです。

---

# Terraform 入門

---

## tfstateファイルの管理
最初に、tfstateファイルという重要なファイルについて理解しましょう。

tfstateファイルはTerraformで管理しているインフラの状態を記録するためのファイルです。
Terraformによってインフラを構築すると、自動的にインフラの状態がtfstateファイルに記録されます。インフラに変更を加える場合などは、このtfstateファイルを見てリソースの追加・変更があるかどうかを判断されます。

この自動で生成されるtfstateファイルですが、保存する場所を指定しない場合はTerraformを実行したディレクトリに保存されます。
ローカル環境にtfstateファイルがあると、メンバーが最新のtfstateファイルからリソースの状況を参照することができなくなってしまうため、通常はS3バケットなどで管理します。こうすることで、メンバー間でtfstateファイルを共有することが可能になります。

注意点：S3バケットにバージョニングを設定しておくことをお勧めします。tfstateファイルを間違って変更、削除してしまった場合に復旧することができます。

---

## ディレクトリ構成について
Terraformの解説を進めるあたってサンプルとなるディレクトリ構造を定義し、これに沿って説明を進めたいと思います。

プロダクトでTerraformを利用する場合、複数環境にAWSリソースを構築するケースはよくあるパターンだと思います。
複数環境に対応するための構成として下記のディレクトリ構成を紹介します。

```
  ├ modules/
  │  ├ vpc/
  │  └ api/
  │
  └ environments/
     ├ dev/
     │ ├ network/  <- 作業ディレクトリ
     │ └ api/      <- 作業ディレクトリ
     └ prod/
       ├ network/  <- 作業ディレクトリ
       └ api/      <- 作業ディレクトリ
```

---

このディレクトリ構成のポイントは下記の通りです。

- `/module`ディレクトリに複数の環境で使用される`module`を作成する
- 環境ごとのディレクトリを用意する
- tfstateファイルを作業ディレクトリ毎に作成する


環境ごとの作業ディレクトリ配下でTerraformのコマンド(`terraform init`や`terraform apply`)を実行します。

---

このディレクトリ構造のメリット・デメリット

- メリット
  - 複数環境に対応している
  - 環境差分を許容できる(dev環境だけに必要なコードは、`/dev`ディレクトリ配下にのみ追加する)
  - `module`を使用することで共通のコードで定義できる(同じようなコードを何度も書くことが減る)
  - tfstateファイルを分割することで、問題があった場合の影響範囲が小さくなる
  - tfstateファイルを分割することで、`terraform plan/apply`の実行時間が短くなる

- デメリット
  - 子モジュールの設計が難しい
  - 環境分に同じようなコードを書く必要がある(`/dev`と`/prod`ディレクトリ配下に同じようなコードが追加される)

---

このディレクトリ構成以外にも、複数環境に対応する方法があります。下記がわかりやすかったので参考にしてみてください。
[Terraformのベストなプラクティスってなんだろうか | フューチャー技術ブログ](https://future-architect.github.io/articles/20190903/)

---

## Moduleについて
ディレクトリ構成の解説の中で出てきた`module`について説明します。

`module`は、複数のリソースをまとまった単位で定義することができます。`module`を使用することでリソースを簡潔に定義し、複数回呼び出して再利用性を高めることができます。ディレクトリ構成の解説では、複数環境に構築するために`module`を利用していました。

また、この`module`は子モジュールと呼ばれます。
作業ディレクトリの`.tf`ファイルはすべてルートモジュールとなり、このルートモジュールから子モジュールを呼び出すことができます。

`module`をどのように設計するのかはチームによって異なると思いますが、ここでは`module`の設計については触れません。

---

## 基本的なコマンド
Terraformの具体的なコードの書き方を学ぶ前に、基本的なコマンドを見ていきましょう。
Terraformをどのように実行し、AWSリソースが構築されるまでの流れが把握できれば良いです。

まず基本のコマンドとして、下記の5つを理解しましょう。
これらを扱うことができれば、AWSにリソースを構築、変更、削除ができます。

また、これらのコマンドはディレクトリ構成の解説の中で出てきた、作業ディレクトリで実行します。

---

### terraform init
初めに必ず実行する必要があります。
ワークスペースの初期化やプラグインがダウンロードされます。

---

### terraform plan
変更内容が表示されます。
コードを作成、変更した場合は、`terraform plan`することで実行内容に問題ないかを確認します。

---

### terraform apply
変更を実行します。ここで完了すると、実際にAWSにリソースが構築されます。
変更内容が表示されるので、問題なければ`yes`を入力します。

---

### terraform destroy
リソースを破棄します。
破棄対象のリソースが表示されるので、問題なければ`yes`を入力します。

---

### terraform fmt
インデントなどのスタイルをフォーマットするためのコマンドです。
`terraform fmt -recursive`で再帰的にフォーマットできます。
Gitにコミットする前は実行するようにしましょう。

---

## 基本的構文(1)
ここまででTerraformを実行する流れをイメージできたのではないかと思います。
ここからは、Terraformのコードを書くために必要となる構文を理解していきましょう。

ここで紹介する構文はルートモジュールで使用します。

---

### terraform
Terraformに関する設定を定義します。

- Backend

tfstateファイルをS3バケットで管理するための設定です。
今回のサンプルでは、`profile`も指定します。

```hcl:backend.tf
terraform {
  backend "s3" {
    bucket  = "dev-tfstate"
    key     = "ecr/terraform.tfstate"
    region  = "ap-northeast-1"
    profile = "sample-dev"
  }
}
```

- Versions

Terraformとプロバイダーのバージョンを指定します。

インストールされているTerraformのバージョンとここで定義されているバージョンが合っていないと、`terraform init`した際にエラーとなります。
プロバイダーはここで指定したバージョンがインストールされます。

```hcl:versions.tf
terraform {
  required_version = "0.12.24"

  required_providers {
    aws = "2.57.0"
  }
}
```

---

### provider
AWSを利用するための定義をします。
今回のサンプルでは、`profile`も指定します。

```hcl:provider.tf
provider "aws" {
  region  = "ap-northeast-1"
  profile = "sample-dev"
}
```

AWS以外も定義できます。Datadogの例。

```hcl:provider.tf
provider "datadog" {
  api_key = var.datadog_api_key
  app_key = var.app_key
}
```

---

## 基本的構文(2)
最低限知っていると良さそうなものを紹介します。

---

### Input Variables
変数を定義します。
ルートモジュールで変数を宣言すると、CLIオプションと環境変数を使用してそれらの値を設定できます。
子モジュールで宣言すると、呼び出し元のモジュールの`module`ブロックで値を渡す必要があります。

```hcl
variable "image_id" {
  type        = string
}

resource "aws_instance" "api" {
  instance_type = "t2.micro"
  ami           = var.image_id
}
```

---

### Local Values
モジュール内に閉じて使える変数です。

```hcl
locals {
  service_name = "forum"
  owner        = "Community Team"
}
```

---

### Resources
インフラのリソースのことです。

CloudWathLogsを構築するリソースの例。

```hcl:cloudwatchlog.tf
resource "aws_cloudwatch_log_group" "api" {
  name              = local.cloudwatch_log_name
  retention_in_days = local.cloudwatch_log_retention_in_days
}
```

参考：[AWS: aws_cloudwatch_log_group - Terraform by HashiCorp](https://www.terraform.io/docs/providers/aws/r/cloudwatch_log_group.html)

---

### Data Sources
Terraformの外部で定義された情報を利用する際に使います。

Terrafromの外部で作成された`sample-vpc`という名前のVPCのIDを取得する例。

```hcl:datasources.tf
data "aws_vpc" "vpc" {
  filter {
     name   = "tag:Name"
     values = ["sample-vpc"]
   }
}

locals {
  vpc_id = data.aws_vpc.vpc.id
}
```

参考:[AWS: aws_vpc - Terraform by HashiCorp](https://www.terraform.io/docs/providers/aws/d/vpc.html)

---

### Output
指定したリソースの情報を出力できます。
`terraform apply`した際に、ターミナルに出力されます。ここで出力した値は、別のtfstateから参照できます。

---

### remote_state
異なるtfstateファイルに記録されているリソースの情報を参照することができます。
ディレクトリ構成のサンプルだと、vpcのIDをapiのモジュールから参照したいケースなどで使用できます。

---

## Terraformバージョンについての注意

Terraform 0.12で構文に変更がありました。
記述が大きく変更されているので、注意が必要です。

詳細は下記を参考にしてください。

- [Upgrading to Terraform 0.12 - Terraform by HashiCorp](https://www.terraform.io/upgrade-guides/0-12.html)

- [Terraform 0.11→0.12で追加された新機能 | Developers.IO](https://dev.classmethod.jp/articles/terraform-0-12-new-features/)
 
---

## ドキュメント

---

[公式ドキュメント](https://www.terraform.io/docs/providers/aws/index.html)
各Resourcesのパラメータ等はここで確認します。
新しくリソースを追加する時は、まずここを見ます。

[Terraform Recommended Practices](https://www.terraform.io/docs/cloud/guides/recommended-practices/index.html)
公式が公開しているベストプラクティス。
設計方針を決める前に一通り見ておく事を推奨します。

[Terraform Module Registry](https://registry.terraform.io/)
Terraformの開発元である、HashiCorp社が作成したmodule等を見る事が出来ます。
リソースを追加する際は、ここを参考にすると良いです。

[terraform-providers/terraform-provider-aws](https://github.com/terraform-providers/terraform-provider-aws)
AWSプロバイダーのGitHubのリポジトリです。
CHANGELOG.md を見ると、バージョンアップにより対応された機能を確認することができます。
使用しているAWSプロバイダーのバージョンが古くてTerrafrom実行時にエラーになることがたまにあり、その際に見に行ったりします。

---

## 終わり