terraformが怖くて震えていたかつての自分に捧ぐInfrastructure as Code入門(AWS)

# はじめに

こんにちは。インフラとバックエンドとフロントエンドをやっている管理栄養士の @Sadalsuud です。好きな飲み物は RedBull です。

突然ですが、Infrastructure as Code って怖くないですか？

コードでインフラが管理できる。なるほど...それはすごい。
すごいけど...すごすぎて怖い...!?

 - 既存インフラ・本番環境を吹き飛ばしちゃったらどうしよう...!
 - 大量のリソースが意図せず爆誕してしまったら...!?
 - 飲酒したノリで、高価なインフラを大量に生成してしまったら.....!?!?!?

脳裏を過ぎる不安の数々。バックエンドのコードと違い実行によって課金が発生しうるという点や、コマンド一つで本番環境を更地にすることも不可能ではないという圧倒的パワ−に私は震えあがりました。

それでも、怖がりながら使っているうちに、抑えるべき点や確認すべき場所などが見え始め、やっと平常心で使えるようになってきたところです。
今回はそのポイントを記事にまとめようと思います。

# terraformのしくみ

terraformでは、`main.tf` のような `tfファイル` を書き、terraformの各種コマンドを行うことで、クラウド上などにインフラリソースを構築することができます。
AWSやAzure,GCPなどの `provider`を指定して使うようになっており、さまざまなクラウド環境の構築を行うことができます。

terraformの実行コマンドである `terraform apply` を実行すると、そのディレクトリの `*.tf` ファイルを全て読み込んだ上で、指定したCloud環境にインフラを作成してくれます。

![tf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/b21e888b-6254-d866-1dd8-a5d322e47a54.gif)


作成だけではく、設定の変更、もちろん削除も行うことができます。

terraformによって作成されたインフラの情報は　`terraform.tfstate` というファイルに記録されます。
割り振られるリソース識別子なども、リソース作成後にならないとわからないため、実行後に `terraform.tfstate` にまとめられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/61c8a2d3-9def-4f0b-6688-9596fa6f79a3.png)

次の実行が行われるとterraformは、この`terraform.tfstate` の状態と、実行後の状態を比較して差分を表示します。

このterraform.tfstateには、作成したインフラの情報でいっぱいになっているため公開リポジトリに含めるようなことは控えた方がいいと思われます。
個人プロジェクトでない場合はs3などに保管する方法がとられ、私も実務ではこれを使っていますが、今回は詳しく触れません。
参考: [TerraformでtfstateファイルをS3で管理する](https://qiita.com/tsukakei/items/2751e245e38c814225f1)

# terraformはじめの一歩: EC2インスタンスを立ち上げてみよう

4ステップを想定しています。

- IAMユーザーの作成/適用
- terraformの導入
- tfファイルを作成する
- 実行する

##

## IAMユーザーの作成/適用
terraformの実行には、扱うリソースに対するIAM権限が必要です。 `aws configure` などのコマンドにより`aws_access_key_id` や `aws_secret_access_key`等の認証情報をセットします。

[設定ファイルと認証情報ファイル](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-files.html)

中にはAdminとしての認証情報をすでに持っている場合もあると思うのですが、私は怖いので必要権限をあえて絞った権限を追加しています。

`$ aws configure`


## terraformの導入
MACの場合は下記で一発で導入できます。
（私はWSL(Ubuntu)だったので[WSL (Ubuntu 18.04) に Terraform をコマンドラインでインストールする方法](https://azukipochette.hatenablog.com/entry/2018/06/24/004354) を参考に導入しました。)

```shell
$ brew install terraform
```

# tfファイルを作成する
とにかく動くEC2を作成してみます。
（実務では、terraform実行時にはディレクトリ内のすべての `*.tf`ファイルが読み込まれることを利用して、定数を指定するファイルと、作成するリソースを書いたファイルを分離したりしていますが、一旦最低限の記述で実行できるようにしたいと思います。)

```main.tf
provider "aws" {
  version = "~> 2.0"
  region  = "ap-northeast-1"
}

resource "aws_instance" "tf_test_instance1" {
  ami           = "ami-0ebe863c3d16bca9d" # AmazonLinax2 → https://aws.amazon.com/jp/amazon-linux-2/release-notes/
  instance_type = "t2.micro"

  tags = {
    Name = "tf_test(20191201)"
  }
}
```

今回最低限の項目だけ設定していますが、EC2のWebコンソールで設定できるようなの様々な項目も設定可能です。それらについては[ドキュメント](https://www.terraform.io/docs/providers/aws/r/instance.html)から確認できます。

上記ファイルが作成できたら、下記を実行してterraformを初期化します。これは1度だけ実行すればOKです。
今回の場合はこのタイミングでAWSのプラグインがterraformに導入されます。
余談ですが[ドキュメント](https://www.terraform.io/docs/commands/init.html#usage)によるとinitは複数回実行しても大丈夫なようです。

```shell
$ terraform init
```
(wsl環境ではinitが失敗したのでこちらで解決しました [Terraform init で Registry service unreachable. とエラーが出た場合の対処法](https://qiita.com/Masahiro_T/items/e5b4a148023efe268ef5))

## 実行する

お待ちかねのterrafaromの実行です。
ですが、実行前に、現在の記述で正しいか、そして実行した場合何が起こるのか教えてくれるコマンドがあるので、先にこれを実行してみます。

```
$ terraform plan
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/6a82e639-6274-8408-11d3-7f3018b57f17.png)
(画像は一部切り出しです)

実行結果が出力されれました
。
見てみると、リソースが追加することが示され、1つのインスタンスと、それに付随するEBSなどが同時に作成されることがわかります。

では本番の実行です。この内容をAWSに反映するためのコマンドを実行します。

```
$ terraform apply
```
コマンドを実行すると、planの結果が一度返されます。これが最後の最後のチェックです。大丈夫ならここで`yes`を入力してエンターします。

完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/dabab326-6f92-ddc6-178c-f13e35421b54.png)

Webコンソールから確認してみましょう。
今回の実行では最低限の必須項目で実行しているので、VPCやサブネットの指定もしていません。この場合デフォルトVPCのいずれかのサブネットに割り振られ作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/7c414137-7e34-05aa-760e-998e0ee60300.png)

EC2インスタンスが作成されました！！

## リソースを削除/削除防止
テストのために作ったインスタンスなので、不要な課金を避けるために削除したいと思います。

削除のコマンドは `terraform destroy` なのですが、その前に`plan`を実行して、なにが削除されるか確認してみましょう。

```shell
$ terraform plan -destroy
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/40f4b3dc-dc7b-27d9-4cc3-71b61c384eee.png)

これで、先ほど作成したインスタンスが`terraform destroy` すれば削除されることがわかったので実行したい....**のですがその前に！**

**削除防止を実験したいと思います。**

### 削除防止(lifecycleを指定する)
前述のように削除も簡単にできてしまうわけですが、中には「**ちょっとやそっとでは消えて欲しくないリソース**」があると思います。本番環境のインスタンスや、RDSなどが該当するでしょうか。

そういったものには下記を指定することによって、意図しない削除を防ぐことができます。

```tf
  lifecycle {
    prevent_destroy = true
  }
```

上記の記述を、先ほど作った `main.tf` に反映してみましょう。

```main.tf
provider "aws" {
  version = "~> 2.0"
  region  = "ap-northeast-1"
}

resource "aws_instance" "tf_test_instance1" {
  ami           = "ami-0ebe863c3d16bca9d" 
  instance_type = "t2.micro"

  tags = {
    Name = "tf_test(20191201)"
  }

  # 追加
  lifecycle {
    prevent_destroy = true
  }
}
```

この状態でリソースを削除しようとするとどうなるのか `plan` で試してみましょう。

```shell
$ terraform plan -destroy
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/1c89ba13-9be1-13fa-8a08-261818994db8.png)


エラーが発生して、リソース削除してしまうので実行できないとの表示が出ました。

また明示的に `destroy` を指定したわけでなくとも、リソースの破壊は起こることがあります。
例えば、subnetを変えるような変更を加えた際、terraformは今あるインスタンスを一度破壊して、新しいインスタンスを作るという動作をします。

例えばこのようにsubnetを変更したとします。

```main.tf
provider "aws" {
  version = "~> 2.0"
  region  = "ap-northeast-1"
}

resource "aws_instance" "tf_test_instance1" {
  ami           = "ami-0ebe863c3d16bca9d" 
  instance_type = "t2.micro"
  subnet_id = "subnet-xxxxxxxx" # 別のサブネットidを指定する

  tags = {
    Name = "tf_test(20191201)"
  }

  lifecycle {
    prevent_destroy = true
  }
}
```
`prevent_destroy = true` の記述がなければ、現状のインスタンスが削除され、新しいインスタンスが指定したサブネットに生成されますが、今回は削除防止を入れているのでエラーが発生します。

```shell
$ terraform plan -destroy
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/87e4f45b-ea07-4140-db1f-951e434a50d0.png)

先ほどと同様にエラーが発生して終了しました。

さて、すこし寄り道してしましたが、 **晴れてこのインスタンスを削除しましょう。**
サブネットの記述と`prevent_destroy = true`の記述を消すとこのようになります。最初にapplyした状態です。

```main.tf
provider "aws" {
  version = "~> 2.0"
  region  = "ap-northeast-1"
}

resource "aws_instance" "tf_test_instance1" {
  ami           = "ami-0ebe863c3d16bca9d" # AmazonLinax2 → https://aws.amazon.com/jp/amazon-linux-2/release-notes/
  instance_type = "t2.micro"

  tags = {
    Name = "tf_test(20191201)"
  }
}
```
これで

```shell
$ terraform plan -destroy
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/8622a0d8-cb91-f08f-807f-fab9b3d21c2b.png)


全部消えそうです。では晴れて実行します。

```shell
$ terraform destroy
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/b4e7d308-c3dd-a20b-c872-30988b8b87ae.png)

これですべてが更地になりました。
`$ terraform show` をすると、terraformが管理しているリソースがないことがわかります。

# ご安全ポイント
ここまでの振り返りと、プラスαですこしでも安心してterraformするためのことをまとめたいと思います。

### 認証情報を `*.tf`ファイルに書かない
実は認証情報をtfファイルに直書きすることもできます。
こうするとAWS ` $ aws configure` の設定をしないでもいいので楽なんですが.....このtfファイルが流出、例えばコミットされて公開リポジトリに晒されるようなことがあると、簡単に不正利用されてしまいます。
認証情報は漏れるとゲロやばいので、たとえ個人プロジェクトであっても `.tf` ファイルに記載しないほうが無難かもしれません。

参考: [AWSで不正アクセスを受けたので、そのときの対応を記録しておく(返金されました)](https://qiita.com/kojiro_ueda/items/503d24b313a3a80b8ecc)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/eaaea554-51a9-fa87-1021-7c5f4229f58b.png)


### 作成するリソースIAMロールしか付与しない
作成するインスタンスのIAMロールが付与されていなければ、`paln`は通るものの`apply`で失敗します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100231/bcff3350-5476-b80c-285e-ffe788a03198.png)
これを逆手に取れば、不要なリソースを誤って作ってしまうようなことも防げるかもしれません。

### terrafromを実行するIAMロールを指定する
前述のIAMの制限をかけるためにterraformの権限を絞ることができました。
`$ aws configure` で複数プロファイルを設定できますが、特段設定をしないとデフォルトのプロファイルで実行されてしまうはずです。
それがAdmin権限だった場合、terraformはありとあらゆるリソースを作る権限を有していることになります。
もちろん `plan` で確認もするし `apply`してyesを押す前にもよく確認するとは思うんですが..........人間の判断能力を常に期待するのは危ないと個人的には思うので、制御したいお気持ちでおります。


このようにすることでプロファイルを指定することもできるので、不用意なリソースを作成されることを防ぐことができそうです。
忘年会シーズンも安心ですね。もちろん泥酔した状態でもtfファイルの編集とIAMロールの付け替えができる人ならば話は別ですが...。

```.tf
provider "aws" {
  region                  = "ap-northeast-1"
  profile                 = "my-profile" # credentialsのプロファイル名を指定する
}
```


### lifecycleを有効活用して、意図しない削除等の挙動を防止する
前述の通り`prevent_destroy = true`は意図しないリソースの削除を防いでくれます。
が、だからといってこのオプションを乱立させると、意図するリソースの削除の妨げになることもあるので注意した方がよさそうです。
細かいところはドキュメントに詳しく書いてあります。
[lifecycle: Lifecycle Customizations](https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations)

### tfstateファイルを直接編集しない/ローカルに保存しない
terraform.tfstateはterraformが現在のインフラの状態を記憶しているものなので、これを直接編集すると意図しないことが発生するかもしれません。
またローカルに置いておくと、誤って永久に削除してしまったり、意図せず編集してしまったりするかもしれません。実務で使う際はterraformの`buckends`の仕組みを使ってs3にstateファイルをおくのが無難かもしれません。


### ちゃんと `terraform plan` する。
何事も確認しないとマズのはわかりつつも「インスタンスサイズ変えるだけだからいいよね！」で実行し、一度インスタンスが削除されてしまった....。私も「あわや」ということがあったので、変更が自明であっても落ち着いてplanするように心がけています。

### レビューしてもらう
Infrastructure as Codeの利点でもありますがコードになっているので、チーム開発においてはレビューしてもらうことができます。
私のチームでは、アンドロイドエンジニアと機械学習エンジニア出身の2人が構文を覚えて可能な限りレビュ−をしてくれており、とても助かっています。

### `apply` `destroy`は複数人でやる
どんなに気をつけていても、手元と思考が狂って `terraform destrotoy` を放ってしまうかもしれません。
ただ複数人でコマンドを一つ一つ確認して行えば、そういったどうにもなならい不注意を潰すことができそうです。

### CIを利用する
`terraform plan`の出力結果をコンソールから目視で確認するのは限界がありそう。そのようなケースをCIで解決しているケースもあるようです。
すごい。

[メルカリ Microservices Team による Terraform 運用とその中で開発したOSSの紹介](https://tech.mercari.com/entry/2018/04/09/110000)


### terraformで構築したリソースは、Webコンソールから変更をしない
terraformはtfstateに現在のリソースの状況を保存します。ここで、Webコンソールからterraformで構築したリソースに手を加えてしまうと、tfstateと実際の状況に差分が生まれることになります。

タグ名など、tfファイル側で後から整合性をとれる場合もあるのですが、場合によっては一度破壊してから作り直すことを強いられるケースがありかもしれません。

私のケースだとインスタンスサイズをこっそりWebコンソールから上げてしまい、tfファイルをごにょごにょにして差分を合わせようとしたのですが、tfstateが記録しているinstance_idが一致しないため、どうあがいても一度破壊して新しく作るようにplanが走りました。

結果的には　`$ terrafom state rm 【リソース名】` をしてtfstateからリソースの情報を外します。このあとに `$ terraform import【リソースタイプ.リソース名】【リソースID】` することで、AWS側の現状の状態をstateに取り込み、差分をなくすことに成功しました。

ともかく、急ぎでないのではあれば、terraformで管理しているものはterraformで対応してあげるのがよさそうです。

# 終わりに
最近は使うのが楽しくなってきており、大量のリソースが一気に立ち上がるのをコンソールから眺めるのは嬉しくもあります。前述のチームメンバーとも「terraform applyやります！」が一つのイベント事のようになっていてチームとしてもいい感じです。

また、terraform自体がインフラの作業ログであり、過不足なく操作ができたことを保証できるのはとても良いと思っています。

明日は @tom-ock  さんの「VimでReactを書く」です！
お楽しみに！

# 自分が大変お世話になったエントリー
[10分で理解するTerraform](https://qiita.com/Chanmoro/items/55bf0da3aaf37dc26f73)
[Terraform - 仕組みと導入部分を簡単にまとめてみる](https://qiita.com/anfangd/items/08cc332f7f47936c6ed6)