Giới thiệu
Ở bài trước chúng ta đã tìm hiểu về vòng đời của một resource trong terraform. Ở bài này chúng ta sẽ tìm hiểu về cách làm sao để lập trình trong Terraform.
Terraform hỗ trợ ta lập trình theo kiểu Functional Programming.
Tạo EC2
Ta sẽ làm ví dụ về EC2 để tìm hiểu về các khái niệm lập trình trong Terraform. Tạo một file tên là main.tf
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"]
}
resource "aws_instance" "hello" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}
Chạy terraform init
và terraform apply
, sau đó lên AWS ta sẽ thấy EC2 của ta. Với đoạn code trên thì EC2 của ta luôn luôn có instance_type
là t2.micro
, nếu ta muốn tạo lại EC2 mà có instance_type
khác thì sao? Ta sẽ sửa lại code trong tệp tin Terraform? Vậy thì không linh hoạt cho lắm, thay vào đó ta sẽ sử dụng biến (được gọi là variable
trong lập trình) để làm việc này.
Khai báo biến đầu vào
Ta có thể định nghĩa biến cho Terraform với cú pháp như bên dưới:
Đây là cú pháp variable block
để khai báo biến. Ở ví dụ trên, ta tạo thêm một tệp tin nữa với tên là variable.tf
(này bạn đặt tên gì cũng được nha) để khai báo biến của ta.
variable "instance_type" {
type = string
description = "Instance type of the EC2"
}
Thuộc tính là type
để chỉ định kiểu dữ liệu của biến đó, thuộc tính description
dùng để ghi lại mô tả cho người đọc biến đó biết nó có ý nghĩa gì. Chỉ có thuộc tính type là bắt buộc phải khai báo. Trong Terraform thì một biến sẽ có các kiểu dữ liệu sau:
- Basic Type: string, number, bool
- Complex Type: list(), set(), map(), object(), tuple()
Trong Terraform, kiểu dữ liệu number và bool sẽ được chuyển thành kiểu dữ liệu string khi cần thiết. Nghĩa là 1 sẽ thành "1", true sẽ thành "true".
Ta dùng cú pháp var.<VARIABLE_NAME>
để truy cập được giá trị của biến, cập nhật lại tệp tin main.tf
.
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"]
}
resource "aws_instance" "hello" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type # change here
}
Ở thuộc tính instance_type
thay vì gán cứng thì bây giờ ta sẽ dùng biến var.instance_type.
Gán giá trị cho biến
Để gán giá trị cho biến, ta sẽ tạo một tệp tin tên là terraform.tfvars
.
instance_type = "t2.micro"
Khi ta chạy terraform apply
thì tệp tin terraform.tfvars
sẽ được Terraform sử dụng để tải giá trị mặc định cho biến, nếu ta không muốn dùng mặc định, thì khi chạy câu lệnh apply
ta thêm vào thuộc tính là -var-file
. Tạo một tệp tin tên là production.tfvars
.
instance_type = "t3.small"
Khi chạy CI/CD cho production, ta chỉ định tệp tin như sau:
terraform apply -var-file="production.tfvars"
Bây giờ thì giá trị instance_type
của ta linh hoạt hơn nhiều.
Kiểm tra lỗi của biến
Ta cũng có thể định nghĩa biến này chỉ có thể được gán những giá trị mà ta cho phép bằng cách sử dùng thuộc tính validating
, như sau:
variable "instance_type" {
type = string
description = "Instance type of the EC2"
validation {
condition = contains(["t2.micro", "t3.small"], var.instance_type)
error_message = "Value not allow."
}
}
Ở tệp tin trên ta sẽ dùng hàm contains để kiểm tra giá trị của biến instance_type
này chỉ được nằm trong mảng ta cho phép, nếu không thì khi ta chạy câu lệnh apply
ta sẽ thấy lỗi ở trường error_message
. Sửa lại tệp tin terraform.tfvars
.
instance_type = "t3.micro"
Chạy apply
.
terraform apply
╷
│ Error: Invalid value for variable
│
│ on variable.tf line 1:
│ 1: variable "instance_type" {
│
│ Value not allow.
│
│ This was checked by the validation rule at variable.tf:5,3-13.
╵
Sử dụng validating
để kiểm soát giá trị của biến mà bạn muốn. Sửa lại tệp tin terraform.tfvars
như cũ nhé. Thông thường khi tạo EC2 xong, ta sẽ muốn xem IP của nó, để làm được việc đó thì ta sử dụng output block
.
Giá trị đầu ra
Giá trị của output block
sẽ được in ra Terminal, cú pháp như sau:
Để in được giá trị public_ip
của EC2, ta thêm vào tệp tin main.tf
đoạn code sau:
...
output "ec2" {
value = {
public_ip = aws_instance.hello.public_ip
}
}
Bạn chạy lại câu lệnh apply
, ta sẽ thấy giá trị IP của EC2 được in ra Terminal.
terraform apply -auto-approve
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
ec2 = {
"public_ip" = "52.36.124.230"
}
Bây giờ thì ta đã biết cách sử dụng biến và output
. Tiếp theo, nếu ta muốn thêm một EC2 nữa thì sao? Trong tệp tin main.tf
ta sẽ sao chép ra thêm một EC2 nữa.
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"]
}
resource "aws_instance" "hello1" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
resource "aws_instance" "hello2" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
output "ec2" {
value = {
public_ip1 = aws_instance.hello1.public_ip
public_ip2 = aws_instance.hello2.public_ip
}
}
Ta sẽ thêm một resource block
cho EC2 thứ hai, và ở phần output
, ta cập nhật lại để nó có thể in ra được IP của hai con EC2. Mọi thứ đều không có gì phức tạp hết, nhưng nếu giờ ta muốn tạo 100 con EC2 thì sao? Ta có thể sao chép ra 100 resource block
, nhưng không ai làm vậy cả 😂, mà ta sẽ sử dụng thuộc tính count
.
Thuộc tính count
Thuộc tính count
là một Meta Argument, là một thuộc tính trong Terraform chứ không phải của resource type thuộc Provider, ở bài 1 ta đã nói resource type
chỉ có chứa các thuộc tính mà Provider cung cấp, còn Meta Argument là thuộc tính của Terraform => nghĩa là ta có thể sử dụng nó ở bất kì resource block
nào. Cập nhật lại tệp tin main.tf
mà sẽ tạo ra 5 EC2.
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"]
}
resource "aws_instance" "hello" {
count = 5
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
output "ec2" {
value = {
public_ip1 = aws_instance.hello[0].public_ip
public_ip2 = aws_instance.hello[1].public_ip
public_ip3 = aws_instance.hello[2].public_ip
public_ip4 = aws_instance.hello[3].public_ip
public_ip5 = aws_instance.hello[4].public_ip
}
}
Bây giờ thì khi ta chạy apply
, Terraform sẽ tạo ra cho ta 5 con EC2. Bạn để ý là ở phần output, để truy cập được resource thì ta sẽ dùng thêm dấu []
và giá trị Index của resource. Bình thường để truy cập được resource ta dùng theo cú pháp <RESOURCE TYPE>.<NAME>
, nhưng khi ta dùng count thì ta sẽ truy cập resource theo cú pháp <RESOURCE TYPE>.<NAME>[index]
.
Bây giờ ta đã giải quyết được vấn đề sao chép resource ra khi cần tạo nó với số lượng lớn, nhưng ở phần output
ta vẫn phải ghi ra từng resource riêng lẻ. Ta sẽ giải quyết nó bằng cách sử dụng biểu thức for.
Biểu thức For
For cho phép ta duyệt qua một danh sách, cú pháp của lệnh for
:
for <value> in <list> : <return value>
Ví dụ dùng for:
- Tạo ra một mảng mới với giá trị của mảng mới sẽ được viết hoa:
[for s in var.words : upper(s)]
- Tạo ra một đối tượng mới với giá trị của đối tượng mới được viết hoa:
{ for k, v in var.words : k => upper(s) }
Ta sẽ dùng for để rút gọn phần output
của EC2. Cập nhật lại tệp tin main.tf
...
resource "aws_instance" "hello" {
count = 5
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
output "ec2" {
value = {
public_ip = [ for v in aws_instance.hello : v.public_ip ]
}
}
Phần output
trên sẽ in ra giá trị public_ip
là một mảng IP của tất cả EC2 được tạo ra. Còn nếu bạn muốn in output
ra theo kiểu { public_ip1: <value>, public_ip2: <value> }
thì ta có thể dùng hàm format
.
Hàm Format
Hàm format
sẽ giúp ta nối chuỗi, cập nhật output
lại như sau:
...
resource "aws_instance" "hello" {
count = 5
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
output "ec2" {
value = { for i, v in aws_instance.hello : format("public_ip%d", i + 1) => v.public_ip }
}
Chạy terraform plan
để kiểm tra ta sẽ thấy output
lúc này sẽ là dạng { public_ip1: <value>, public_ip2: <value> }
.
terraform plan
...
Changes to Outputs:
+ ec2 = {
+ public_ip1 = (known after apply)
+ public_ip2 = (known after apply)
+ public_ip3 = (known after apply)
+ public_ip4 = (known after apply)
+ public_ip5 = (known after apply)
}
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.
Bây giờ thì định đạng của giá trị đầu ra dễ đọc hơn nhiều.
Kết luận
Vậy là ta đã tìm hiểu xong về một số cách đơn giản để lập trình trong Terraform. Sử dụng varibale
để chứa biến, sử dụng output
để hiển thị giá trị đầu ra, sử dụng for để duyệt qua mảng. Ở bài tiếp theo ta sẽ tìm hiểu thêm một vài hàm thông qua ví dụ dùng Terraform để triển khai một trang Web lên S3.
Tác giả @Quân Huỳnh
Nếu bài viết có gì sai hoặc cần cập nhật thì liên hệ Admin.
Tham gia nhóm chat của DevOps VN tại Telegram.
Kém tiếng Anh và cần nâng cao trình độ giao tiếp: Tại sao bạn học không hiệu quả?