เริ่มต้นรู้จักกับ Terraform

Terraform Logo

ในบทความนี้เราก็จะมาทำการรู้จักกับเครื่องมือยอดนิยมตัวนึงของ DevOps ที่ชื่อว่า Terraform กันนะครับ Terraform ถูกพัฒนาขึ้นโดยบริษัท HashiCorp ซึ่งเครื่องมือนี้เป็นส่วนนึงของการบริหารจัดการ Infrastructure as a Code หรือที่เรียกย่อๆว่า IaC

Infrastructure as a Code (IaC) คืออะไร

ตามชื่อเลยครับ IaC คือการบริหารจัดการระบบโครงสร้างของ software ด้วยการเขียนโค้ด ก่อนที่จะมี IaC เราอาจจะต้องทำทุกอย่าง manual ยกตัวอย่างเช่นหากเราต้องการสร้าง web server สักตัวบน AWS สิ่งที่เราต้องทำคือ login เข้าไปยัง AWS console เลือกอิมเมจพิมพ์ฺและ click เพื่อ set ค่าต่างๆ สร้าง security group เพื่อเปิดพอร์ต ฯลฯ

กระบวนการเหล่านี้ซับซ้อนและไม่สามารถทำซ้ำได้ เช่นถ้าเราต้องการ web server อีกตัวก็ต้องทำ process เดิมซ้ำๆซึ่งเสียเวลา นี่แค่ตัวอย่างของ web server ง่ายๆนะครับ ถ้าเป็นระบบที่ซับซ้อนกว่านี้จะทำให้เสียเวลาไปอีกเท่าไหร่

IaC จึงถูกออกแบบมาเพื่อแก้ปัญหานี้ครับ การเขียน infrastructure เพื่อใส่ใน code จะช่วยลดระยะเวลาของ process ซ้ำๆนี้ได้ อีกทั้งยังเป็น document ที่ทำให้คนมาอ่านเข้าใจโครงสร้างได้ง่าย และเมื่อเป็น code เราก็จะสามารถใช้ประโยชน์จาก source control, versioning และอื่นๆอีกมากมายครับ

จะเริ่มต้นใช้ Terraform อย่างไรดี

จากที่ได้กล่าวไปแล้ว Terraform เป็นส่วน provision ของ Infrastructure as a Code สิ่งที่ terraform จะทำคืออ่าน code ของเราเพื่อสร้างทรัพยากรณ์ของระบบที่เราออกแบบขึ้นมาครับ ตัว Terraform เองมีโครงสร้าง syntax ไม่กี่อย่าง ส่วนที่เหลือจะขึ้นอยู่กับ provider นั้นๆ (เช่น AWS, GCP, Azure) ว่ามีโครงสร้างของทรัพยากรณ์อย่างไร

ดังนั้น คุณไม่จำเป็นต้องจำรายละเอียดของวิธีสร้าง resource ว่าประกอบด้วยอะไรบ้าง ให้จำแค่ syntax หลักของ terraform ก็พอ ที่เหลือให้ reference กับ provider นั้นๆดูครับ

ตัวอย่าง syntax หลักๆของ Terraform

  • provider เป็นตัวกำหนดว่าเราจะใช้ provider เจ้าไหน
  • resource เป็นการกำหนด resource ย่อยของ provider นั้นๆว่าต้องการให้มีอะไรบ้าง
  • variable เป็นการกำหนดค่าที่ปรับเปลี่ยนได้ในการ deploy infrastructure ตัวอย่างเช่น เราอยากใช้ code เดียวกันในการ deploy web server สอง environment แต่ละ environment เรากำหนดให้ค่า CPU ที่ต่างกัน เราสามารถกำหนดข้อมูลของ CPU ให้เป็น variable ได้ครับ
  • data ในบางกรณีเราอาจต้องการ refer ถึงข้อมูลที่อยู่กับ provider แล้ว (เช่น ค่า default ต่างๆ) เราสามารถใช้ส่วนของ data เพื่อทำการดึงข้อมูลนั้นๆมาได้

ลองเขียน terraform เพื่อ deploy Nginx ไปยัง Docker

เพื่อให้เข้าใจการทำงานของ Terraform ให้มากขึ้น เรามาลองใช้งานจริงด้วยการ Deploy container ไปยัง Docker ของเราดีกว่าครับ

ในบทเรียนนี้ผมจะใช้ Docker provider เป็นตัวอย่างนะครับเนื่องจากไม่ต้องเสียเวลา register account กับ Cloud provider เนื่องจากทุกอย่างจะอยู่บนเครื่องของเรา ก่อนอื่นให้ตรวจสอบ version ของ terraform เราก่อน ต้องเป็น version 0.13 ขึ้นไปนะครับ

1
terraform --version
Terraform Version

กำหนด terraform provider

เริ่มแรกเลย เราจะกำหนด terraform provider นะครับ แต่ก่อนอื่นสร้าง directory ที่เราจะใช้เป็น playground ก่อน

1
mkdir terraform-demo && terraform-demo

provider ที่เราใช้จะเป็น Docker Provider ให้สร้างไฟล์ชื่อ main.tf และใส่ script นี้ลงไปครับ

1
# main.tf
2
terraform {
3
required_providers {
4
docker = {
5
source = "kreuzwerker/docker"
6
version = "2.16.0"
7
}
8
}
9
}
10
11
provider "docker" {
12
host = "unix:///var/run/docker.sock" # ใช้ docker daemon ที่รันอยู่บนเครื่องของเรา
13
}

จากโค้ดของบน บล้อคของ terraform (บรรทัด 2-9) จะเป็นการบอก terraform ว่าเราจะใช้ provider อะไรบ้าง ในทีนี้เราจะใช้แค่ docker provider นะครับ
ส่วนบรรทัด 11-13 จะเป็น provider บล็อคซึ่งใน บล้อคนี้จะเป็นการเซทค่าของ docker ตาม provider ที่เรา declare ข้างบน ในที่นี้จะเป็นการบอกว่าเราจะอ้างอิงถึง docker daemon ที่อยู่บนเครื่องของเราเองครับ

ถึงตอนนี้ให้รันคำสั่ง ข้างล่างครับ

1
terraform init
Terraform Init

คำสั่ง terraform init จะเป็นการ download libraries ที่จำเป็นลงมาไว้บนเครื่องเรา ในที่นี้ Terraform จะทำการดาวโหลด docker provider ที่เรา declare ไว้ใน code ข้างบนและนำไปเก็บไว้ใน folder .terraform.

กำหนด Nginx image และ รัน container

ในไฟล์ main.tf เดิมให้เพิ่ม script เข้าไปดังนี้ครับ

1
# main.tf
2
# Create a container
3
resource "docker_container" "my_tf_container" {
4
image = docker_image.my_nginx_image.latest
5
name = "my_terraform_nginx_container"
6
ports {
7
internal = 80
8
external = 8080
9
}
10
}
11
12
# Pulls the image
13
resource "docker_image" "my_nginx_image" {
14
name = "nginx:latest"
15
}

จากโค้ดข้างบนจะเห็นได้ว่าเราใช้ resource block ซึ่งมี syntax ดังนี้
resource "<provider>_<resource>" "<resource_name>" {...}

  • <provider> คือ docker ตามที่เราได้ declare ไว้ในช่วงต้นลองไฟล์
  • <resource> คือ image และ container ตามที่ได้อธิบายไว้ใน docker provider document
  • <resource_name> คือชื่อที่เราตั้งขึ้นมาเพื่อใช้อ้างอิงภายในโค้ดของเราเอง จากตัวอย่างของเรา จะเห็นได้ว่า container resource จะอ้างอิงถึง image resource ด้วย <resource_name>
  • {...} ค่าต่างๆภายในปีกกานี้จะแตกต่างกันไปขึ้นอยู่กับ resource และ provider ค่าเหล่านี้เราจะต้อง refer ไปยัง document ของ provider เท่านั้น

นอกจากนี้จะสังเกตได้ว่า container จะถูก declare ขึ้นมาก่อน image ทั้งๆที่ refer ถึง image ซึ่งตัวอย่างนี้แสดงให้เห็นว่าลำดับก่อนหลังในการเขียน code ไม่สำคัญ เนื่องจาก terraform จะสามารถตัดสินใจได้เองว่าควรจะทำการสร้าง resource ไหนก่อนครับ

Terraform plan

คำสั่ง terraform plan จะเป็นการตรวจสอบล่วงหน้าว่าหลังจาก deploy infrastructure ด้วยโค้ดชุดนี้แล้วจะเกิดอะไรขึ้นบ้าง แต่ยังไม่ใช่การ deploy จริงๆ ในขั้นตอนนี้จะช่วยให้เราตรวจสอบว่า resources ในระบบจริงจะถูกเปลี่ยนแปลงอะไรบ้างหลังที่เราทำการ deploy ไปแล้ว ลองรันคำสั่งข้างล่างเลยครับ

1
terraform plan

output ที่เราได้จะเป็นประมาณ screenshot ข้างล่างครับ

Terraform Plan

Terraform apply

หลังจากที่เราตรวจสอบ plan และแน่ใจแล้วว่าสิ่งที่เราต้องการ deploy นั้นถูกต้อง ให้รันคำสั่งข้างล่างได้เลยครับ

1
terraform apply

ระบบจะถามยืนยันว่าเราต้องการ deploy การเปลี่ยนแปลง infrastructure ตาม plan หรือเปล่า ให้เราพิมพ์ yes และ enter ผลลัพธ์ที่ได้ก็จะประมาณนี้

Terraform Apply

เท่านี้ก็เป็นอันเรียบร้อย Nginx ได้ถูก deploy ขึ้น server เรียบร้อย ลองรัน docker เพื่อ check process status และ image ได้เลยครับ

1
docker images && docker ps
Terraform Apply

จะเห็นได้ว่าเรามี Nginx image อยู่ใน local repository และมี container ที่ชื่อ my_terraform_nginx_container ตามที่เราได้โค้ดไว้

นอกจากนี้ถ้าไปยัง url http://localhost:8080 เราก็จะได้เห็น Nginx welcome page ครับ

Nginx welcome page

Terraform state file

หลังจากที่ได้ทำการ apply แล้วจะสังเกตเห็นได้ว่า terraform สร้างไฟล์ที่ชื่อว่า terraform.tfstate ขึ้นที่ root folder เสตจไฟล์นี้เป็นตัวบันทึกการเปลี่ยนแปลงที่ทำให้ terraform รู้ว่าในการ apply ครั้งหน้า terraform จะต้องอ้างอิงถึง resource ไหน ตัวอย่างเช่น เราอาจจะมี image และ container ที่ไม่ได้ถูกสร้างด้วย terraform อยู่ใน docker daemon ของเรา ถ้าเราลองสำรวจ state file นี้จะเห็นได้ว่า terraform เก็บข้อมูล รวมถึง id ของ resource ที่เราสร้างขึ้นมา

ใน live environment state file ควรจะถูกเก็บไว้ที่ remote (ซึ่งบทความนี้จะยังไม่ครอบคลุมถึงเนื้อหานี้) เพื่อให้แน่ใจว่า developer จะอ้างอิงถึง state file เดียวกันอีกทั้งจะไม่สามารถสร้างการเปลี่ยนแปลงพร้อมกันได้ เนื่องจาก state file จะมีการ lock ทุกครั้งในระหว่างทำการเปลี่ยนแปลง

1
# ตัวอย่างเนื้อหาที่อยู่ใน terraform state file
2
{
3
"mode": "managed",
4
"type": "docker_image",
5
"name": "my_nginx_image",
6
"provider": "provider[\"registry.terraform.io/kreuzwerker/docker\"]",
7
"instances": [
8
{
9
"schema_version": 0,
10
"attributes": {
11
"build": [],
12
"force_remove": null,
13
"id": "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest",
14
"keep_locally": null,
15
"latest": "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b",
16
"name": "nginx:latest",
17
"output": null,
18
"pull_trigger": null,
19
"pull_triggers": null,
20
"repo_digest": "nginx@sha256:859ab6768a6f26a79bc42b231664111317d095a4f04e4b6fe79ce37b3d199097"
21
},
22
"sensitive_attributes": [],
23
"private": "bnVsbA=="
24
}
25
]
26
}

Terraform variable

จากโค้ดที่เราเขียนมา จะเห็นได้ว่าเราทำการ hardcode port ที่ map ออกมายัง host เป็น port 8080 ปัญหาก็คือเราไม่สามารถเปลี่ยนแปลง port นอกจากไปแก้ไขโค้ด ซึ่งในบางสถานการณ์ เราอาจอยากให้ environment ต่างๆใช้ port ที่ต่างกันไปเช่นเราอาจจะอยากให้ qa ของเราใช้พอร์ต 8000 และ production ใช้พอร์ต 9000 ในที่นี้ terraform variable จะช่วยเราแก้ปัญหานี้ครับ

ให้ใส่ block ข้างล่างเพิ่มเข้าไปในโค้ดของเรา

1
variable "nginx_external_port" {
2
type = number
3
description = "Port to map from nginx to docker host" # description เป็นส่วนของ document ที่ทำให้คนที่จะมาใช้งานต่อจากเราเข้าใจว่าตัวแปรนี้ใช้ทำอะไร
4
}

หลังจากนั้นให้เราเปลี่ยน nginx argument อ้างอิงถึง variable เรา

1
...
2
ports {
3
internal = 80
4
external = var.nginx_external_port
5
}
6
...

ไฟล์ main.tf ทั้งหมดของเราก็จะเป็นดังนี้ครับ

1
terraform {
2
required_providers {
3
docker = {
4
source = "kreuzwerker/docker"
5
version = "2.16.0"
6
}
7
}
8
}
9
10
provider "docker" {
11
host = "unix:///var/run/docker.sock" # ใช้ docker daemon ที่รันอยู่บนเครื่องของเรา
12
}
13
14
variable "nginx_external_port" {
15
type = number
16
description = "Port to map from nginx to docker host"
17
}
18
19
# Create a container
20
resource "docker_container" "my_first_tf_container" {
21
image = docker_image.my_nginx_image.latest
22
name = "my_terraform_nginx_container_xx"
23
ports {
24
internal = 80
25
external = var.nginx_external_port
26
}
27
}
28
29
# Pulls the image
30
resource "docker_image" "my_nginx_image" {
31
name = "nginx:latest"
32
}

หลังจากนี้เราสามารถส่ง variable ผ่าน terraform command ได้เลย ในที่นี้เราจะให้ external port เป็น port 9000

1
terraform apply -var 'nginx_external_port=9000'

หลังจาก apply แล้ว terraform ก็จะทำการเปลี่ยน external port ให้เป็น 9000 เราสามารถไปยัง Nginx welcome page ที่ port ใหม่ได้เลย http://localhost:9000

Terraform Destroy

คำสั่งสุดท้ายของเราก็คือ terraform destroy ครับคำสั่งนี้ก็จะเป็นการลบ resources ทุกอย่างที่เราสร้างขึ้นมาด้วย terraform รันคำสั่งตามข้างล่าง

1
terraform destroy

terraform จะถามเพื่อคอนเฟิร์มให้พิมพฺ์ yes แล้ว enter เท่านี้ก็เป็นอันเรียบร้อยครับ ถ้าเรารันคำสั่ง docker images && docker ps จะพบว่า resources ของเราได้ถูกลบไปเรียบร้อยแล้ว

สรุป

ยินดีด้วยครับ ถึงตอนนี้คนที่ทำตามมาได้จนจบถือว่าได้เรียนรู้พื้นฐานของการใช้ terraform พอสมควรแล้ว ซึ่งสิ่งที่เราได้เรียนรู้ไปก็จะมีการใช้ command terraform init เพื่อดาวโหลด library ที่จำเป็น terraform plan เพื่อตรวจสอบสิ่งที่จะเปลี่ยนแปลงกับ resource บนระบบ terraform apply เพื่อเปลี่ยนแปลง resource และการใช้ terraform destroy เพื่อทำการลบ resources ทั้งหมดที่สร้างด้วย terraform ออกจากระบบ

นอกจากนี้เรายังได้เรียนรู้วิธีใช้ syntax provider เพื่อกำหนดและปรับแต่งค่าของ provider ที่เราต้องการ resource เพื่อกำหนดและอ้างอิงถึง resource ภายในที่เราสร้างขึ้น และใช้ variable เพื่อทำให้เราสามารถกำหนดค่าต่างๆใน server environment ที่แตกต่างกันได้ หวังว่าบทความนี้จะเป็นประโยชน์ไม่มากก็น้อยนะครับ

สอบถาม ติชม เสนอแนะ ได้ที่ช่อง comment ด้านล่างเลยครับ

Copyright © 2024. All rights reserved - Ninenote.net