Terraform入門資料(v0.12.0対応) ~基本知識から設計や運用、知っておくべきtipsまで~

![cover (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/853d8fc1-22f8-159f-0191-0271e18659ee.jpeg)

## はじめに
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今日は体調がよろしくないので、大人しく勉強会用のTerraform入門資料をしこしこ作る。。オライリーのIaC本読み返しながら</p>&mdash; nari@BOOTHで好評発売中「GoとAWS CDKで作る本格SlackBot入門」 (@fukubaka0825) <a href="https://twitter.com/fukubaka0825/status/1180689263685296128?ref_src=twsrc%5Etfw">October 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こんにちは。[Wano株式会社](https://wano.co.jp/)の[nari](https://twitter.com/fukubaka0825)と申します。
本日、WanoグループでTerraform入門をテーマとした勉強会を行いました。
その際使用した勉強資料を、Qiitaに一般公開いたします。

## 対象参加者(読者)

- **インフラのコード化ってよく聞くけど、よくわからんって方**
- **インフラのコード化に興味がある、Terraform触ってみたい方**
- **Terraformを学び始める前または始めたばかりで、最新バージョン版(v0.12.0以降)で、設計や運用の話まで一通りのことがさらっと学べる資料を探していた方**
  - ただ何か作って終わり、のハンズオンにはなってないはず？？ 
  - 一通り学んだことがある方は、1章と3章だけお読みいただければ良いかと思います

## 前提
- AWSアカウントもしくはユーザーを所有している(**今回はAWS上でリソースを構築する**)
- EC2とS3操作権限のあるUserのアクセスキーとシークレットキーを発行している(ハンズオンで使用します)

## 勉強会のゴール
- **インフラのコード化のメリットデメリットや文化、その中でのTerraformの立ち位置をきちんと把握すること**
- **Terraformのオススメの学習ソースや知っておくべきtips,運用や設計方法まで一通り把握すること**

## 勉強会の流れ

- 第1章 前提知識・セットアップ編
- 第2章 基本編 ~実際にTerraformでAWS EC2を立てるハンズオン~
- 第3章 応用編 ~ハンズオンで説明しきれなかったが、知っておくべきtips~


# 第1章 前提知識・セットアップ編

ここではインフラのコード化の文化やメリットデメリット、その中でのTerraformの立ち位置について説明し、開発環境を整備します。

## 1.1 そもそもインフラのコード化(Infrastructure as Code)とは 
>Infrastructure as Codeという言葉が現れ、現在のように定着するまでの大まかな流れを振り返ると、その起源は１９９３年に登場した[CFEngine](https://www.pagerduty.com/docs/guides/cfengine-integration-guide/)にまで遡れる
>Infrastructure as CodeはPuppetやChefと深い関係にあり、これらのツールの存在を踏まえて、自然発生的に生まれた言葉であると言えるだろう [^1]     
>Infrastructure as Codeは、ソフトウェア開発のプラクティスをインフラのオートメーションに生かすアプローチだ。[^2]

- **「Infrastructure as Codeは、ソフトウェア開発のプラクティスをインフラのオートメーションに生かすアプローチ」**
- サーバー構成ツールとしてのIaCは、自動化ばかりに焦点を当てた論説が多かったが、クラウドの技術の発展により、旧来の装置産業、鉄の時代だったインフラ管理から、ソフトウェア的にインフラを扱える時代になったことの方が重要である

## 1.2 Infrastructure as Code(IaC)の分類
IaCでは大きく使用するツールは、下記の4つに分類できます。(境界はかなりファジーで、絶対的なものではない) [^3]

- 1.ダイナミックインフラストラクチャプラットフォーム（AWS,GCP等）
  - インフラストラクチャの基本リソース、特に計算(サーバー)、ストレージ、ネットワークを提供する(使い捨てでき、プログラマブルでオンデマンドなクラウドリソース)
- **2.インフラストラクチャ定義ツール（Terraform、CloudFormation等）**
  - **ダイナミックインフラストラクチャの構成/設定を管理、プロビジョニングする。**
- 3.サーバー構成ツール（Ansible,Chef,Puppet等）
  - サーバー自体の細部を扱う。一般のIaCイメージはおそらくここの話
- 4.インフラストラクチャサービス（モニタリング等のインフラやアプリ管理支援サービス）
  - モニタリング、分散処理管理、ソフトウェアのデプロイなどがテーマ
  
![Untitled Diagram (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/f29b8713-539f-5c57-d382-ae72fc41fcdd.png)

**今回は2.インフラストラクチャ定義ツールの一つである、Terraformについてまとめていく。**

## 1.3 インフラのコード化のデメリット
- 小規模なものであれば、CUIやクラウドの提供するマネージメントコンソールでのGUI操作で構築してしまった方が早く、**IaCで構築する方が時間がかかってしまう**
- きちんと保守性を考えた設計し、運用手法やルールをチームで共有していかなければ、**ただ管理する範囲が増えるだけ**で終わってしまう
  - ここはアプリケーションと同一だと考えれば良い
 
## 1.4 インフラのコード化のメリット

- ソースコードでインフラを管理することで、**VCSなりCI/CDといったソフトウェア開発のシステムをインフラにも適用でき、保守性やスケーラビリティを高める**ことができる
- デメリットの裏返しで、多リージョン、多環境への対応が必要なシステムであれば、複製や再利用しやすいためコード化してしまった方が素早く正確に環境を用意できる

<img width="535" alt="スクリーンショット 2019-10-08 22.10.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/8d6ff708-ab1d-bd90-1f77-8365b8a7baee.png">

## 1.5 そもそもTerraformとは
- TerraformはHashiCorp社(VagrantやATLAS開発しているとこ)が開発しているインフラの構成管理ツール
- AWSとかGCPとかその他クラウドリソースなどのリソースの状態定義をソースコードで**宣言的に記述して管理できる**

## 1.6 Terraformのメリット
- HCL (HashiCorp Configuration Language)という独自言語で宣言的に記述し定義するが、Jsonライクで非常にわかりやすく、学習コストが低い(アプリケーション構築経験がなくても記述できる)
- CloudFormationに比べて比較的記述量は少なく宣言できる
- **[公式のドキュメント](https://www.terraform.io/docs/)がとにかく読みやすく、整理されている**
  - [AWS: aws_s3_bucket - Terraform by HashiCorp](https://www.terraform.io/docs/providers/aws/r/s3_bucket.html)などみていただくとわかりやすいかも
- IaC技術として採用企業が非常に多いので、ググラビリティが非常に高く、日本のコミュニティも非常に発展している（ex：[terraform-jp](https://terraformjp.github.io/))

## 1.7 Terraformのデメリット
- AWS CDKやPulumiのようなアプリケーション言語で記述できるものと比べると、デプロイ前後の処理の自由度が低い
- まだバージョン0代(現在0.12.x)なので、破壊的変更がこれからも起こりうる(追従が大変)

## 1.8 Terraformの仕組み
![terraformの仕組み.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/a4cc990b-b35d-4c07-8870-57ebc933c763.png)

- tfファイルというファイルをHCLで記述し、terraformコマンドを実行すると、Providerというmoduleが
指定したサービス上のリソースをAPI経由で作成/計画/実行し、その結果の状態をtfstateというファイルで保持する

>Providers generally are an IaaS (e.g. Alibaba Cloud, AWS, GCP, Microsoft Azure, OpenStack), PaaS (e.g. Heroku), or SaaS services (e.g. Terraform Cloud, DNSimple, CloudFlare).
[Providers - Terraform by HashiCorp](https://www.terraform.io/docs/providers/index.html)より引用

上記のように基本的にクラウドプラットフォームサービスに対してProviderを提供していますが、
APIを提供しているサービスに対してであればProviderを自作し、terraformコマンドで操作できるようにもできます。
例:[GitHub - ndmckinley/terraform-provider-dominos: The Terraform plugin for the Dominos Pizza provider.](https://github.com/ndmckinley/terraform-provider-dominos)は、terraform applyコマンドでドミノピザを注文できるProviderのリポジトリです。(米国でしかテストされてないっぽいけど))


## 1.9 Terraformのセットアップ

### 1.9.1 aws-cliをinstallする

MacOSであれば、以下のコマンドでinstallできます。

```
pip3 install awscli --upgrade

//version確認
aws --version
```
他のOSの場合、以下を参照
[AWS CLI のインストール - AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html)


### 1.9.2 tfenvをinstallする

MacOSであれば、以下のコマンドでinstallできます。

```
brew install tfenv

//version確認
tfenv --version
```

他のOSの場合、以下を参照
[GitHub - tfutils/tfenv: Terraform version manager](https://github.com/tfutils/tfenv)

### 1.9.3 tfenvからterraform 0.12.6をinstallする

```sh

//インストールできるバージョン一覧を取得
tfenv list-remote
//インストール
tfenv install 0.12.6
//インストール済みバージョン一覧を取得
tfenv list
//バージョン切り替え
tfenv use 0.12.6
```

チーム開発なら、以下のような.terraform-versionファイルをリポジトリに含め、
メンバーが「tfenv install」コマンドを実行してバージョンを統一できるようにすると良いです。

```sh

echo 0.12.6 > .terraform-version
tfenv install
```
### 1.9.4 terraformで使用するクレデンシャルを設定する

- 次のスクリプトを実行してください

```sh
$ export AWS_ACCESS_KEY_ID=${YOUR_ACCESS_KEY_ID}
$ export AWS_SECRET_ACCESS_KEY=${YOUR_SECRET_ACCESS_KEY}
$ export AWS_DEFAULT_REGION=ap-northeast-1
```

### 1.9.5 tfstateファイルを置いておくように、AWS S3のバケットを作成する

- Terraformが認識しているリソースの状態をStateと言う。それを保持するファイルをtfstateファイルと言う
- バージョニング設定ありで、名前はなんでも構わない
- tfstateの置く場所を、Backendと呼ぶ

### 1.9.5 Editorのセットアップ
自分が普段使用しているEditorに、Terraform用のPluginがないか探してみてください。(あるのとないのとじゃ、開発効率かなり違う。自動補完とシンタックスハイライトは欲しい。)

- **VSCode**はまだ0.12の記法をサポートしていないっぽいです。。(2019/10/10現在)
[feat: Terraform 0.12 support (was hcl2 support) · Issue #157 · mauve/vscode-terraform · GitHub](https://github.com/mauve/vscode-terraform/issues/157)

- **Intellij**は対応しているので、いかのPluginを入れてください。
チームでIntellijを使っている場合はwatcherファイルなんかでファイルを閉じると自動フォーマットされるようにすると良いと思います。
[HashiCorp Terraform / HCL language support - Plugins \| JetBrains](https://plugins.jetbrains.com/plugin/7808-hashicorp-terraform--hcl-language-support)


- **vimmer**のかたは、以下のものがオススメのようです。
[GitHub - juliosueiras/vim-terraform-completion: A (Neo)Vim Autocompletion and linter for Terraform, a HashiCorp tool](https://github.com/juliosueiras/vim-terraform-completion)
[GitHub - hashivim/vim-terraform: basic vim/terraform integration](https://github.com/hashivim/vim-terraform)

# 第2章 基本編 ~実際にTerraformでAWS EC2を立てるハンズオン~

実際にAWS EC2を立てるハンズオンの中で、基本知識と基本操作を覚えていきましょう。

リポジトリ：[GitHub - wano/terraform_hands_on: Wanoグループ内で、10/10の勉強会で使用した、ハンズオンサンプルです](https://github.com/wano/terraform_hands_on)


## 2.1 EC2のリソース定義をしてみよう(基本的な記述方法を抑える)

まずを以下のようなディレクトリとファイルを作成してください。

```sh

$ tree
.
└── sample_ec2
        └── main.tf         //❶
        └── outputs.tf      //❷
        └── variables.tf　　 //❸
```
❶ **リソースの定義や、providerなどの設定を定義**します。これはmainという名前ではなく、リソースの名前にして何を定義しているファイルなのかわかりやすくすることもできます(今回ならec2_instance.tfとか)
❷ terraform apply時にコンソールに出力され、**他のコンポーネントから参照させたいリソースの値**を記述します。
❸ ここに、**mainで使用する変数**の値を定義します。
この三つがterraform基本セットですので覚えておきましょう。

そして、それぞれ以下のように記述してください。

```hcl:main.tf  
provider "aws" { //❶
  //バージョンを固定
  version = "2.23.0"
  region  = "ap-northeast-1"
}

terraform { //❷
  //バージョンを固定
  required_version = "0.12.6"
  backend "s3" { //❸
    // 保存するbucketとObject keyを
    bucket = "nari-sample-terraform-backend" 
    key    = "sample_ec2/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

data "aws_ami" "ubuntu" {  //❹
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }

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

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "web" {  //❺
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  //default VPCが設定されてない場合は以下を設定する
  //subnet_id = "subnet-019fzxxxxxxxxxx5"
  tags = {
    Name = var.name
  }
//  lifecycle { //❻
//    create_before_destroy = true
//    prevent_destroy = true
//  }
}
```
❶ providerブロックで使用Providerの設定を行います(versionなど)。今回はawsを指定します。
❷ terraformブロックでterraformの設定を書いていきます(versionやどこにtfstateファイルを置くかなど)
❸ backend argumentで、どこにtfstateファイルを置くかを指定します。今回はs3で、**事前に1章で立てていただいたbucketの名前**を指定してください。
❹ dataブロックを使うと**外部データを参照**できます。今回は、[AWS: aws_instance - Terraform by HashiCorp](https://www.terraform.io/docs/providers/aws/r/instance.html)のサンプルのものを使用しています。
❺ resourceブロックで、**デプロイしたいリソースを定義**します。
❻ resourceブロックは、lifecycle argumentを記述することができます。ここではprevent_destroy=trueで削除を防いだり、create_before_destroyで作り変えられるときにリソースを作成してから昔のリソースを削除するといった設定を行うことができます。**lifecycle argumentはmeta-argumentとして定義**されており、どのresourceでも定義することができます
[Resources - Configuration Language - Terraform by HashiCorp](https://www.terraform.io/docs/configuration/resources.html)

```hcl:outputs.tf  

output "sample_ec2" {
  //resourceの単位でoutputできる
  value = aws_instance.web //❶
}

```
❶ mainで定義したresourceのwebを出力しています。*${resource_type}*.*${resource_name}*.*${argument_name}*で記述します。
（v0.12.0以降は、*${argument_name}*を省略するとresource全体をoutputできます)

```hcl:variables.tf  

variable "name" {
  description = "sample_ec2のインスタンスの名前です。"  //❶
  type        = string  //❷
  default     = "HelloWorld"  //❸
}

variable "instance_type" {
  description = "sample_ec2のinstance_typeです"
  type        = string
  default     = "t2.micro"
}

```
❶ 定義した変数の説明は極力description argumentで記載しましょう。
❷ 変数の型はtypeで指定できます。
❸ 変数は環境変数、コマンドの引数、CLIの対話型、tfvarsファイルでの指定、variableのdefault指定のいずれかで渡すことができます。
上書きされたくない場合は、[Local Value](https://www.terraform.io/docs/configuration/locals.html)を使用します。

## 2.2 基本的なコマンド操作をおさえよう

- `terraform init`コマンドを、sample_ec2ディレクトリに移動して実行してみましょう

```sh
-> % terraform init

Initializing the backend...

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

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というディレクトリができ、中にはtfstateができ、pluginがinstallされていると思います。

```sh

-> % tree
.
├── plugins
│   └── darwin_amd64
│       ├── lock.json
│       └── terraform-provider-aws_v2.23.0_x4
└── terraform.tfstate
```

- `terraform fmt`　を実行してみましょう

```
terraform fmt -recursive -check=true　//❶
```
❶ -recursiveで再帰的にフォーマットできます。また、-check=trueにすることで、もしfmt対象があった場合ExitCodeが0以外を返すようになっており、CIに組み込めば自動でチェックできます。

- `terraform plan`を実行してみましょう

```sh
-> % terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.aws_ami.ubuntu: Refreshing state...

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.web will be created
    + resource "aws_instance" "web" {　//❶
    ....
```
❶　デプロイ予定のリソースの詳細がコンソールに出力され、確認できるはずです

## 2.3 実際に立ててみる

- sample_ec2ディレクトリで、以下のコマンドを実行してください

```
terraform 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とうてば実行されます。

```shell-session

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

Outputs:  //❶

sample_ec2 = {
  "ami" = "ami-019f92b9a431da6ed"
  "arn" = "arn:aws:ec2:ap-northeast-1:785020710912:instance/i-0ed3cf51efe923d90"
  "associate_public_ip_address" = false
  "availability_zone" = "ap-northeast-1a"
  "cpu_core_count" = 1
 . . .
}
```
❶outputsファイルで指定したsample_ec2がきちんと出力されていることが確認できます。

- 次のコマンドで、実際に構築されたリソースのtfstateファイルをコンソールで確認できます。

```
terraform state pull
```


- また、環境変数「TF_LOG_PATH」を使うと、ログをファイル出力できます。(ログレベルとして、TRACE・DEBUG・INFO・WARN・ERROR を指定できます)

```bash
$ TF_LOG=debug terraform apply
```
途中経過の処理のログが、コンソールに出ると思います

## 2.4 再利用できるmoduleにリファクタリング

- 以下のようなディレクトリ構成で、ソースを追加します

```
$ tree
.
├── modules
│   └── ec2
│       └── ec2_instance.tf　//❶
│       └── outputs.tf      
│       └── variables.tf　　 
└── sample_ec2_use_modules
  　　　 └── main.tf　　//❷

```
❶で定義したmoduleを、❷で呼び出す形にリファクタリングしましょう。
それぞれのファイルを見ていきます。

```hcl:modules/ec2/ec2_instance.tf 

data "aws_ami" "ubuntu" {  //❶
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }

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

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "web" { 
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  //default VPCが設定されてない場合は以下を設定する
  //subnet_id = "subnet-019f2d7c9bab778e5"
  tags = {
    Name = var.name
  }
  //  lifecycle {
  //    create_before_destroy = true
  //    prevent_destroy = true
  //  }
}

```

❶ 特に変更前のmain.tfと変わりないです。Providerとterraformの記述だけ呼び出す側に移ったってイメージです。

```hcl:modules/ec2/outputs.tf  
output "sample_ec2" {
  //resourceの単位でoutputできる
  value = aws_instance.web
}
```
```hcl:ec2/variables.tf 

//required
variable "name" {
  description = "sample_ec2のインスタンスの名前です。"
  type = string 
      //❶
}

//optional
variable "instance_type" {
  description = "sample_ec2のinstance_typeです"
  type = string
  default = "t2.micro"  //❷
}

```
❶,❷ module側では、defaultを指定するとoptional、defaultをしていないとrequiredな変数として指定してあげられます(呼び出すときに渡すことが必須か任意か)

```hcl:sample_ec2/main.tf
provider "aws" {
  //バージョンを固定
  version = "2.23.0"
  region  = "ap-northeast-1"
}

terraform {
  //バージョンを固定
  required_version = "0.12.6"
  backend "s3" {
    // 保存するbucketとObject keyを
    bucket = "nari-sample-terraform-backend"
    key    = "sample_ec2/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

module "sample_ec2" {  //❶
  source = "../modules/ec2"
  name = "HelloWorld2"  //❷
}
```
❶ modulesで定義したresourceの呼び出しは、moduleブロックで行います。moduleにlifecycleは指定できません
❷ module側のvariableファイルでdefaultが設定されているinstance_typeに関しては渡さなくても作成できます。(optional)
defaultで設定していないnameに関しては、渡してあげる必要があります(required)

詳しい汎用的なmoduleの作り方に関しては、以前にまとめた記事があるので合わせてお読みください。
[Terraform0.12時代に、より汎用的なmoduleを作るためのtips3選 - Qiita](https://qiita.com/fukubaka0825/items/d2a1e1615fbe166f902c)

sample_ec2_for_modulesディレクトリに移動して、sample_ec2ディレクトリで行なった手順通りコマンドを実行すると、HelloWorld2という名前のinstanceが作成されます。

## 2.5 削除する
- sample_ec2ディレクトリで、以下のコマンドを実行してください

```
terraform destroy
```
- sample_ec2_for_modulesディレクトリで、以下のコマンドを実行してください

```
terraform destroy
```

# 第3章 応用編 ~ハンズオンで説明しきれなかったが、知っておくべきtips~
## 3.1 git管理やdocker化から外すべきもの
- VCSとしてgitを使用する場合、リポジトリ作成時に生成できるtemplateでterraformを選択するか、以下の内容をきちんと記述する必要がある
  - .terraform以下にはproviderのpluginなどが含まれかなり容量を食うため、それぞれでterraform initで落としてくるべき
  - .tfvarsにクレデンシャルを含めている場合は必ず.gitignoreに含む
  
```:.gitignore

#  Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# .tfvars files
*.tfvars
```
- docker化でも同様で、.terraform以下は平気で２GBを超えたりするので、必ず対象外にしておく

```:.dockerignore

#  Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# .tfvars files
*.tfvars

```

## 3.2 Terraform0.12.0から取り入れられたHCL2記法について知っておく
これを知らないと、様々な以前の記事は0.11.0以前の記法で書かれているものが多いので、闇雲にコピペしてきて昔の記法が混じってしまうことになリますので、これから始める方もどういうところが変わったかきちんと押さえておきましょう。

- 変数の参照で、いちいち"{}"で囲まなくてよくなったことや
- for文が使えるようになったこと
- dynamic blockというArgumentの単位で繰り返しを制御できるものが追加されたこと
- Object型と呼ばれるよりリッチな連想配列が追加されたこと
- MapからValueを取得するのにlookup関数ではなくMap[Key]でValueを取得できるようになったこと

が非常に大きな変化なので、ここら辺は以下のDocumentも読みつつ押さえておきましょう。

[Upgrading to Terraform 0.12 - Terraform by HashiCorp](https://www.terraform.io/upgrade-guides/0-12.html)
[Terraform 0.11→0.12で追加された新機能 ｜ DevelopersIO](https://dev.classmethod.jp/tool/terraform-0-12-new-features/)

## 3.3 使用可能な型と便利関数
### 3.3.1 型
Primitiveなものはstring,number,boolが使用可能で、それ以外だとmap,list,object型などが使用可能です。
詳しくは、[Type Constraints - Configuration Language - Terraform by HashiCorp](https://www.terraform.io/docs/configuration/types.html)をみてください。
0.12.0から追加されたObject型は、mapと違いvalueとして複数種類の型でそれぞれのフィールドを定義できるので、以下のようにより柔軟にvariableなどを定義できます。


```hcl
variable "cwa_objs" {
  description = "通知したいアラームをここに詰める"
  type = list(object({
    alarm_name          = string
    comparison_operator = string
    evaluation_periods  = number
    threshold           = number
    alarm_description   = string
    metric_name         = string
    namespace           = string
    period              = string
    statistic           = string
    dimensions          = map(string)
    cw_tags             = map(string)
    treat_missing_data  = string
    datapoints_to_alarm = number
  }))
}
```


### 3.3.2 関数
エンコードや文字数字操作まで多種多様なものが用意されているので[Functions - Configuration Language - Terraform by HashiCorp](https://www.terraform.io/docs/configuration/functions.html)を読んで適宜使っていくといいです。
挙動がわかりずらい場合は、`terraform console`コマンドで試してみてください。

```
$terraform console
> max(5, 12, 9)
12
```
個人的には、**file関数**はよく使います。
Jsonで記述しなければならないIam Policy定義などは、その場でヒアドキュメントで書くこともできますが、可読性が落ちるので、Policy定義ファイルとして切り出して、それをPath指定でfile関数で読み込むという風にするといい感じに記述できます。

```hcl
resource "aws_iam_role" "sample" {
  name = "sample-ec2-role"

  assume_role_policy = file("./ec2_policy.json")
}
```

```json:ec2_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

## 3.4 既存リソースをtfファイルに落とし込む手順
- 既存のterraformで構築していないリソースを、terraformのコードに落とす方法として標準では importコマンドが提供されている
  - しかし、こちらはtfstateファイルのみexportしてくれるので、tfファイルの方は、それに合わせる形で自前で書く必要がある
- 標準ではない外部ツールとしては、[terraformer](https://github.com/GoogleCloudPlatform/terraformer)や[terraforming](https://github.com/dtan4/terraforming)といったツールがあり、こちらはtfファイルまでexportしてくれる
- terraforming対応リソースはterraformingでexportし、していない場合はimportコマンド、それすら対応していないリソースは[公式のDocumentのSample](https://www.terraform.io/docs/)や[Terraform Registry](https://registry.terraform.io/)を参考に書くといい
   - terraform importに対応していないリソースの扱い方については、[Terraform職人入門: 日々の運用で学んだ知見を淡々とまとめる - Qiita](https://qiita.com/minamijoyo/items/1f57c62bed781ab8f4d7)が詳しく取り扱ってくださっている

2019/12/15 追記: 詳しく別記事でまとめてみました -> [本番環境リソース以外をTerraformでIaCして別アカウントに移す方法 - Qiita](https://qiita.com/fukubaka0825/items/524cf17d096d2653eefe)

## 3.5 lambdaリソースを扱う場合のインフラとアプリの密結合問題
- terraformでlambdaリソースをデプロイすると、アプリとインフラのdeployが密結合になる
![terraform-what-tf-wants (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/ccfb49fd-fb05-1bd3-5917-bc3b19499a11.png)
 ([Terraform does (not) need your code to provision a lambda function - Amido](https://amido.com/blog/terraform-does-not-need-your-code-to-provision-a-lambda-function/)より引用)

- そこで、Serverless FrameworkやSAMなど別のデプロイツールを使用するか、dummyのtxt(空のzipだとterraformでエラーが発生する)をまずアプリケーションソースとしてあげて、aws lambda update-function-codeで上書きする際に本当のアプリケーションソースを上げることで、インフラとアプリのデプロイを疎結合にするという方法がある
  - Serverless FrameworkやSAMなど別のデプロイツールを使用した場合は、terraformのリソースとの変数のやりとり問題を解決する必要がある
![terraform-what-i-want (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/d6a0d234-5f23-cc8e-b179-eaee15b2e640.png)
 ([Terraform does (not) need your code to provision a lambda function - Amido](https://amido.com/blog/terraform-does-not-need-your-code-to-provision-a-lambda-function/)より引用)

こちらの運用のサンプルについては[Lambda proxy integration×Go(echo)×Terraformでお手軽Web API(サーバレス) - Qiita](https://qiita.com/fukubaka0825/items/d49d77fc5738b1846bb9)で詳しく取り扱っています。

## 3.6 コンポーネント分割(tfstateの分割)
### 3.6.1 コンポーネント分割
![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_321850_55d8a2ee-a6bf-cb84-a491-8280ca4e8d0a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/fdbc6337-1da1-53ff-845f-7f102066709d.jpeg)

- terraformの結果ファイル(tfstate)をモノシリックにするのは怖い
   - **変更の影響範囲が読めない**
   - ファイル自体の可読性が悪くなる
- かといってawsサービスごとに分けると、細かくなりすぎて運用がかなり煩雑になる(makefileやterragruntなどのツールでカバーしても)

上記理由から、いい塩梅のtfstateの分割粒度というのを探し求めてはいますが、まだ答えは出ていません。
しかし、**変更頻度やステートフルなものをごちゃ混ぜでstateファイルとして持つというのは影響範囲が読めず運用を困難にするのが明白**なので、落し所を模索する必要があります。
その際、**以下の観点からコンポーネントについて整理するのは非常に有益**だと思いますので是非やってみてください。(Pragrmatic Terraform on AWS 17章より)

- 安定度
- ステートフル
- 影響範囲
- 組織のライフサイクル
- 関心ごとの分離
- 依存関係の制御

著者がコンポーネント分割について検討した記事 -> [Terraformのコンポーネント分割について検討する - Qiita](https://qiita.com/fukubaka0825/items/103c900a4072121bb4ae)

### 3.6.2 コンポーネントへの参照
分けたコンポーネントへの参照は、terraform_remote_stateというデータソースを使って実現します。

```hcl:main.tf
data "terraform_remote_state" "network" {
  backend "s3"
  config = { //❶
    bucket = "tfstate-pragmatic-terraform"
    key    = "network/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

resource "aws_instance" "server" {
   ...
  subnet_id = data.terraform_remote_state.network.outputs.subnet_id //❷
}
```
❶ terraform_remote_stateのデータソースのなかのconfigで、参照したいコンポーネントのtfstateのある場所を指定してあげます。(今回はS3のobject key)
❷ ❶で定義してあげたterraform_remote_stateを使って、そのコンポーネントのoutputを参照しています。

また、分けたコンポーネントを一気にdeployしてくれたり、remote_stateの記述をOmitしてDRYな記述ができたりする、
[Terragrunt](https://github.com/gruntwork-io/terragrunt)というツールがおすすめです。

## 3.7 複数環境への対応方法(開発,staging,本番)
### Workspaceの利用
- 1つのディレクトリで複数のStateを扱い、Workspaceという機能がある
- 同一ファイル/ディレクトリで複数環境を管理できるので、非常にDRYに記述することを可能にする
- しかし、環境を増やそうとするした時のmoduleの変更がかなり大変
  - 増えれば増えるほど三項演算子などの多用でで可読性は落ちていく
  - 環境切り替えのオペレーションの難易度もかなり高い印象

著者はこちらでの運用経験がないので説明対象外とする。

### ディレクトリで分け、moduleに渡すvariablesで差分吸収する。
  - こちらでやっている人が多い印象 環境複製が非常に容易
  - ただこのDRYではないパターンは[O'Reilly Japan - Infrastructure as Code](https://www.oreilly.co.jp/books/9784873117966/)ではアンチパターンとして紹介されている
    - 例えば、開発環境も本番環境も同じ修正が必要というケースの場合、どちらのファイルも変更して回らなければならない

```shell-session
$ tree
.
├── modules
│   └── ec2
│       └── ec2_instance.tf
└── services
    └── sample
        ├── prod
        │   └── main.tf
        └── stage
            └── main.tf
```

上記のように環境ごとにディレクトリをわけ、それぞれでmoduleを仕様して別ファイルで定義し、それぞれのディレクトリでオペレーションします。
(prodのリソースをいじりたかったらprodのディレクトリでterraformコマンド、stageでも同様)

 その際、[direnv](https://github.com/direnv/direnv)で環境それぞれのディレクトリ（treeの例だとprodとstageディレクトリ)に.envrcを置き、そこにそれぞれの環境のクレデンシャルを仕込むと非常に切り替えが楽です。
変数を全てtfvarsで管理するか問題は、著者は一回やって参照が非常にめんどくさくなるだけで運用にいい影響を感じなかったので、moduleを呼び出す側(treeの例だとprod/main.tf、stage/main.tf)にベタに書いています。

## 3.8 マルチリージョン対応

- v0.11からはmoduleへproviderを明示的に渡せるようになったようです。こちらを使うと既存のmoduleへは変更をせず、moduleを呼び出す側は変更する必要がありません

```hcl:main.tf
provider "aws" {
    region = "ap-northeast-1"
}
provider "aws" {
    region = "us-east-1"
    alias  = "use1"
}
# Default region (ap-northeast-1)
module "network-apn1" {
    source = "./network"
    cidr   = "10.200.0.0/16"
}
# Another region (us-east-1)
module "network-use1" {
    source = "./network"
    cidr   = "10.201.0.0/16"
    providers = { //❶
        aws = "aws.use1"
    }
}
```
❶providersで、regionのaliasを渡してあげることで、そのmoduleで定義されているリソースが指定したリージョンに構築されます。

## 3.9 CI/CDで仕組み化
- なるべく早期に入れてやれると、チームで管理更新が非常に楽になる
- 「Infrastructure as Codeは、ソフトウェア開発のプラクティスをインフラのオートメーションに生かすアプローチ」なので、ここの仕組み化がIaCのわかりやすい恩恵であり、Terraformはわりかしそれがやりやすいエコシステムが構築されている

例えば、まずmerge requestを作成すると、build serverでterraform planが走り、結果を[tfnotify](https://github.com/mercari/tfnotify)というツールでgithubにmergeリクエストのコメントとして送る。
その際に、fmtやtestコードの実行などのCIも自動で走り、その結果もwebコンソールで確認できる。

![CI_CD terraform (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/aadd0742-7611-ac4b-f196-27c36941a793.png)

それを管理者がチェックし、masterにマージするとterraform applyが走り、リソースに反映される
みたいな仕組み化が簡単にできます。
![CI_CD terraform.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/0cc38604-f77e-53d2-0a42-2685f8288a70.png)

以下は著者が自動化した際に書いた記事(インフラとアプリのリポジトリが一緒になっていたりして少し運用を変えていますが参考にはなるかも)
[Terraformで管理しているインフラのデプロイを自動化する - Qiita](https://qiita.com/fukubaka0825/items/64259a73749c75b09d20)

## 3.10 学び始めるときに知っとくといいサイト、ツール集
### 公式Document
[Documentation - Terraform by HashiCorp](https://www.terraform.io/docs/)

### Terraform Module Registry
PublicなModule集です。そのままsourceで読み込んだり、module作成の参考にできたりします。

[Terraform Registry](https://registry.terraform.io/)
### Terraform Source Code Repository

Terraformの本体は以下のリポジトリにあります。
[GitHub - hashicorp/terraform: Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.](https://github.com/hashicorp/terraform)

Providerはterraform-providersというOrganizationの下にあリます。
[Terraform Providers · GitHub](https://github.com/terraform-providers)

### 命名規則
[Naming conventions - Terraform Best Practices](https://www.terraform-best-practices.com/naming)

### terraform の日本語ユーザコミュニティ
Slackチャンネルが存在し、そこでは非常に盛んにterraformについての議論がなされており、質問もしやすい環境です。
[ようこそ terraform-jp slack コミュニティへ \| terraformjp.github.io](https://terraformjp.github.io/)
月に一度、イベントを開催しているので是非行ってみると多くの知見を共有し会えると思います。
[terraform-jp - connpass](https://terraform-jp.connpass.com/)

### ここで語れなかった詳しいデバックや既存リソースのimportなどなどの素晴らしいTips集
[Terraform職人入門: 日々の運用で学んだ知見を淡々とまとめる - Qiita](https://qiita.com/minamijoyo/items/1f57c62bed781ab8f4d7)

### [実践Terraform　AWSにおけるシステム設計とベストプラクティス](https://www.amazon.co.jp/dp/B07XT7LJLC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)
控えめに言って名著です。
同人誌版の頃に、書評を書かせてもらいました。([「Pragmatic Terraform on AWS」が神本だったので紹介する - Qiita](https://qiita.com/fukubaka0825/items/53cd168730815a33f51f))

### [O'Reilly Japan - Infrastructure as Code](https://www.oreilly.co.jp/books/9784873117966/)
控えめに行って(ry
特定のツールに依存しないIaCの考え方がまとまっているので是非参照しながら学習すると良いと思います。
  
## 3.11 最も大事なのはROI(投資対効果)を考えて、IaCと付き合うこと
- 「マネコン(GUI)操作、絶対ダメ」ではなく**手動作成や変更をチームで共有できてないのがダメ**(緊急対応で必要になることもあるはず)
- 適応範囲をチームで決定し、その部分に関してはIaCを徹底する気概を
  - どこまでやる？ -> **複製可能性、マネコン操作の煩雑さ、オペミスがクリティカルな影響を与えうるかetcを加味し、決定する**
  - どこまでIaCされているか、されていないのか、きちんと更新管理されるDocumentを
  - その範囲に関しては、せっかくコード化してるしテストやデプロイの仕組み化をきちんと構築して楽していきましょう
 

## 参考文献
- [Documentation - Terraform by HashiCorp](https://www.terraform.io/docs/)
- Kief Morris(2017年03月)『Infrastructure as Code』(宮下 剛輔　監訳、長尾 高弘　訳) オライリー・ジャパン
- 野村 友規(2019年9月) 『実践Terraform　AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ（NextPublishing）)』 インプレスR&D
- [Terraformで始めるInfrastructure as Code](https://www.slideshare.net/jey0taka/terraforminfrastructure-as-code)
- [Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~ - 長生村本郷Engineers'Blog](http://kenzo0107.hatenablog.com/entry/2019/04/17/103558)
- [Upgrading to Terraform 0.12 - Terraform by HashiCorp](https://www.terraform.io/upgrade-guides/0-12.html)
- [Terraform v0.12で変わるHCLの記述について - Qiita](https://qiita.com/dd511805/items/6e8dd1cf8335d244cf78)
- [Infrastructure-as-Code-is-very-tired - Speaker Deck](https://speakerdeck.com/shogomuranushi/infrastructure-as-code-is-very-tired)
- [Terraformを使ったマルチリージョンでのインフラ構築 - Atsushi Nakajo - Medium](https://medium.com/@jyotti/terraform-multi-region-79a84ce1da0c)
- [Terraform does (not) need your code to provision a lambda function - Amido](https://amido.com/blog/terraform-does-not-need-your-code-to-provision-a-lambda-function/)
[^1]: Kief Morris(2017年03月)『Infrastructure as Code』(宮下 剛輔　監訳、長尾 高弘　訳) オライリー・ジャパン 「監訳者まえがき」 より引用
[^2]: Kief Morris(2017年03月)『Infrastructure as Code』(宮下 剛輔　監訳、長尾 高弘　訳) オライリー・ジャパン 「1章課題と原則 1.2 Infrastructure as Code とは何か」 より引用
[^3]: Kief Morris(2017年03月)『Infrastructure as Code』(宮下 剛輔　監訳、長尾 高弘　訳) オライリー・ジャパン 「1章課題と原則 1.8 この後の章について」 より引用