Terraform EP2 - Hand-On!
Tags: terrform, devops, iac, aws
Published: 2020-03-20 09:27:54

> สรุปมาจาก [An Introduction to Terraform](https://blog.gruntwork.io/an-introduction-to-terraform-f17df9c6d180) และเสริมด้วยข้อมูลจาก [Official Doc](https://www.terraform.io/docs/configuration/index.html)

จาก EP ที่แล้ว เราน่าจะพอเข้าใจ Terraform แล้วว่ามันคืออะไร คราวนี้เรามาลงมือเล่นกันเถอะ โดยเรามาลอง ทำ cluster ของ web server พร้อมด้วย load balancer บน AWS ด้วย Terraform กัน 

ถ้าใครไม่เคยใช้ทั้ง Terraform และ AWS ทำใจร่มๆ ไว้ได้เลย เพราะผมก็ไม่เคย เรามาเรียนรู้มันไปพร้อมๆ กัน

<u>**Topics**</u>
- [Install Terraform](#Install-Terraform)
- [Terraform Configuration Syntax](#Terraform-Configuration-Syntax)
- [Set up AWS account](#Set-up-AWS-account)
- [Deploy a single server](#Deploy-a-single-server)
- [Deploy a single web server](#Deploy-a-single-web-server)
- [Play around with variables](#Play-around-with-variables)
- [Deploy a cluster of web servers](#Deploy-a-cluster-of-web-servers)
- [Deploy a load balancer](#Deploy-a-load-balancer)

<a name="Install-Terraform"></a>

---

# Install Terraform

การ install `terraform` นั้นทำได้ง่ายมาก โดยแค่ไป download binary จาก [Official Website](https://www.terraform.io/downloads.html) ตาม system และ CPU Architecture ของเรา แล้ว run ได้เลย ซึ่งสามารถดู [tutorial](https://learn.hashicorp.com/terraform/getting-started/install.html) นี้ได้เลย

เนื่องจากผมทำบน Ubuntu ดังนั้น ผมจะแสดงแค่ Ubuntu นะครับ

``` bash
$ sudo -i
$ wget https://releases.hashicorp.com/terraform/0.12.23/terraform_0.12.23_linux_amd64.zip
$ unzip terraform_0.12.23_linux_amd64.zip
$ mv terraform /usr/local/sbin/
$ terraform 
Usage: terraform [-version] [-help] <command> [args]

The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you''re just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.

Common commands:
    apply              Builds or changes infrastructure
    console            Interactive console for Terraform interpolations
    destroy            Destroy Terraform-managed infrastructure
    env                Workspace management
    fmt                Rewrites config files to canonical format
    get                Download and install modules for the configuration
    graph              Create a visual graph of Terraform resources
    import             Import existing infrastructure into Terraform
    init               Initialize a Terraform working directory
    login              Obtain and save credentials for a remote host
    logout             Remove locally-stored credentials for a remote host
    output             Read an output from a state file
    plan               Generate and show an execution plan
    providers          Prints a tree of the providers used in the configuration
    refresh            Update local state file against real resources
    show               Inspect Terraform state or plan
    taint              Manually mark a resource for recreation
    untaint            Manually unmark a resource as tainted
    validate           Validates the Terraform files
    version            Prints the Terraform version
    workspace          Workspace management

All other commands:
    0.12upgrade        Rewrites pre-0.12 module source code for v0.12
    debug              Debug output management (experimental)
    force-unlock       Manually unlock the terraform state
    push               Obsolete command for Terraform Enterprise legacy (v1)
    state              Advanced state management
```

<a name="Terraform-Configuration-Syntax"></a>

---

# Terraform Configuration Syntax

Terraform ใช้ HCL syntax ในการ configure ซึ่งสามารถอ่านแบบเต็มๆ ได้จาก [Github ของ HCL](https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md) ในที่นี้ผมจะเกริ่นคร่าวๆ ถึง key syntax หลักๆ เพื่อความเข้าใจซักเล็กน้อย ดังนี้

- **Arguments**: เป็นการ assign ค่าให้กับตัวแปร ใน HCL document มักจะเรียกว่า **"attribute"** แต่ใน Terraform ใช้คำว่า attribute กับ resource อื่นแล้ว เลยเลี่ยงมาใช้คำว่า "argument" แทน
    ``` hcl
    image_id = "abc123"
    ```
- **Blocks**: เป็น container ที่เก็บ content อื่นๆ เพื่อใช้ในการกำหนด specification ของ resource ที่เราต้องการ create โดย โครงสร้างจะเป็น `<type> <label#1> <label#2> ... <label#N> { ... }` และ block สามารถซ้อนเป็น nested block ได้
    ``` hcl
    resource "aws_instance" "example" {
        ami = "abc123"

        network_interface {
            # ...
        }
    } 
    ```
- **Identifiers**: คือชื่อของสิ่งต่างๆ ใน configuration เช่น Argument names, block type names, resources และ input variables เป็นต้น สามารถใช้ letters, digits, underscores (_) และ hyphens (-) เป็น identifier ได้ แต่ไม่ควรใช้ digits ขึ้นต้น identifier เพราะจะทำให้สับสนกับเลขจริงๆ
- **Comments**: สามารถ
    - ใช้ `#` (default) ในการ comment แต่ละบรรทัด
    - ใช้ `//` ในการ comment แต่ละบรรทัด 
    - ใช้ `/* ... */` ในการ comment หลาย บรรทัด
- **Character Encoding and Line Endings**: configuration ของ Terraform เป็น UTF-8 แต่ delimiter ยังต้องเป็น ASCII อยู่ ตัวแบ่งบรรทัดสามารถเป็นได้ทั้ง Unix-style (LF only) และ Windows-style (CR then LF) แต่ที่ถูกต้องต้องเป็น Unix-style
- **Variables**: ชนิดของตัวแปรหลักๆ มีดังนี้
    - **string**: ตัวหนังสือ
    - **number**: ตัวเลข
    - **bool**: true หรือ false
    - **list(type)**: เป็น list โดยใน list ต้องเป็น type เดียวกัน และมีลำดับ
        ``` hcl
        [0,5,2,4,5,4]
        ```
    - **set(type)**: เหมือน list แต่ element ต้องไม่ซ้ำกัน และ ไม่มี order เพราะ มันจะ sort จากน้อยไปมากโดยอัตโนมัติ
        ``` hcl
        [0,2,4,5]
        ```
    - **tuple([<type>, ...])** เหมือน list แต่ element ต่าง type กันได้
        ``` hcl
        [0, 'string', false]
        ```
    - **map(type)**: ใช้เก็บ key-value pair 
        ``` hcl
        { "key" = "value" }
        ```
    - **object({\<attr_name\>=\<type\>, ...})**: ใช้เก็บ key-value pair แต่ต่าง type กันได้
        ``` hcl
        {
            firstname = 'john'
            housenumber = 10
        }
        ```

เรามาลองเล่นกันตัวแปรกันดีกว่า โดยเราใช้ใช้ command `terraform console` เพื่อ interact กับ configuration file ของเราดู

``` bash
$ mkdir -p terraform-variables && cd terraform-variables
$ cat > main.tf << EOF
variable "myvar" {
  type = string
  default = "hello terraform"
}
 
variable "mymap" {
  type = map(string)
  default = {
    mykey = "my value"
  }
}
 
variable "mylist" {
  type = list
  default = [1,2,3]
}
EOF
$ terraform console
> var.myvar                 # Access variable แบบที่ 1
hello terraform
> "${var.myvar}"            # Access variable แบบที่ 2
hello terraform
> var.mymap
{
  "mykey" = "my value"
}
> var.mymap["mykey"]        # Access variable ที่เป็น Map โดยระบุ key ที่ต้องการเข้าถึง
my value
> var.mylist
[
  1,
  2,
  3,
]
> var.mylist[0]             # Access variable แบบ list index ที่ 0
1
> var.mylist[1]             # Access variable แบบ list index ที่ 1
2
> var.mylist[2]             # Access variable แบบ list index ที่ 2
3
> element(var.mylist, 0)    # Access variable แบบ list index ที่ 0 โดยใช้ function "element"
1
> element(var.mylist, 1)    # Access variable แบบ list index ที่ 1 โดยใช้ function "element"
2
> element(var.mylist, 2)    # Access variable แบบ list index ที่ 2 โดยใช้ function "element"
3
> slice(var.mylist, 0, 2)   # Slice list เพื่อดึงเฉพาะค่าที่ต้องการ
[
  1,
  2,
]
> exit
```

<a name="Set-up-AWS-account"></a>

---

# Set up AWS account

1. ถ้าใครยังไม่มี account เราสามารถสมัครใช้บริการ [Free Tier](https://aws.amazon.com/free/) ได้ 1 ปี แต่ต้องผู้บัตร credit ด้วย อย่าไปกลัว 1 ปีนี้เราไม่เสียตังค์แน่นอน 😎
2. ทำการ login ด้วย email ของเรา โดยเลือกเป็น Root user
  ![signin](https://dev-to-uploads.s3.amazonaws.com/i/bu8xx26snw754qx67tfm.png)
3. ทำการสร้าง IAM user ชื่อ "terraform" และ ให้สิทธิ์ "AmazonEC2FullAccess" เพื่อให้ Terraform เข้าไปสั่งงานสร้าง EC2 (เป็นชื่อเรียก VM ของ AWS) ผ่าน API ของ AWS
  - กดที่ **"Services"** ตรงมุมซ้ายบน
  - พิมพ์ **"IAM"** ในช่อง search แล้วกดที่ **"IAM"**
  - ในหน้า **"IAM"**
      - กด **"Users"** ตรง menu ทางซ้าย
      - กดปุ่ม **"Add user"** สีน้ำเงิน
  - ในหน้า **"Add user"**
      - ช่อง **"User name"** ใส่ **"terraform"**
      - ช่อง **"Access type"** เลือก **"Programmatic access"**
      - กดปุ่ม **"Next: Permissions"**
      - เลือก **"Add user to group"**
      - กดปุ่ม **"Create group"** จะมี Popup "Create group" ปรากฏขึ้นมา
          - ช่อง **"Group name"** ใส่ **"terraform-administrator"**
          - เลือก Policy **"AmazonEC2FullAccess"**
          - กดปุ่ม **"Create group"** สีน้ำเงิน
      - เมื่อกลับมาหน้า "Add user" กดปุ่ม **"Next: Tags"** สีน้ำเงิน
      - กดปุ่ม **"Next: Review"** สีน้ำเงิน
      - กดปุ่ม **"Create user"** สีน้ำเงิน
      - ถ้า <mark>✔ "Success"</mark> กดปุ่ม **"Download .csv"** เพื่อเก็บ Access key ID และ Secret access key เอาไว้ใช้กับ Terraform ต่อไป
      - กดปุ่ม **"Close"**
    - จะได้ user ดังนี้
      ![IAM-User](https://dev-to-uploads.s3.amazonaws.com/i/gqmoobcbu6u5hn9zuu41.png)

<a name="Deploy-a-single-server"></a>

---

# Deploy a single server

> ถ้าคุณใช้ editor ต่างๆ เช่น vim, emacs, Sublime Text, Atom, Visual Studio Code หรือ IntelliJ ลอง search หา "HCL" ดูจะช่วย highlight syntax ให้เราดูง่ายขึ้น

1. ทำการ set credential ของ AWS ให้กับ shell ของคุณก่อน โดย เอาข้อมูลมาจาก CSV file ที่เรา download มาก่อนหน้านี้

    ``` bash
    $ export AWS_ACCESS_KEY_ID=(your access key id)
    $ export AWS_SECRET_ACCESS_KEY=(your secret access key)
    ```

2. สร้าง Terraform configuration file  เพื่อสร้าง EC2 1 server

    > ในการสร้าง EC2 เราต้องทำการระบุ image ที่จะใช้สร้าง servers ด้วย AMI-ID ซึ่งเราสามารถ หา AMI-ID ที่เราต้องการได้จาก <br/> 
    > 1. เข้า [EC2 console](https://console.aws.amazon.com/ec2/) <br/>
    > 2. เลือก Region ที่เราต้องการสร้าง EC2 <br/>
    > 3. กดที่ **AMIs** แล้วเลือก **Public Image** แล้วหา AMI-ID ที่ต้องการ
    > ![AMI-EC2](https://dev-to-uploads.s3.amazonaws.com/i/t2etwk8e02gpcg9vqnwj.png)

    ``` bash
    $ cd ~ && mkdir -p tf_01 && cd tf_01
    $ cat > main.tf << EOF
    provider "aws" {
        region = "us-east-2"
    }

    resource "aws_instance" "example" {
        ami           = "ami-0c55b159cbfafe1f0"
        instance_type = "t2.micro"
    }
    EOF
    ```

    <u>**อธิบาย**</u>
    - **provider**: เป็นการระบุ cloud provider ที่เราต้องการ นั่นคือ **aws**
    - **region**: เป็น region ของ AWS ที่เราต้องการจะสร้าง EC2
    - **resource**: ใช้ระบุ resource ที่ต้องการสร้าง ในที่นี้จะสร้าง **aws_instance** (EC2) โดยให้มีชื่อว่า **example**
    - **ami**: ระบุ ami-id ของ image ที่เราจะใช้สร้าง server สามารถหาได้จาก [aws marketplace](https://aws.amazon.com/marketplace/)
    - **instance_type**: กำหนด size ของ server ที่เราต้องการสร้าง สามารถดู type ทั้งหมดได้จาก [Amazon EC2 Instance Types](https://aws.amazon.com/ec2/instance-types/)

3. ทำการสร้าง EC2 ด้วย Terraform

    ``` bash
    1. ทำการ init terraform เพื่อ download library ที่จำเป็นต้องใช้ทั้งหมด

    $ terraform init

    Initializing the backend...

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

    (...)

    * provider.aws: version = "~> 2.53"

    Terraform has been successfully initialized!

    (...)
    
    2. library จะอยู่ใน .terraform

    $ tree -a .terraform/
    .terraform/
    └── plugins
        └── linux_amd64
            ├── lock.json
            └── terraform-provider-aws_v2.53.0_x4
   
    3. ตรวจสอบว่า จาก configuration file ของเรา Terraform จะทำอะไรบ้าง
       +   คือ สร้างเพิ่ม
       -   คือ ลบออก
       ~   คือ มีการแก้ไข
       -/+ คือ destroy and then create replacement

    $ terraform plan

    (...)

    Terraform will perform the following actions:

    # aws_instance.example will be created
    + resource "aws_instance" "example" {
        + ami                          = "ami-0c55b159cbfafe1f0"
        + arn                          = (known after apply)
        + associate_public_ip_address  = (known after apply)
        + availability_zone            = (known after apply)
        + cpu_core_count               = (known after apply)
        + cpu_threads_per_core         = (known after apply)
        + get_password_data            = false
        + host_id                      = (known after apply)
        + id                           = (known after apply)
        + instance_state               = (known after apply)
        + instance_type                = "t2.micro"
        + ipv6_address_count           = (known after apply)
        + ipv6_addresses               = (known after apply)
        + key_name                     = (known after apply)
        + network_interface_id         = (known after apply)
        + password_data                = (known after apply)
        + placement_group              = (known after apply)
        + primary_network_interface_id = (known after apply)
        + private_dns                  = (known after apply)
        + private_ip                   = (known after apply)
        + public_dns                   = (known after apply)
        + public_ip                    = (known after apply)
        + security_groups              = (known after apply)
        + source_dest_check            = true
        + subnet_id                    = (known after apply)
        + tenancy                      = (known after apply)
        + volume_tags                  = (known after apply)
        + vpc_security_group_ids       = (known after apply)

    (...)

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

    (...)

    4. ทำการสร้าง EC2 server โดยมันจะให้เรา review อีกครั้ง ถ้าเราตอบ yes มันจึงจำเริ่มสร้าง

    $ terraform apply

    (...)

    Terraform will perform the following actions:

    # aws_instance.example will be created
    + resource "aws_instance" "example" {
        + ami                          = "ami-0c55b159cbfafe1f0"
        + arn                          = (known after apply)
        + associate_public_ip_address  = (known after apply)
        + availability_zone            = (known after apply)
        + cpu_core_count               = (known after apply)
        + cpu_threads_per_core         = (known after apply)
        + get_password_data            = false
        + host_id                      = (known after apply)
        + id                           = (known after apply)
        + instance_state               = (known after apply)
        + instance_type                = "t2.micro"
        + ipv6_address_count           = (known after apply)
        + ipv6_addresses               = (known after apply)
        + key_name                     = (known after apply)
        + network_interface_id         = (known after apply)
        + password_data                = (known after apply)
        + placement_group              = (known after apply)
        + primary_network_interface_id = (known after apply)
        + private_dns                  = (known after apply)
        + private_ip                   = (known after apply)
        + public_dns                   = (known after apply)
        + public_ip                    = (known after apply)
        + security_groups              = (known after apply)
        + source_dest_check            = true
        + subnet_id                    = (known after apply)
        + tenancy                      = (known after apply)
        + volume_tags                  = (known after apply)
        + vpc_security_group_ids       = (known after apply)

    (...)

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

    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

    aws_instance.example: Creating...
    aws_instance.example: Still creating... [10s elapsed]
    aws_instance.example: Still creating... [20s elapsed]
    aws_instance.example: Still creating... [30s elapsed]
    aws_instance.example: Creation complete after 35s [id=i-0610f6754ebbc6824]

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

4. เข้าไปดูผลงานของเรากัน

    ![EC2-1-Server](https://dev-to-uploads.s3.amazonaws.com/i/dzkz00b27mhnokg8brro.png)

5. ทำการเพิ่ม [tag](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html) ให้กับ EC2 ที่เราเพิ่มสร้างไป

    ``` bash
    1. เพิ่ม Tag ลงไปใน block resooure {...} ดัง เครื่องหมาย +

    $ vi main.tf
        | provider "aws" {
        |     region = "us-east-2"
        | }
        | 
        | resource "aws_instance" "example" {
        |     ami           = "ami-0c55b159cbfafe1f0"
        |     instance_type = "t2.micro"
       +|     tags = {
       +|         Name = "terraform-example"
       +|     }
        | }

    2. ทำการ apply configuration ใหม่เพื่อเพิ่ม tag

    $ terraform apply

    (...)

    Terraform will perform the following actions:

    # aws_instance.example will be updated in-place
    ~ resource "aws_instance" "example" {
            ami                          = "ami-0c55b159cbfafe1f0"
            arn                          = "arn:aws:ec2:us-east-2:139383842991:instance/i-0610f6754ebbc6824"
            associate_public_ip_address  = true
            availability_zone            = "us-east-2b"
            cpu_core_count               = 1
            cpu_threads_per_core         = 1
            disable_api_termination      = false
            ebs_optimized                = false
            get_password_data            = false
            hibernation                  = false
            id                           = "i-0610f6754ebbc6824"
            instance_state               = "running"
            instance_type                = "t2.micro"
            ipv6_address_count           = 0
            ipv6_addresses               = []
            monitoring                   = false
            primary_network_interface_id = "eni-01e4dd5120846158a"
            private_dns                  = "ip-172-31-27-212.us-east-2.compute.internal"
            private_ip                   = "172.31.27.212"
            public_dns                   = "ec2-18-218-190-100.us-east-2.compute.amazonaws.com"
            public_ip                    = "18.218.190.100"
            security_groups              = [
                "default",
            ]
            source_dest_check            = true
            subnet_id                    = "subnet-e0a68f9a"
        ~ tags                         = {
            + "Name" = "terraform-example"
            }

    (...)

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

    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

    aws_instance.example: Modifying... [id=i-0610f6754ebbc6824]
    aws_instance.example: Still modifying... [id=i-0610f6754ebbc6824, 10s elapsed]
    aws_instance.example: Modifications complete after 12s [id=i-0610f6754ebbc6824]

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

6. เข้าไปดูผลงาน จะเห็นว่า EC2 เรามีชื่อแล้ว

    ![EC2-with-name](https://dev-to-uploads.s3.amazonaws.com/i/kzhvuj9smn2xu2fs4rlb.png)

<a name="Deploy-a-single-web-server"></a>

---

# Deploy a single web server

ในหัวข้อนี้เราจะมาทำให้ EC2 ของเราเป็น web server ง่ายๆ บน port 8080 ซึ่งตอบแค่ "Hello, World" ด้วย script ดังนี้ 

``` bash
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
```

วิธีที่ควรทำคือ ทำ custome image ด้วย Packer แล้วสร้าง EC2 ใหม่จาก image นั้น แต่ใน case นี้เราจะทำง่ายๆ ด้วยการสั่งให้ image run script ผ่านทาง [User Data](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) ซึ่งจะถูก run ตอน boot เครื่องขึ้นมา

และเพื่อให้เราเข้าถึงหน้า web จากภายนอกได้ ต้องเพิ่ม [security group](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html) เพื่อ allow traffic จากภายนอกผ่านทาง port 8080 ด้วย

ในการ apply security group ให้ EC2 เราต้องใส่ ID ของ security group ไปใน attribute ของ EC2 ด้วย แต่เนื่องจากเรายังไม่รู้ ID ของ security group เราสามารถ reference ค่าได้ โดยใช้ format ดังนี้

``` hcl
Format: [<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>]
Result: [aws_security_group.instance.id]
```

การทำ reference นี้ จะเป็นการสร้าง **"implicit dependency"** เป็นผลให้เวลาที่ Terraform ทำงานมันจะไปสร้าง **security group** ก่อน เพื่อให้ได้ ID แล้วจึงนำมา apply ให้ EC2 ใช้งาน

> เวลาที่ Terraform parse dependency tree มันจะพยายามสร้าง plan ในการทำงานให้ parallel มากที่สุดเท่าที่จะทำได้ เพื่อให้สร้างได้รวดเร็วที่สุด 

ลงมือทำกันเลย 🖐

1. เราจะแก้ไข main.tf อีกครั้ง ด้วยการเพิ่มบรรทัดที่มีเครื่องหมาย + ดังนี้ 

    ``` bash
    $ vi main.tf
        | provider "aws" {
        |     region = "us-east-2"
        | }
        |  
        | resource "aws_instance" "example" {
        |     ami           = "ami-0c55b159cbfafe1f0"
        |     instance_type = "t2.micro"
       +|     vpc_security_group_ids = [aws_security_group.instance.id]
       +|     user_data = <<-EOF
       +|                 #!/bin/bash
       +|                 echo "Hello, World" > index.html
       +|                 nohup busybox httpd -f -p 8080 &
       +|                 EOF
        |     tags = {
        |         Name = "terraform-example"
        |     }
        | }
       +| 
       +| resource "aws_security_group" "instance" {
       +|     name = "terraform-example-instance"
       +|     ingress {
       +|         from_port   = 8080
       +|         to_port     = 8080
       +|         protocol    = "tcp"
       +|         cidr_blocks = ["0.0.0.0/0"]
       +|     }
       +| }
    ```

2. ทำการ apply configuration ใหม่ที่เราเพิ่งทำไป

    ``` bash
    $ terraform apply

    (...)

    Terraform will perform the following actions:

    # aws_instance.example must be replaced
    -/+ resource "aws_instance" "example" {
            ami                          = "ami-0c55b159cbfafe1f0"
            (...)
            instance_type                = "t2.micro"
            (...)
        + user_data                    = "c765373c563b260626d113c4a56a46e8a8c5ca33" # forces replacement
        (...)
        ~ vpc_security_group_ids       = [
            - "sg-594b143f",
            ] -> (known after apply)
        (...)
        }

    # aws_security_group.instance will be created
    + resource "aws_security_group" "instance" {
        + arn                    = (known after apply)
        + description            = "Managed by Terraform"
        + egress                 = (known after apply)
        + id                     = (known after apply)
        + ingress                = [
            + {
                + cidr_blocks      = [
                    + "0.0.0.0/0",
                    ]
                + description      = ""
                + from_port        = 8080
                + ipv6_cidr_blocks = []
                + prefix_list_ids  = []
                + protocol         = "tcp"
                + security_groups  = []
                + self             = false
                + to_port          = 8080
                },
            ]
        + name                   = "terraform-example-instance"
        (...)
        }

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

    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

    aws_instance.example: Destroying... [id=i-0610f6754ebbc6824]
    aws_security_group.instance: Creating...
    aws_security_group.instance: Creation complete after 9s [id=sg-0d7d9b50531db13c5]
    aws_instance.example: Still destroying... [id=i-0610f6754ebbc6824, 10s elapsed]
    aws_instance.example: Still destroying... [id=i-0610f6754ebbc6824, 20s elapsed]
    aws_instance.example: Destruction complete after 24s
    aws_instance.example: Creating...
    aws_instance.example: Still creating... [10s elapsed]
    aws_instance.example: Still creating... [20s elapsed]
    aws_instance.example: Creation complete after 30s [id=i-0db21cb267f702ce7]

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

3. ไปดูผลงานของเรากัน จะเห็นว่า EC2 อันเก่าถูก terminate ไป แล้ว และมี EC2 ใหม่ขึ้นมาแทน นั้นเป็นเพราะ Terraform ทำงาน แบบ Immutable ดังนั้น การเปลี่ยนแปลงภายใน EC2 เช่น การ assign "User Data" นั้น จะต้องสร้าง EC2 ใหม่ เท่านั้น

    ![EC2-Web](https://dev-to-uploads.s3.amazonaws.com/i/p5fve6j5nddxjh0pszbp.png)

4. ในส่วนของ Security Group ก็มีการ create และ allow port 8080 เรียบร้อย

    ![SG-8080](https://dev-to-uploads.s3.amazonaws.com/i/ti7c40gcwcjz1y7xjtgg.png)

5. ลองเข้า web โดยใช้ public IP ของ EC2 ของเราก็จะได้ "Hello, World" กลับมา

    ![Hello-World](https://dev-to-uploads.s3.amazonaws.com/i/v19u5u3igrphs7q4seuj.png)

<a name="Play-around-with-variables"></a>

---

# Play around with variables

ก่อนไปสร้าง cluster ยังจำ [Don't Repeat Yourself (DRY) Principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) กันได้รึเปล่า ถ้าจำได้ จะเห็นว่าเรามีการ define port 8080 ซ้ำกัน 2 รอบคือ ที่ security group และ user data

เรามาทำให้มันไม่ซ้ำกันก่อนดีกว่า ด้วย **[input variable](https://www.terraform.io/docs/configuration/variables.html)**

## Input Variables

syntax ของ Input variable เป็นดังนี้ 

<u>**Syntax**</u>

``` hcl
variable "NAME" {
    [CONFIG ...]
}
```

<u>**Example**</u>

``` hcl
variable "server_port" {
  description = "The port the server will use for HTTP requests"
  type        = number
  default     = 8080
}
```

ในส่วนของ [CONFIG ...] จะประกอบด้วย 3 parameters คือ
- **description**: เป็นคำอธิบายตัวแปรนี้ ว่าใช้ทำอะไร ค่านี้จะถูกแสดงตอน `plan` และ `apply` ด้วย
- **default**: เป็นการกำหนดค่า default value ให้กับตัวแปรดังกล่าว โดย เราสามารถ pass ค่าของตัวแปร ตอน `apply` ได้ด้วย
    - การใช้ `-var` option แล้ว pass ค่าของตัวแปรเข้ามา
        ``` bash
        $ terraform apply -var "server_port=8080"
        ```
    - การใช้ `-var-file` option แล้ว pass file ที่เก็บค่าของตัวแปรเข้าไป
        ``` bash
        $ terraform apply -var-file="testing.tfvars"
        ```
    - การใช้ Environment Variable ใน format TF_VAR_\<variable_name\>
        ``` bash
        $ export TF_VAR_server_port=8080
        $ terraform apply
        ```
    - ถ้าเราไม่ได้กำหนด default value และ ไม่ได้ pass ค่าเข้าไป Terraform จะถามเราทางหน้า Terminal
        ``` bash
        $ terraform apply
        var.server_port
            The port the server will use for HTTP requests
            Enter a value:
        ```
- **type**: เป็นการกำหนด type ของตัวแปร สามารถเป็นได้ทั้ง string, number, bool, list, map, set, object, tuple หรือ any (ถ้าไม่ใส่ default เป็น any)

ในการใช้งานเราสามารถเข้าถึงด้วย `var.<variable_name>` ซึ่งทำให้เรานำไปแทนค่าใน main.tf ได้ดังนี้ 

- ในส่วน Security Group
    ``` hcl
    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"]
        }
    }
    ```
- ในส่วนของ User Data นั้นเป็น string ดังนั้นเวลาที่จะไปแทนคนต้องใช้ expression `"${...}"` ซึ่งเรียกว่า **interpolation**
    ``` hcl
        user_data = <<-EOF
                    #!/bin/bash
                    echo "Hello, World" > index.html
                    nohup busybox httpd -f -p "${var.server_port}" &
                    EOF
    ```

ดังนั้น main.tf ใหม่จะเป็นแบบนี้

``` hcl
provider "aws" {
    region = "us-east-2"
}

variable "server_port" {
  description = "The port the server will use for HTTP requests"
  type        = number
  default     = 8080
}
 
resource "aws_instance" "example" {
    ami           = "ami-0c55b159cbfafe1f0"
    instance_type = "t2.micro"
    vpc_security_group_ids = [aws_security_group.instance.id]
    user_data = <<-EOF
                #!/bin/bash
                echo "Hello, World" > index.html
                nohup busybox httpd -f -p "${var.server_port}" &
                EOF
    tags = {
        Name = "terraform-example"
    }
}

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"]
    }
}
```

## Output Variable

เป็นการดึงค่าต่างๆ ที่ได้จากการทำงานมาใส่ในตัวแปร เพื่อให้เรานำไปใช้งานต่อไป โดยเราสามารถเลือกให้แสดงผลหลังจากที่ทำการ `terraform apply` เรียบร้อย หรือ เก็บไว้ใช้แต่ตอนเรียกใช้ด้วย command `terraform output` ก็ได้ 

<u>**Syntax**</u>

``` hcl
output "<NAME>" {
    value = <VALUE>
    [CONFIG ...]
}
```

<u>**Example**</u>

``` hcl
output "public_ip" {
    value       = aws_instance.example.public_ip
    description = "The public IP of the web server"
}

output "private_ip" {
    value       = aws_instance.example.private_ip
    description = "The private IP of the web server"
    sensitive   = true
}
```

ในส่วนของ [CONFIG ...] มีได้ 2 parameters คือ

- **description**: เป็นคำอธิบายตัวแปรนี้ ว่าใช้เก็บค่าอะไร
- **sensitive**: ถ้าเป็น true คือ sensitive จะไม่แสดง ค่าของ variable หลังจาก `terraform apply` เสร็จ

ถ้าหากเรา run `terraform apply` ซ้ำเป็นครั้งที่ 2 terraform จะไปทำอะไรนอกจากแสดงค่า output ออกมา 

``` bash
$ terraform apply
aws_security_group.instance: Refreshing state... [id=sg-0d7d9b50531db13c5]
aws_instance.example: Refreshing state... [id=i-0db21cb267f702ce7]

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

Outputs:

private_ip = <sensitive>
public_ip = 18.191.93.62
```

เพื่อป้องกันการ apply โดยบังเอิญ และเพื่อให้สะดวกต่อการนำไปเขียน script เพื่อทำงานต่อไป สามารถใช้ `terraform output` เพื่อดึงเฉพาะ output ออกมาแสดงโดยไม่ `apply`

``` bash
$ terraform output
private_ip = <sensitive>
public_ip = 18.191.93.62

$ terraform output private_ip
172.31.45.142

$ terraform output public_ip
18.191.93.62
```

ทำลายสิ่งที่เราเพิ่งสร้างไป เตรียมตัวทำ cluster กัน

``` bash
$ terraform destroy

(...)

Terraform will perform the following actions:

  # aws_instance.example will be destroyed
  - resource "aws_instance" "example" {
      (...)

  # aws_security_group.instance will be destroyed
  - resource "aws_security_group" "instance" {
      (...)

Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.example: Destroying... [id=i-0db21cb267f702ce7]
aws_instance.example: Still destroying... [id=i-0db21cb267f702ce7, 10s elapsed]
aws_instance.example: Still destroying... [id=i-0db21cb267f702ce7, 20s elapsed]
aws_instance.example: Still destroying... [id=i-0db21cb267f702ce7, 30s elapsed]
aws_instance.example: Destruction complete after 40s
aws_security_group.instance: Destroying... [id=sg-0d7d9b50531db13c5]
aws_security_group.instance: Destruction complete after 2s

Destroy complete! Resources: 2 destroyed.
```

<a name="Deploy-a-cluster-of-web-servers"></a>

---

# Deploy a cluster of web servers

ถ้าเราจะทำ web ทั้งทีแล้วมีแค่ server เดียวคงจะเป็นเรื่องที่เสี่ยงไม่น้อย เพราะถ้าหากมันล่มขึ้นมาเป็นอันว่าจบกัน ดังนั้นเพื่อให้สมจริง เรามาสร้าง cluster ของ web server กัน แม้จะเป็น web hello world ก็เถอะ

ในการทำ cluster ใน AWS นั้น จะใช้ [Auto Scaling Group (ASG)](https://aws.amazon.com/autoscaling/) ในการ launch EC2 servers เพราะนอกจากจะ launch ได้หลาย servers ในครั้งเดียวแล้ว มันยัง ช่วย monitor health ของ servers และช่วย restart server ที่ fail หรือ unhealthy ให้ด้วย และที่ขาดไม่ได้ มันยังช่วยปรับขนาดของ cluster ตาม load (autoscaling) ด้วย 

แต่ก่อนจะสร้าง ASG เราต้องสร้าง [Launch Configuration](https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchConfiguration.html) ก่อน โดย Launch Configuration ทำหน้าที่เป็น template ที่ระบุ specification ในการ launch EC2 ของ ASG

จากห้วข้อก่อนหน้า เราสามารถนำข้อมูลมาเขียน specification ของ Launch Configuration ได้ดังนี้ 

``` hcl
resource "aws_launch_configuration" "example" {
  image_id        = "ami-0c55b159cbfafe1f0"
  instance_type   = "t2.micro"
  security_groups = [aws_security_group.instance.id]
  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, World" > index.html
              nohup busybox httpd -f -p "${var.server_port}" &
              EOF
  lifecycle {
    create_before_destroy = true
  }
}
```

จาก configure ข้างบน มีของใหม่แค่ 1 อันคือ [lifecycle settings](https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations) ซึ่งเป็นตัวที่กำหนดวิธีในการ create และ destroy resource โดย default คือ ทำลายของเก่าก่อนแล้วค่อยสร้างใหม่ แต่การ set ให้ `create_before_destroy = true` คือ สร้างให้เสร็จก่อนแล้วค่อยทำลายของเก่า

จากนั้น เรามาดู specification ของ ASG ที่เราจะให้ Terraform create ให้ เป็นดังนี้

``` hcl
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
  }
}
```

จาก specification ข้างบน ใน cluster นี้จะมี EC2 อยู่ 2 - 10 servers ขึ้นอยู่กับ traffic โดยให้ server run แยกในทุกๆ Available Zones (AZs) มากที่สุดเท่าที่จะทำได้ เพื่อที่ว่าถ้ามี AZ ไหน fail ไปจะได้ไม่กระทบ service ของเรา 

โดย AZs คือ data center ของ AWS ที่แยกขาดออกจากกันทั้งทั้งน้ำ ไฟ และ ระบบทำความเย็น แต่อยู่ใน region เดียวกัน

แล้ว ทำไม `data.aws_availability_zones.all.names` หมายถึง ทุก AZ ล่ะ ?

data คือ data source เป็นการดึงข้อมูลมาจาก provider (ในที่นี้คือ AWS) ในทุกครั้งที่ terraform ถูก run ตัวอย่าง data ที่สามารถดึงได้จาก providers ก็เช่น [VPC data](https://www.terraform.io/docs/providers/aws/d/vpc.html), [subnet data](https://www.terraform.io/docs/providers/aws/d/subnet_ids.html), [AMI IDs](https://www.terraform.io/docs/providers/aws/d/ami.html), [IP address ranges](https://www.terraform.io/docs/providers/aws/d/ip_ranges.html), [current user’s identity](https://www.terraform.io/docs/providers/aws/d/caller_identity.html) และ อื่นๆ อีกมากมาย

ในการใช้งานเราต้อง define มันก่อนดังนี้ 

<u>**Format**</u>

``` hcl
data "<PROVIDER>_<TYPE>" "<NAME>" {
    [CONFIG ...]
}
```

<u>**Example**</u>

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

โดย [CONFIG ...] ของ aws_availability_zones สามารถดูได้จาก [doc](https://www.terraform.io/docs/providers/aws/d/availability_zones.html)

ส่วนการเข้าถึงจะเป็น format ดังนี้

``` hcl
Format: data.<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>
Result: data.aws_availability_zones.all.names
```

ดังนั้น เราสามารถสร้าง cluster ของ EC2 ด้วย ASG ดังนี้ 

1. สร้าง configuration file ใหม่ เพื่อสร้าง cluster

    ``` bash
    $ cd ~ && mkdir -p tf_02 && cd tf_02
    $ cat > custer.tf << EOF
    provider "aws" {
        region = "us-east-2"
    }

    variable "server_port" {
    description = "The port the server will use for HTTP requests"
    type        = number
    default     = 8080
    }

    resource "aws_launch_configuration" "example" {
    image_id        = "ami-0c55b159cbfafe1f0"
    instance_type   = "t2.micro"
    security_groups = [aws_security_group.instance.id]
    user_data = <<-EOF
                #!/bin/bash
                echo "Hello, World" > index.html
                nohup busybox httpd -f -p "\${var.server_port}" &
                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
    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"]
        }
    }
    EOF
    ```

2. ทำการสร้าง cluster 

    ``` bash
    $ terraform init
    $ terraform apply

    (...)

    Terraform will perform the following actions:

    # aws_autoscaling_group.example will be created
    + resource "aws_autoscaling_group" "example" {
        + arn                       = (known after apply)
        + availability_zones        = [
            + "us-east-2a",
            + "us-east-2b",
            + "us-east-2c",
            ]
        (...)
        }

    # aws_launch_configuration.example will be created
    + resource "aws_launch_configuration" "example" {
        + arn                         = (known after apply)
        + associate_public_ip_address = false
        + ebs_optimized               = (known after apply)
        + enable_monitoring           = true
        + id                          = (known after apply)
        + image_id                    = "ami-0c55b159cbfafe1f0"
        + instance_type               = "t2.micro"
        + key_name                    = (known after apply)
        + name                        = (known after apply)
        + security_groups             = (known after apply)
        + user_data                   = "4430fd6498339061effa6d27ccf341a1e94569d7"

        (...)
        }

    # aws_security_group.instance will be created
    + resource "aws_security_group" "instance" {
        + arn                    = (known after apply)
        + description            = "Managed by Terraform"
        + egress                 = (known after apply)
        + id                     = (known after apply)
        + ingress                = [
            + {
                + cidr_blocks      = [
                    + "0.0.0.0/0",
                    ]
                + description      = ""
                + from_port        = 8080
                + ipv6_cidr_blocks = []
                + prefix_list_ids  = []
                + protocol         = "tcp"
                + security_groups  = []
                + self             = false
                + to_port          = 8080
                },
            ]
        (...)
        }

    Plan: 3 to add, 0 to change, 0 to destroy.

    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

    aws_security_group.instance: Creating...
    aws_security_group.instance: Creation complete after 9s [id=sg-022d3b1e15e652188]
    aws_launch_configuration.example: Creating...
    aws_launch_configuration.example: Creation complete after 5s [id=terraform-20200319075721931300000001]
    aws_autoscaling_group.example: Creating...
    aws_autoscaling_group.example: Still creating... [10s elapsed]
    aws_autoscaling_group.example: Still creating... [20s elapsed]
    aws_autoscaling_group.example: Still creating... [30s elapsed]
    aws_autoscaling_group.example: Still creating... [40s elapsed]
    aws_autoscaling_group.example: Still creating... [50s elapsed]
    aws_autoscaling_group.example: Still creating... [1m0s elapsed]
    aws_autoscaling_group.example: Still creating... [1m10s elapsed]
    aws_autoscaling_group.example: Still creating... [1m20s elapsed]
    aws_autoscaling_group.example: Still creating... [1m30s elapsed]
    aws_autoscaling_group.example: Still creating... [1m40s elapsed]
    aws_autoscaling_group.example: Still creating... [1m50s elapsed]
    aws_autoscaling_group.example: Still creating... [2m0s elapsed]
    aws_autoscaling_group.example: Still creating... [2m10s elapsed]
    aws_autoscaling_group.example: Still creating... [2m20s elapsed]
    aws_autoscaling_group.example: Still creating... [2m30s elapsed]
    aws_autoscaling_group.example: Still creating... [2m40s elapsed]
    aws_autoscaling_group.example: Still creating... [2m50s elapsed]
    aws_autoscaling_group.example: Creation complete after 2m54s [id=tf-asg-20200319075725933800000002]

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

3. ตรวจสอบ EC2 ที่เรา create ไป โดยกดที่ Instance

    ![EC2-ASG](https://dev-to-uploads.s3.amazonaws.com/i/8eb68in2055fgcg1g2lg.png)

4. ตรวจสอบ Launch Configuration ที่เรา create ไป โดยกดที่ "Launch Configurations"

    ![EC2-LC](https://dev-to-uploads.s3.amazonaws.com/i/ihhl6mh1mvh44724ni4k.png)

5. ตรวจสอบ Launch Configuration ที่เรา create ไป โดยกดที่ "Auto Scaling Groups"

    ![EC2-ASG](https://dev-to-uploads.s3.amazonaws.com/i/wbf4cqu9xyi05mrc4tba.png)

เรียบร้อย! ต่อไป เรามาทำตัวที่ช่วย share load ไปยังทุก server ใน ASG ของเรา ที่ support การ auto scaling ของ ASG ด้วย กันต่อดีกว่า

<a name="Deploy-a-load-balancer"></a>

---

# Deploy a load balancer

ตอนนี้เรามีหลาย server เพื่อทำหน้าที่เป็น web server ละ ต่อมาสิ่งที่เราต้องทำเพิ่มคือ load balancer เพื่อช่วยแจก traffic ไปยังทุก servers ใน cluster ของเรา โดย ผู้ใช้งานจะได้ IP ของ load balancer ผ่านการ solve domain จาก DNS

เช่นเดิม เราจะใช้ *Amazon’s Elastic Load Balancer (ELB) service* ของ AWS ซึ่ง มี Auto Scaling และ สามารถทำการ failover ได้ โดย default โดย ELB มี 3 แบบ คือ

- **Application Load Balancer (ALB):** เหมาะสำหรับ HTTP และ HTTPS traffic
- **Network Load Balancer (NLB):** เหมาะสำหรับ TCP and UDP traffic
- **Classic Load Balancer (CLB):** เป็นแบบ legacy ใช้ได้ทั้ง HTTP, HTTPS และ TCP แต่มี feature น้อยกว่า ALB และ NLB

ในตัวอย่างนี้จะใช้ CLB เพราะ configure ง่ายกว่า โดย สามารถดูรายละเอียดเพิ่มเติมของ ELB ได้จาก [doc](https://www.terraform.io/docs/providers/aws/r/elb.html)

โดย configuration ของ ELB และ security group เป็นดังนี้ 

``` hcl
variable "elb_port" {
    description = "The port the server will use for HTTP load balancer"
    type        = number
    default     = 80
}

resource "aws_elb" "example" {
    name               = "terraform-asg-example"
    security_groups    = [aws_security_group.elb.id]
    availability_zones = data.aws_availability_zones.all.names
    # This add a health checking for ASG
    health_check {
        target              = "HTTP:${var.server_port}/"
        interval            = 30
        timeout             = 3
        healthy_threshold   = 2
        unhealthy_threshold = 2
    }
    # This adds a listener for incoming HTTP requests.
    listener {
        lb_port           = var.elb_port
        lb_protocol       = "http"
        instance_port     = var.server_port
        instance_protocol = "http"
    }
}

resource "aws_security_group" "elb" {
    name = "terraform-example-elb"
    # Allow all outbound
    egress {
        from_port   = 0
        to_port     = 0
        protocol    = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    # Inbound HTTP from anywhere
    ingress {
        from_port   = 80
        to_port     = 80
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}
```

จาก configure 
- define ตัวแปร elb_port = 80
- define CLB โดยให้ listen port 80 (default) แล้ว share load ไปยัง port 8080 ของ instance ใน ASG
- CLB ของเรามีการ health check backend ด้วย ถ้าตัวไหน fail จะได้ไม่ส่ง traffic ไป
- เช่นเดียวกับ EC2 เราต้อง configure "security group" ให้ allow traffic ขาเข้าจาก port 80 ของ load balancer และ allow ขาออกทั้งหมด 

เมื่อเราได้ CLB แล้ว เราต้องเพิ่ม configuration ของ ASG ให้ register ตัวเองไปยัง CLB เพื่อรับ traffic ด้วย ดังนี้

``` hcl
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
    
    # Register ASG ไปยัง CLB 
    load_balancers    = [aws_elb.example.name]
    health_check_type = "ELB"
    
    tag {
        key                 = "Name"
        value               = "terraform-asg-example"
        propagate_at_launch = true
    }
}
```

และเพื่อให้ได้ domain name ของ CLB เราต้อง เพิ่ม output ดังนี้ 

``` hcl
output "clb_dns_name" {
    value       = aws_elb.example.dns_name
    description = "The domain name of the load balancer"
}
```

ดังนั้น เรามาสร้าง CLB ให้ ASG ของเรากัน ดังนี้ 

1. สร้าง configuration ใหม่ของ cluster.tf โดยเพิ่ม CLB, Security Group, ASG และ DNS output

    ``` bash
    $ cat > cluster.tf << EOF
    provider "aws" {
        region = "us-east-2"
    }

    variable "server_port" {
        description = "The port the server will use for HTTP requests"
        type        = number
        default     = 8080
    }

    variable "elb_port" {
        description = "The port the server will use for HTTP load balancer"
        type        = number
        default     = 80
    }

    resource "aws_launch_configuration" "example" {
        image_id        = "ami-0c55b159cbfafe1f0"
        instance_type   = "t2.micro"
        security_groups = [aws_security_group.instance.id]
        user_data = <<-EOF
                #!/bin/bash
                echo "Hello, World" > index.html
                nohup busybox httpd -f -p "\${var.server_port}" &
                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
        min_size = 2
        max_size = 10
        load_balancers    = [aws_elb.example.name]
        health_check_type = "ELB"
        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"]
        }
    }

    resource "aws_elb" "example" {
        name               = "terraform-asg-example"
        security_groups    = [aws_security_group.elb.id]
        availability_zones = data.aws_availability_zones.all.names
        # This add a health checking for ASG
        health_check {
            target              = "HTTP:\${var.server_port}/"
            interval            = 30
            timeout             = 3
            healthy_threshold   = 2
            unhealthy_threshold = 2
        }
        # This adds a listener for incoming HTTP requests.
        listener {
            lb_port           = var.elb_port
            lb_protocol       = "http"
            instance_port     = var.server_port
            instance_protocol = "http"
        }
    }

    resource "aws_security_group" "elb" {
        name = "terraform-example-elb"
        # Allow all outbound
        egress {
            from_port   = 0
            to_port     = 0
            protocol    = "-1"
            cidr_blocks = ["0.0.0.0/0"]
        }
        # Inbound HTTP from anywhere
        ingress {
            from_port   = 80
            to_port     = 80
            protocol    = "tcp"
            cidr_blocks = ["0.0.0.0/0"]
        }
    }

    output "clb_dns_name" {
        value       = aws_elb.example.dns_name
        description = "The domain name of the load balancer"
    }
    EOF
    ```

2. ทำการ apply configuration

    ``` bash
    $ terraform apply

    (...)

    Terraform will perform the following actions:

    # aws_autoscaling_group.example will be updated in-place
    ~ resource "aws_autoscaling_group" "example" {
        (...)
        ~ health_check_type         = "EC2" -> "ELB"
            id                        = "tf-asg-20200319075725933800000002"
            launch_configuration      = "terraform-20200319075721931300000001"
        ~ load_balancers            = [
            + "terraform-asg-example",
            ]
            (...)

    # aws_elb.example will be created
    + resource "aws_elb" "example" {
        (...)
        + name                        = "terraform-asg-example"
        (...)

        + health_check {
            + healthy_threshold   = 2
            + interval            = 30
            + target              = "HTTP:8080/"
            + timeout             = 3
            + unhealthy_threshold = 2
            }

        + listener {
            + instance_port     = 8080
            + instance_protocol = "http"
            + lb_port           = 80
            + lb_protocol       = "http"
            }
        }

    # aws_security_group.elb will be created
    + resource "aws_security_group" "elb" {
        + arn                    = (known after apply)
        + description            = "Managed by Terraform"
        + egress                 = [
            + {
                + cidr_blocks      = [
                    + "0.0.0.0/0",
                    ]
                + description      = ""
                + from_port        = 0
                + ipv6_cidr_blocks = []
                + prefix_list_ids  = []
                + protocol         = "-1"
                + security_groups  = []
                + self             = false
                + to_port          = 0
                },
            ]
        + id                     = (known after apply)
        + ingress                = [
            + {
                + cidr_blocks      = [
                    + "0.0.0.0/0",
                    ]
                + description      = ""
                + from_port        = 80
                + ipv6_cidr_blocks = []
                + prefix_list_ids  = []
                + protocol         = "tcp"
                + security_groups  = []
                + self             = false
                + to_port          = 80
                },
            ]
        (...)

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

    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

    aws_security_group.elb: Creating...
    aws_security_group.elb: Still creating... [10s elapsed]
    aws_security_group.elb: Creation complete after 11s [id=sg-0aa521374a7f5a184]
    aws_elb.example: Creating...
    aws_elb.example: Still creating... [11s elapsed]
    aws_elb.example: Creation complete after 19s [id=terraform-asg-example]
    aws_autoscaling_group.example: Modifying... [id=tf-asg-20200319075725933800000002]
    aws_autoscaling_group.example: Still modifying... [id=tf-asg-20200319075725933800000002, 10s elapsed]
    aws_autoscaling_group.example: Modifications complete after 11s [id=tf-asg-20200319075725933800000002]

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

    Outputs:

    clb_dns_name = terraform-asg-example-1486800117.us-east-2.elb.amazonaws.com
    ```

3. ตรวจสอบ Load Balancers ที่เราเพิ่ม create ไป โดยกดที่ "Load Balancers"

    ![EC2-LB](https://dev-to-uploads.s3.amazonaws.com/i/a94i65eaiflzjljwnz68.png)

4. ตรวจสอบ Security Group ที่เราเพิ่ม create ไป โดยกดที่ "Security Groups"

    ![EC2-ELB](https://dev-to-uploads.s3.amazonaws.com/i/0zrs71eckxagsbkpldpq.png)

5. ลองเข้า web ตาม domain ที่เราได้มา

    ![EC2-Domain](https://dev-to-uploads.s3.amazonaws.com/i/h82umnd9mwfupe32131a.png)

6. ทำลายทุกอย่างให้หมด

``` bash
$ terraform destroy

(...)

Terraform will perform the following actions:

  # aws_autoscaling_group.example will be destroyed
  - resource "aws_autoscaling_group" "example" {
      (...)

  # aws_elb.example will be destroyed
  - resource "aws_elb" "example" {
      (...)

  # aws_launch_configuration.example will be destroyed
  - resource "aws_launch_configuration" "example" {
      (...)

  # aws_security_group.elb will be destroyed
  - resource "aws_security_group" "elb" {
      (...)

  # aws_security_group.instance will be destroyed
  - resource "aws_security_group" "instance" {
      (...)

Plan: 0 to add, 0 to change, 5 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_autoscaling_group.example: Destroying... [id=tf-asg-20200319075725933800000002]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 10s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 20s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 30s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 40s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 50s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 1m0s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 1m10s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 1m20s elapsed]
aws_autoscaling_group.example: Still destroying... [id=tf-asg-20200319075725933800000002, 1m30s elapsed]
#aws_autoscaling_group.example: Destruction complete after 1m39s
aws_elb.example: Destroying... [id=terraform-asg-example]
aws_launch_configuration.example: Destroying... [id=terraform-20200319075721931300000001]
aws_launch_configuration.example: Destruction complete after 1s
aws_security_group.instance: Destroying... [id=sg-022d3b1e15e652188]
aws_security_group.instance: Destruction complete after 3s
aws_elb.example: Destruction complete after 5s
aws_security_group.elb: Destroying... [id=sg-0aa521374a7f5a184]
aws_security_group.elb: Still destroying... [id=sg-0aa521374a7f5a184, 10s elapsed]
aws_security_group.elb: Destruction complete after 16s

Destroy complete! Resources: 5 destroyed.
```

จบ แย้วววววว.... ยาวหน่อยนะ แต่ถ้าลองทำตามสนุกแน่นอน 🤯🤯🤯