Terraformでawsのec2インスタンスを立ち上げる

タイトルの通りなのですが、terraformを使ってec2インスタンスを1つ、最低限の設定のみをして立ち上げるということを行います。
terraformやawsを全く触ったことがない人向けの内容となっています。

#Terraform

https://www.terraform.io/intro/index.html
>Terraformは、インフラを安全かつ効率的に構築、変更、バージョン管理するためのツールです。Terraformは既存のサービスプロバイダーとカスタムの社内ソリューションを管理できます。

awsの他にもGoogle CloudやHerokuなど様々なプロバイダーに対応しています。
今回はterraformを使い、最低限のec2インスタンスの設定をコードで書き、terraformのコマンドで実際に立ちげてみたいと思います。

##Infrastructure as Code
インフラの構成を管理したり、プロビジョニングを自動化するためにコードで書きましょうという概念です。
バージョン管理ができたり、CIだったりとソフトウェア開発と同じ要領でインフラの構築も行えるようになります。
このような概念があるから、terraformのようにコードで設定を書いておくのが良いのだなとわかりました。

#まずはterraformをインストール

```shell
$ brew install terraform
```

MacOS Sierraの場合エラーが起こるかもしれません。(最初にbrew installをした時にエラーが起きました。)
一度homebrewをアンイストールして、再度インストールすれば解決します。

```shell
% ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
% ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

#IAMとAMI
ec2インスタンスを立ち上げる前に必要なところです。

##IAM
外部からawsへアクセスするにはIAM(Identity and Access Management)というサービスを使用しなければなりません。
aws環境ではそれぞれのサービスに例えばS3やRDSに外部からアクセスする権限を管理するのがそれぞれのサービスではなくてIAMというサービスになります。
http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/introduction.html
>AWS Identity and Access Management (IAM) は、ユーザーに対して AWS へのアクセスを安全に制御するための仕組みで、IAM の利用自体が課金対象になることはありません。IAM により、どのユーザーがお客様の AWS リソースを使用できるか（認証）、それらのユーザーがどのリソースをどのような方法で使用できるか（承認）を制御できます。

IAMのユーザーを作成してアクセスキーとシークレットキーを取得します。
コンソール右上のログインユーザーの名前 → セキュリティ認証情報
ここからユーザーを作成することでアクセスキーとシークレットキーを取得できます。

アクセスキーは再度確認が可能ですが、シークレットキーは確認することができません。
そのため安全な場所に保管しておくなどしておかないと、再度作り直す必要があります。

また、ユーザーには権限があります。権限のないユーザーを作成してしまうとterraformでコマンドを実行してもエラーになってしまいます。
そのため既存の管理ポリシーのコピーからAmazonEC2FullAccessを選択しておきます。

##AMI
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/AMIs.html
>Amazon マシンイメージ (AMI) は、クラウドの仮想サーバーであるインスタンスの起動に必要な情報を提供します。インスタンスを起動するときに AMI を指定します。

AMIは自分で作成をしなくても、amazonが提供しているpublicのイメージも存在します。
今回はEC2のコンソールからpublic imageのidを取得します。
ナビゲーションペイン(コンソールの左にあるサイドバーのようなもの)の中のイメージにあるAMIというところです。
ひとまず今回は"ami-f80e0596"を使用します。

#コードを書いていく
まず適当にterraform用のディレクトリを作成しましょう。
その中にec2.tf,terraform.tfvarsの2ファイルを作成します。
ファイルの構成に関しては色々なやり方があると思うのですが、terraformに関する理解が進んできたらどのようなファイル構成が良いのかを考えるのが良いと思います。

ec2.tf

```t
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "aws_region" {
    default = "ap-northeast-1"
}
variable "aws_zone" {
    default = "ap-northeast-1c"
}

provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region     = "${var.aws_region}"
}

resource "aws_instance" "web1" {
    ami           = "ami-f80e0596"
    instance_type = "t2.micro"
    monitoring    = true
    tags {
        Name = "web1"
    }
}
```

変数として使用しているaws_access_keyなどはterraform.tfvarsに記載していきます。
regionのap-northeast-1は東京です。
また、instance_typeによってかかる金額が変わってきます。１年間の無料プランにt2.microがあったので無料プランの方はt2.microにするのが良いと思います。
ただ、変更される可能性もありますので、現在の無料プランについてawsから確認をしてください。
https://aws.amazon.com/jp/free/

terraform.tfvars

```
aws_access_key = "ACCESS_KEY_HERE"
aws_secret_key = "SECRET_ACCESS_KEY_HERE"
```
terraform.tfvarsファイルに先ほど取得しアクセスキーとシークレットキーを記載して起きます。
terraformのコマンド実行の際に、こちらのファイルが読み込まれます。
こちらの設定を行っておかないと毎回下記のようにアクセスキーとシークレットキーをきかれてしまいます。(ec2.tfにベタ書きすれば出ませんが)

```
var.aws_access_key
  Enter a value:

var.aws_secret_key
  Enter a value: 

```

**アクセスキーとシークレットキーをGithubにあげてしまい、60万円ぐらい請求がきたという記事を読んだので取り扱いには注意してください。**

terraformをgithubにあげて管理する場合には`.gitignore`を作成しましょう。

##terraformで確認をしてみる
今回コマンドを5つ紹介します。

```
1. terraform init
初期化

2. terraform plan
dry run

3. terraform apply
AWS上にリソースを作成

4. terraform show
今の状態を表示。

5. terraform destroy
リソース一式を削除
```

### terraform init
最初に `terraform init` を実行しないとプラグインの最初期化が必要だぞというエラー出ます。

```
$ terraform plan
Plugin reinitialization required. Please run "terraform init".
Reason: Could not satisfy plugin requirements.

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can't be located,
don't satisfy the version constraints, or are otherwise incompatible.

```

ということでまずはinit

```
$ terraform init
```

###terraform plan
terrafrom planで構文があっているか、パラメータに誤りがないかなどを確認でき、問題がなければ実行計画が表示されます。
どんな設定で実行されるのかがわかるのと、planの最後に追加や変更、削除の個数が表示されるため、意図していない変更が行われていないかを確認できます。

+がついている箇所が新規の追加となります。今回だと`+ aws_instance.web1`の部分

また末尾に下記のように全体の追加や変更数が表示されますが、今回インスタンスを1つ立てようとしているだけなので、問題がなさそうだということがわかります。

```
Plan: 1 to add, 0 to change, 0 to destroy.
```

```
$ terraform plan

+ aws_instance.web1
    ami:                         "ami-f80e0596"
    associate_public_ip_address: "<computed>"
    availability_zone:           "<computed>"
    ebs_block_device.#:          "<computed>"
    ephemeral_block_device.#:    "<computed>"
    instance_state:              "<computed>"
    instance_type:               "t2.micro"
    key_name:                    "<computed>"
    monitoring:                  "true"
    network_interface_id:        "<computed>"
    placement_group:             "<computed>"
    private_dns:                 "<computed>"
    private_ip:                  "<computed>"
    public_dns:                  "<computed>"
    public_ip:                   "<computed>"
    root_block_device.#:         "<computed>"
    security_groups.#:           "<computed>"
    source_dest_check:           "true"
    subnet_id:                   "<computed>"
    tags.%:                      "1"
    tags.Name:                   "web1"
    tenancy:                     "<computed>"
    vpc_security_group_ids.#:    "<computed>"


Plan: 1 to add, 0 to change, 0 to destroy.
```

###terrafrom apply
これで実際に実行ができます。
Apply complete!と表示されたらAWSのコンソールを確認すると立ち上がっていると思います。
また、planと同じく最後に追加や変更、削除の個数が表示されるため、意図していない変更が行われていないか確認できます。

```
$ terraform apply
aws_instance.web1: Creating...
  ami:                         "" => "ami-f80e0596"
  associate_public_ip_address: "" => "<computed>"
  availability_zone:           "" => "<computed>"
  ebs_block_device.#:          "" => "<computed>"
  ephemeral_block_device.#:    "" => "<computed>"
  instance_state:              "" => "<computed>"
  instance_type:               "" => "t2.micro"
  key_name:                    "" => "<computed>"
  monitoring:                  "" => "true"
  network_interface_id:        "" => "<computed>"
  placement_group:             "" => "<computed>"
  private_dns:                 "" => "<computed>"
  private_ip:                  "" => "<computed>"
  public_dns:                  "" => "<computed>"
  public_ip:                   "" => "<computed>"
  root_block_device.#:         "" => "<computed>"
  security_groups.#:           "" => "<computed>"
  source_dest_check:           "" => "true"
  subnet_id:                   "" => "<computed>"
  tags.%:                      "" => "1"
  tags.Name:                   "" => "web1"
  tenancy:                     "" => "<computed>"
  vpc_security_group_ids.#:    "" => "<computed>"
aws_instance.web1: Still creating... (10s elapsed)
aws_instance.web1: Creation complete

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

ただし、注意点としてはplanでエラーが起こっていなかったとしても、applyを実行するとエラーが起こる場合があります。
例えばIAMで作成したユーザーの権限がなかった場合などがそうです。
planは実行をしないので権限がなくても、実行したらこうなるよって返してくれますが、いざapplyで実行をすると権限がないと下記のようにエラーが起こります。
その際には権限がIAMのユーザーの権限を確認してみてください。

```
$ terraform apply

Error applying plan:

1 error(s) occurred:

* aws_instance.web1: Error launching source instance: UnauthorizedOperation: You are not authorized to perform this operation. Encoded authorization failure message: ~
```

### terraform show
こちらのコマンドで現在の状態を表示することができます。


```
$ terraform show
aws_instance.web1:
  id = i-0aa958d0237y1ee90
  ami = ami-f80e0596
  associate_public_ip_address = true
  availability_zone = ap-northeast-1a
(長いので省略)
```


### terrafrom destroy
今回お試しで作っただけなので不必要であればこちらのコマンドで削除しておきましょう。
今回省略しましたが、`terraform plan -destroy`でリソース削除の実行計画を確認できます。

```
$ terraform destroy 
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.web1: Refreshing state... (ID: i-0aa958d0237y1ee90)
aws_instance.web1: Destroying...
aws_instance.web1: Still destroying... (10s elapsed)
aws_instance.web1: Still destroying... (20s elapsed)
aws_instance.web1: Still destroying... (30s elapsed)
aws_instance.web1: Still destroying... (40s elapsed)
aws_instance.web1: Still destroying... (50s elapsed)
aws_instance.web1: Destruction complete

Destroy complete! Resources: 1 destroyed.

```

# ここまで出来たら
ここまでterraformを触ってみて、次にやると良いなと思ったことをまとめておきます。
## ドキュメントを参考に他のリソースも作成する
terraformを使ってAWSを構築する方法は、公式のドキュメントに詳しく書いてあります。
https://www.terraform.io/docs/providers/aws/index.html

例えばvpcを作成しようと思ったら、[Data Source: aws_vpc](https://www.terraform.io/docs/providers/aws/d/vpc.html)のページにサンプルが載ってます。

```
resource "aws_vpc" "vpc-main" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags {
    Name = "vpc-main"
  }
}

resource "aws_subnet" "example" {
  vpc_id                  = "${aws_vpc.vpc-main.id}" // 作成したvpcはこのように指定できる
  cidr_block              = "10.0.0.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags {
    Name = "example"
  }
}
```

ディレクトリ構成で迷ったら、[Terraformにおけるディレクトリ構造のベストプラクティス](https://dev.classmethod.jp/devops/directory-layout-bestpractice-in-terraform/)などを参考に進めてみると良いと思います。

## terraforming
[terraforming](https://github.com/dtan4/terraforming)を使うと、すでにAWSを使ってる人向けですが、既存のインフラ環境をterraformの管理下に置けます。(リポジトリに使い方が載っています。)
こんな感じで今のインフラ環境がterraformの形式で出力されるため、書き方がわからない時も使えます。

```
$ terraforming ec2
resource "aws_instance" "sitename_web1" {
    ami                         = "ami-374db956"
    availability_zone           = "ap-northeast-1a"
    ebs_optimized               = false
    instance_type               = "t2.medium"
    monitoring                  = true
    key_name                    = "ec2_test_keypair"
    subnet_id                   = "subnet-9f18c0d6"
    vpc_security_group_ids      = ["sg-62486b04"]
    associate_public_ip_address = false
    private_ip                  = "10.0.0.66"
    source_dest_check           = true

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 8
        delete_on_termination = true
    }

    tags {
        "Name" = "sitename_web1"
    }
}
```


#まとめ
最低限だけですがterraformでec2のインスタンスを立ち上げることができました。(実際にはもっと設定を行わなければいけませんが)
初めてAWSに触る人はインスタンスを立ち上げるだけでも色々と知らないといけない概念が多いので、まずはほぼ初期設定でも良いから立ち上げてみるのが良いなと思いました。

また、できればいきなりterraformを触るよりもAWSの画面を触ってインスタンスを立ち上げたりしておくと、これを全部自動化させてくれるのかということがわかるのでオススメです。