Terraform入門その四（AWSのELB&ASGの導入）

[Terraform入門その一（インストール）](https://qiita.com/kenkono/items/86fb4544735d9cfad080)
[Terraform入門その二（AWSのEC2作成）](https://qiita.com/kenkono/items/a3a67585b1737a11c5c4)
[Terraform入門その三（hello worldの表示）](https://qiita.com/kenkono/items/2fb65e7326f957315362)
Terraform入門その四（AWSのELB&ASGの導入）**[本記事]**

前回EC2インスタンスにapcheの導入→helloworldをブラウザに表示させるところまで記述しました。

しかし、実際の現場ではインスタンス1台の稼働では冗長性や障害対応の観点では心もとないです。
そこで、AWSの`ELB`と`ASG`の追加をterraformに記述していきます。

# 最終的なディレクトリ構成図

```
working
├── main.tf
├── outputs.tf
└── vars.tf
```

# ASG(Auto Scaling Group)とは
登録したEC2を管理し、例えば何かしらの原因でインスタンスに障害が発生した場合に、自動的に追加のインスタンスを生成する等を自動してくれるものです。

# ASGの設定
`main.tf`に、下記を記述していきます。
前回記事までに記載していた`resource "aws_instance`を書き換える形になります。

```main.tf
resource "aws_launch_configuration" "example" {
    image_id = "ami-04b2d1589ab1d972c"
    instance_type = "t2.micro"
    security_groups = ["${aws_security_group.instance.id}"]

    user_data = <<-EOF
                #! /bin/bash
                sudo yum update
                sudo yum install -y httpd
                sudo chkconfig httpd on
                sudo service httpd start
                echo "<h1>hello world</h1>" | sudo tee /var/www/html/index.html
                EOF

    lifecycle {
        create_before_destroy = true
    }
}
```

`lifecycle`の記述ですが、`create_before_destroy = true`にしておくと、何かの変更を実行した際に、その全ての変更が完了した時点で既存のインスタンスが破棄される仕様です。
参照：https://www.terraform.io/docs/providers/aws/r/launch_configuration.html#using-with-autoscaling-groups

続いて、`aws_security_group`にも、`lifecycle`の記述を追加します。

```main.tf
resource "aws_security_group" "instance" {
    name = "terraform-example-instance"
    ingress {
        from_port = "${var.server_port}"
        to_port = "${var.server_port}"
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

ここまでの記述で、ようやくASG本体の設定ができます。
下記を記述します。

```main.tf
resource "aws_autoscaling_group" "example" {
    launch_configuration = "${aws_launch_configuration.example.id}"

    min_size = 2
    max_size = 10

    tag {
        key = "Name"
        value = "terraform-asg-example"
        propagate_at_launch = true
    }
}
```
ASGが2〜10台のEC2インスタンスを管理する設定です。立ち上がると最小単位の2台のインスタンスが立ち上がります。
`propagate_at_launch = true`は、インスタンス作成の際に記述したタグを付ける設定です。

### availability zones(AZ)の記述
下記記述で、region(今回の設定ですと`ap-northeast-1`)内のAZを拾ってきてくれます。
AZ = ap-northeast-1a, ap-northeast-1c, ap-northeast-1d

```
data "aws_availability_zones" "all" {}
```

これをASGの設定内容に組み込みます。

```main.tf
data "aws_availability_zones" "all" {}

resource "aws_autoscaling_group" "example" {
    launch_configuration = "${aws_launch_configuration.example.id}"
# 追加
    availability_zones = "${data.aws_availability_zones.all.names}"

    min_size = 2
    max_size = 10

    tag {
        key = "Name"
        value = "terraform-asg-example"
        propagate_at_launch = true
    }
}
```

# ELB(Load Balancer)の導入
ASGの設定により、複数インスタンスを管理できるようになりました。
しかし、ユーザ支店から考えた場合、一つのIPアドレスでアクセスできるようにしたいです。
そこで、`ELB`を導入し各インスタンスへの窓口としての役割を果たしてもらいます。

### security_groupの記述
ELBが外部からのアクセスを許可するための`security_group`を作成します。
インバウンドは80番ポートへのアクセスを許可、アウトバウンドは全許可を表します。

```main.tf
resource "aws_security_group" "elb" {
    name = "terraform-example-elb"

    ingress {
        from_port = 80
        to_port = 80
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

次にELB自身の設定です。
先程記述した、`security_group`を反映させています。
`listener`はどのポートからのアクセスを、どのインスタンス（厳密にはポート）へ転送するのかの記述です。

```main.tf
resource "aws_elb" "example" {
    name = "terraform-asg-example"
    availability_zones = "${data.aws_availability_zones.all.names}"
    security_groups = ["${aws_security_group.elb.id}"]

    listener {
        lb_port = 80
        lb_protocol = "http"
        instance_port = "${var.server_port}"
        instance_protocol = "http"
    }
}
```

### ヘルスチェックの追加
ELBの主要機能である、ヘルスチェック(health check)の記述を追加します。
30秒毎に`/`へアクセスしにいき(この場合、自動的に`index.html`へ向かうことになる）、2回連続でステータスコード200が帰ったこなかった場合unhealthと判定されます。

```main.tf
resource "aws_elb" "example" {
    name = "terraform-asg-example"
    availability_zones = "${data.aws_availability_zones.all.names}"
    security_groups = ["${aws_security_group.elb.id}"]

    listener {
        lb_port = 80
        lb_protocol = "http"
        instance_port = "${var.server_port}"
        instance_protocol = "http"
    }

# 追加
    health_check {
        healthy_threshold = 2
        unhealthy_threshold = 2
        timeout = 3
        interval = 30
        target = "HTTP:${var.server_port}/"
    }
}
```

最後に`ELB`の情報を`ASG`に紐づけます。
`health_check_type = "ELB"`と記述することで、ELBが`unhealth`と判断した場合、ASGが作動して新しいインスタンスを作成することになります。

```main.tf
resource "aws_autoscaling_group" "example" {
    launch_configuration = "${aws_launch_configuration.example.id}"
    availability_zones = "${data.aws_availability_zones.all.names}"

# 追加
    load_balancers = ["${aws_elb.example.name}"]
    health_check_type = "ELB"

    min_size = 2
    max_size = 10

    tag {
        key = "Name"
        value = "terraform-asg-example"
        propagate_at_launch = true
    }
}
```

# main.tfの全体図

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

resource "aws_launch_configuration" "example" {
    image_id = "ami-04b2d1589ab1d972c"
    instance_type = "t2.micro"
    security_groups = ["${aws_security_group.instance.id}"]

    user_data = <<-EOF
                #! /bin/bash
                sudo yum update
                sudo yum install -y httpd
                sudo chkconfig httpd on
                sudo service httpd start
                echo "<h1>hello world</h1>" | sudo tee /var/www/html/index.html
                EOF

    lifecycle {
        create_before_destroy = true
    }
}

data "aws_availability_zones" "all" {}

resource "aws_autoscaling_group" "example" {
    launch_configuration = "${aws_launch_configuration.example.id}"
    availability_zones = "${data.aws_availability_zones.all.names}"

    load_balancers = ["${aws_elb.example.name}"]
    health_check_type = "ELB"

    min_size = 2
    max_size = 10

    tag {
        key = "Name"
        value = "terraform-asg-example"
        propagate_at_launch = true
    }
}

resource "aws_security_group" "instance" {
    name = "terraform-example-instance"
    ingress {
        from_port = "${var.server_port}"
        to_port = "${var.server_port}"
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  lifecycle {
      create_before_destroy = true
  }
}

resource "aws_elb" "example" {
    name = "terraform-asg-example"
    availability_zones = "${data.aws_availability_zones.all.names}"
    security_groups = ["${aws_security_group.elb.id}"]

    listener {
        lb_port = 80
        lb_protocol = "http"
        instance_port = "${var.server_port}"
        instance_protocol = "http"
    }

    health_check {
        healthy_threshold = 2
        unhealthy_threshold = 2
        timeout = 3
        interval = 30
        target = "HTTP:${var.server_port}/"
    }
}

resource "aws_security_group" "elb" {
    name = "terraform-example-elb"

    ingress {
        from_port = 80
        to_port = 80
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

# outputの作成

現在の状態でメインは完成したので、`terraform apply`コマンドを実行すれば動作します。
しかし、ブラウザへ確認しにいくときにELBのドメイン名をマネジメントコンソール画面で確認するのは面倒です。
そこで、`outputs.tf`を作成し、作成完了後にアクセスすべきドメイン名をターミナルに出力するようにします。

```outputs.tf
output "elb_dns_name" {
  value = "${aws_elb.example.dns_name}"
}
```

# terraform applyの実行
`terraform apply`をターミナルで実行しますと、最終的に以下のようなドメイン名が帰ってくるかと思います。

```
elb_dns_name = terraform-asg-example-73024.ap-northeast-1.elb.amazonaws.com
```
こちらにアクセスしますと`hello world`が表示されます。
また、AWSマネジメントコンソールにアクセスしますと`EC2` `ELB` `ASG`が作成されていることも確認できます。

ためしに、2つあるEC2インスタンスのうちの一つ(`terraform-asg-example`)を削除してみてください。
ASGが作動し、自動的にインスタンスを複製してくれます（多少時間がかかります、少なくとも5分後には新しいものができているはず）。

# リソースの削除
今回terraformで作成したリソースは下記コマンドで簡単に削除することができます。

```
mbp:terraform mac$ terraform destroy
```

# 参照
[Resource: aws_autoscaling_group](https://www.terraform.io/docs/providers/aws/r/autoscaling_group.html)
[Resource: aws_elb](https://www.terraform.io/docs/providers/aws/r/elb.html)