【爆速Terraform入門】AWS環境をコードで管理してみよう

まえがき
---
東南アジア発のスタートアップスタジオ、[GAOGAO](https://gaogao.asia/)でエンジニアをしているMass-minと申します。
先日は、GAOGAOにて開発をサポートさせていただいている株式会社Ancar様のアドベントカレンダーで、[記事](https://qiita.com/mass-min/items/9c28d77b626581e35fe6)を書かせていただきました！まだ読んでない方は是非！

[【ザンギョーウはヤメロ】スクラム開発で、チームが絶対に残業してはいけない理由](https://qiita.com/mass-min/items/9c28d77b626581e35fe6)

そんなこんなで、今日はAncar様にてサービス開発を行った際に得た技術的知見をアウトプットしていこうかと思います。


インフラ構築どうしてる？
---
みなさま、インフラ構築ってどうしてますか？
よほど大きな規模のプロダクトやレスポンスにシビアな要求があるプロダクトでない限りは、お手軽さという面でオンプレよりクラウドに軍配が上がるかと思います。
AWSやGCP、Azureなどを使っているサービスは、今や世の中の大半を占めると言っても過言ではないかと思われます。

そんな今の世の中ですが、一度クラウドサービスのコンソール上でポチポチボタンを押して環境を構築した後、その環境をドキュメントなり何なりで適切に管理、保守出来ているチームは意外に少ないのでは、と思いました。
ドキュメントに落とし込んでいれば、同じような環境をもう一度作成する場合のスピードは段違いになりますよね。特にスタートアップにおいて、仮説検証の段階で環境を作っては壊しを繰り返す場合は顕著です。
また変更した箇所をちゃんとドキュメントにも反映していれば、わざわざコンソール画面の中で設定の項目を見なくてよくなります。
したがって、何かしらのカタチで環境をまるごと管理したいという欲求が出てきます。

ここで登場するのが[Terraform](https://www.terraform.io/)です。Terraformはクラウド環境を独自の言語でコード化して管理し、コマンド1発で環境のセットアップ、及び削除を行えるツールです。
TerraformはHashiCorp社が運営するサービスで、AWSが提供しているわけではありません。AWSに縛られず、GCPやAzureなどの環境もTerraformでコード管理することが出来ます。つまり一度Terraformの書き方を覚えてしまえば、どんな環境でもおおよそコード化して管理できてしまう、というわけです。ステキじゃないですか！

というわけで今回は、Terraformの入門編。EC2インスタンスの設定をコード化して、環境構築を行ってみましょう。

おしながき
---
- 前提
- Terraformのインストール
- EC2インスタンスの設定
    - 初級編 - TerraformでEC2インスタンスを作成してみる
    - 中級編 - コードの内容を追ってみる
    - 実践編 - Nginxをインストールしてトップページを表示

前提
---
- OS: macOS 10.15.1
- AWS CLIはインストール済み(credentials設定済み)
  - まだ入ってない人は以下の手順でインストールしてください
     - [macOS に AWS CLI をインストールする](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-macos.html)

Terraformのインストール
---
Macの場合はHomebrewでTerraformがインストールできます。

```shell:HomebrewでTerraformをインストール
$ brew update
$ brew install terraform
```

2019/12/03時点では、Ver0.12.16が入りました。

```text:Terraformのバージョン確認
$ terraform -v
Terraform v0.12.16
```

EC2インスタンスの設定
---
### 初級編
さて、ではお待ちかねのEC2インスタンス作成です。
まずTerraformのコードを置くディレクトリを作成しましょう。

```shell:ディレクトリを作成し移動
$ mkdir terraform
$ cd terraform/
```

#### Terraformコードを書くファイルの作成と初期設定
Terraformのコードは `.tf`という拡張子のファイルに書きます。今回はEC2インスタンスに関する記述をするので、ファイル名は`ec2.tf`としましょう。

```ec2.tf
provider "aws" {
  profile = "default"
  region = "ap-northeast-1"
}

resource "aws_instance" "terraform-web" {
  ami = "ami-0ab3e16f9c414dee7"
  instance_type = "t2.micro"
  tags = {
    Name = "ec2-terraform"
  }
}
```

とりあえずこれだけ。この状態で、terraformプロジェクトの初期設定を行います。

```shell:初期化
$ terraform init
```

`Terraform has been successfully initialized!` と出れば完了です。何かしらエラーが出た人は、 `前提`の欄に書いたAWS CLIのcredentials設定が終わっていないかもしれませんね。[AWS CLIの設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html)を見てキーの設定を行ってください。

#### 作成計画を確認し、実際にコードを実行する
さて、では次に作成される予定のEC2について、作成計画を見てみましょう。

```shell:作成計画を見る
$ terraform plan
```

なんかワーって出てきたかと思います。とりあえず次のステップ、実際にインスタンスを作成するところまでいってみましょう。

```shell:Terraformコードの実行
$ terraform apply
```

`Apply complete! Resources: 1 added, 0 changed, 0 destroyed.`のようになれば作成完了です。では、AWSのコンソールにいってEC2インスタンスのタブを見てみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/21ec72a4-90d7-9372-8578-87144f97113d.png)

おおっ！ちゃんとインスタンスが作成されていますね！このように、コード化した設定からEC2インスタンスを起動することができました！:tada: 

### 中級編
作成できたのはいいものの、やっていることがよく分かりませんね。もうちょっと深堀りしてみましょう。
ちなみに[TerraformのAWS関連公式ドキュメントはこちら](https://www.terraform.io/docs/providers/aws/index.html)です。公式をあたるのが一番早いです。

#### コマンド
まず、terraformのコマンドについて。今出てきた3種類は、それぞれ以下のようになっています。

|コマンド|内容|備考|
|---|---|---|
|init|Terraformプロジェクトの初期設定を行う|AWS CLIのcredentialsが設定済みである事が前提|
|plan|Terraformコードの実行計画を見る|いわゆるdry run|
|apply|Terraformコードの実行||

したがって、コードを書いたらplanで環境がどうなるかを確認し、OKそうならapplyで実行、という流れになります。

#### コード
次に、 `ec2.tf`の中を見てみましょう。1行1行にコメントを書いてみました。

```ec2.tf
provider "aws" {            # AWS以外にも、GCPやAzure、Herokuなどがここに入る
  profile = "default"       # キーを指定せず、AWS CLIに設定したdefaultのcredentials情報を用いる
  region = "ap-northeast-1" # Terraformのコードで環境を作成する対象のリージョン
}

resource "aws_instance" "terraform-web" { # EC2インスタンスに関する記述。コード内ではterraform-webという名前でアクセスできる
  ami = "ami-0ab3e16f9c414dee7"           # どのAMIをベースにインスタンスを作成するか、ID指定
  instance_type = "t2.micro"              # インスタンスタイプ指定
  tags = {
    Name = "ec2-terraform"                # タグに Name: ec2-terraform をセット
  }
}
```

もうお分かりですね。
providerで対象とするサービスを指定し、認証情報やリージョンを設定する。
そして、構築したい個々の内容をresourceに書いていく、という感じです。
今はEC2のみ設定していますが、RDSやS3、VPCだって同じように書けます。書いていくとだんだん分かるのですが、コード管理できるのはめちゃめちゃ楽ですよ！

### 実践編
それではみなさんよいクリスマスを！...とはならず。EC2がポッと1つたっただけでは面白くないですよね。最後に、EC2インスタンスにNginxをインストールしトップページを表示するところまでやって終わりにしましょう。

#### セキュリティグループの設定
プラウザからアクセスしてNginxのトップページを表示するには、80番ポートへのアクセスを受け付ける必要があります。
また、そもそもNginxをインストールするためにssh接続してインスタンスにアクセスしなければなりませんね。したがって、22番ポートも開けなければいけません。これらを設定していきましょう。

```ec2.tf
provider "aws" {
  profile = "default"
  region = "ap-northeast-1"
}

# セキュリティグループ
resource "aws_security_group" "terraform-web-sg" {
  egress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = [
      "0.0.0.0/0"
    ]
  }
  tags = {
    Name = "sg-terraform"
  }
}

# インバウンドのルール
resource "aws_security_group_rule" "inbound_http" {
  security_group_id = aws_security_group.terraform-web-sg.id
  type = "ingress"
  from_port = 80
  to_port = 80
  protocol = "tcp"
  cidr_blocks = [
    "0.0.0.0/0"
  ]
}
resource "aws_security_group_rule" "inbound_ssh" {
  security_group_id = aws_security_group.terraform-web-sg.id
  type = "ingress"
  from_port = 22
  to_port = 22
  protocol = "tcp"
  cidr_blocks = [
    "0.0.0.0/0"
  ]
}

# EC2
resource "aws_instance" "terraform-web" {
  ami = "ami-0ab3e16f9c414dee7"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.terraform-web-sg.id] # <= 追加
  tags = {
    Name = "ec2-terraform"
  }
}
```

まず、セキュリティグループの`egress`はアウトバウンド、`igress`はインバウンドのルールに関する記述です。セキュリティグループにルールを複数追加したい場合は、`aws_security_group_rule`で記述します。そしてEC2の記述には、セキュリティグループのidについての記述を追加します。

では`terraform apply`を叩いてコンソールを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/4698ba00-3d5e-2730-6870-c54169f7d8fc.png)

ちゃんと複数ルールが設定されたセキュリティグループが作成されています！:tada:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/0fad1640-2dc5-a167-c357-0bc021c454bc.png)

EC2インスタンスにも作成されたセキュリティグループが正しく割り当てられていますね！

#### Nginxのインストール
さぁ、ここまできたならあとはもう少し。Nginxのインストールを行いましょう。
ここで、ssh接続するために鍵の設定が必要になりますので、先にキーペア作成を行っておきます。本筋とずれるので詳細は省きます。やり方は[AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-key-pairs.html)をみてくださいね。

さて、鍵ファイルが出来ました。自分は作成したファイルを`~/.ssh/test_terraform.pem`に置いていますので、それをコードに記述していきます。長くなるので、以下はEC2に関する記述の部分だけを記しています。

```ec2.tf
# EC2
resource "aws_instance" "terraform-web" {
  ami = "ami-0ab3e16f9c414dee7"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.terraform-web-sg.id]
  key_name = "test_terraform" # <= 追加
  tags = {
    Name = "ec2-terraform"
  }
  provisioner "remote-exec" { # <= 追加
    connection {
      host = self.public_dns
      type = "ssh"
      user = "ec2-user"
      private_key = file("~/.ssh/test_terraform.pem")
    }
    inline = [
      "sudo yum -y update && sudo yum install -y nginx && sudo service nginx start"
    ]
  }
}
```

例のごとく、`Apply complete! Resources: 1 added, 0 changed, 1 destroyed.`と出たら完了です。

ではちゃんとNginxが動いているか確認しましょう！
まずはTerraformで作成した環境の内容を表示するコマンドを打ってみます。

```shell:内容表示
$ terraform show
```

ズラズラと出てきたかと思いますが、これが構築された環境の内容です。
EC2の記述の中に、`public_ip`というのがあると思います。これが、作成したEC2インスタンスに振られたパブリックIPです！
では、ブラウザでこのIPにアクセスしてみましょう！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/5317145b-f5e1-cd93-6df9-a119d21f2529.png)

おおお！！無事にNginxのトップページが表示されました！このようにして、コードで環境の設定からプロビジョニングまで行うことができちゃうんですね！ビバTerraform！！:tada:😍🤗🙌

まとめ
---
Terraformを使うことによって、今までクラウドコンソール上でポチポチやってた環境をコード化して管理することが出来るようになりました！:tada:(というか、CLI経由で作ってた時代の手順をわかりやすくコード化した、という風にも言えますねｗ)
いずれにせよコード化が出来たことにより、環境の変更差分等は全てGitで管理できるようになります。いつ誰が何をどのように変更したか見える化できるので、今まで以上に安心して環境構築 & 保守ができるようになるのではないでしょうか！
また、一度よくあるインフラ形態をコード化しておけば、それを他のアプリケーションでも使い回すことが可能になります。まさに爆速インフラ構築！
ということで、インフラをコードに落として安心、安全な開発体制を整えていきましょう！

*なお今回のサンプルコードは[GitHub](https://github.com/mass-min/first_terraform)にあげていますので、勉強用に是非お使いください！