Terraform - terragrunt cơ bản
Table of contents
- Syllabus
- Nội dung
- 1. Terraform
- Giới thiệu IaC và terraform
- Terraform workflow
- Terraform AWS provider & terraform resources
- Terraform local backend and S3 backend
- Terraform variables, terraform types and values
- Các câu lệnh terraform thường dùng
- Terraform state & terraform import
- Terraform default tags
- Terraform dynamic block
- Terraform Conditional Expressions
- Terraform data sources
- Terraform local values
- Terraform for expressions
- Terraform splat expressions
- Terraform provisioner
- Terraform variables custom validation rules
- Terraform modules
- Cách tạo Terraform module dựa vào terraform docs
- Cách refer attributes của module khác
- Terraform console và ứng dụng terraform console
- Hướng dẫn sử dụng Terraform depends_on
- Terraform functions
- 2.Terragrunt
- 3.Một số plugins
Syllabus
- Terraform
Giới thiệu IaC và terraform
Terraform workflow
Terraform AWS provider & terraform resources
Terraform local and S3 backends
Terraform variables, terraform types and values: string, number, bool, list (tuple), set, map, terraform type conversion
Các câu lệnh terraform thường dùng: terraform init, terraform validate, terraform plan, terraform apply, terraform fmt, terraform terraform destroy
Terraform state, terraform state lock và unlock, terraform state show, terraform output, terraform state rm, terraform import
Terraform module, các thành phần của terraform module, cách tạo 1 terraform module, cách sử dụng terraform module, refer các giá trị của terraform module khác
Terraform functions: format, formatlist, join, split, alltrue, anytrue, coalesce, compact, concat, contains, element, flatten, index, keys, length, lookup, merge, one, setproduct, values, file, fileexists, filebase64, templatefile, filemd5, cidrsubnet, sensitive, tolist, tomap, toset, try, type
Terraform console và ứng dụng terraform console để viết terraform code
- Terragrunt
Giới thiệu về terragrunt
Cách khai báo biến trong terraform
Cấu trúc thư mục tiêu chuẩn của terragrunt
Thư mục _components trong terragrunt
Override giá trị khai báo terragrunt
- Các plugins
Terraform docs
Checkov
Infracost
Nội dung
1. Terraform
Giới thiệu IaC và terraform
1. Infrastructure as Code (IaC):
Tưởng tượng: Quản lý cơ sở hạ tầng như viết mã.
Lợi ích:
Nhất quán: Cấu trúc đồng nhất, dễ dàng tái tạo.
Tự động hóa: Triển khai nhanh chóng, ít lỗi hơn.
Kiểm soát phiên bản: Theo dõi thay đổi, dễ dàng rollback.
Ví dụ: Thay vì cấu hình thủ công từng máy chủ, bạn viết mã IaC mô tả cấu hình mong muốn và tự động triển khai.
2. Terraform:
Công cụ IaC phổ biến: Dễ sử dụng, linh hoạt.
Hỗ trợ nhiều nhà cung cấp: AWS, Azure, GCP, v.v.
Ngôn ngữ cấu hình HCL: Dễ đọc, dễ viết.
Ví dụ: Sử dụng Terraform để tạo EC2 instance trên AWS, mô tả cấu hình mạng, bảo mật, v.v.
IaC thay đổi cách quản lý cơ sở hạ tầng, giúp bạn làm việc hiệu quả và nhất quán hơn.
Terraform là công cụ IaC mạnh mẽ giúp bạn dễ dàng triển khai và quản lý cơ sở hạ tầng trên nhiều đám mây.
Terraform workflow
1. Khởi tạo (init):
Bắt đầu dự án Terraform của bạn.
Tải xuống các plugin, provider cần thiết.
Khởi tạo Terraform state (terraform.tfstate) để lưu trữ thông tin về cơ sở hạ tầng của bạn.
2. Lên kế hoạch (plan):
Mô tả những thay đổi bạn muốn thực hiện đối với cơ sở hạ tầng của mình.
Terraform sẽ phân tích kế hoạch và hiển thị cho bạn những gì sẽ thay đổi.
Bạn có thể xem xét kỹ lưỡng kế hoạch trước khi thực hiện.
3. Áp dụng (apply):
Thực hiện các thay đổi được mô tả trong kế hoạch.
Terraform sẽ tạo hoặc cập nhật cơ sở hạ tầng của bạn theo cấu hình mong muốn.
Tệp trạng thái được cập nhật để phản ánh trạng thái hiện tại của cơ sở hạ tầng.
4. Hủy (destroy):
Xóa cơ sở hạ tầng đã được tạo bằng Terraform.
Terraform sẽ dỡ bỏ các tài nguyên theo thứ tự đảo ngược với quy trình apply.
Tệp trạng thái được cập nhật để phản ánh việc xóa cơ sở hạ tầng.
Ví dụ:
Bạn muốn tạo một instance EC2 trên AWS.
Sử dụng Terraform để mô tả cấu hình instance (loại instance, mạng, bảo mật, v.v.).
Khởi chạy Terraform plan để xem những gì sẽ thay đổi.
Apply plan để tạo instance EC2.
Sau đó, bạn có thể sửa đổi cấu hình instance và lặp lại quy trình.
Khi bạn không còn cần instance, hãy sử dụng
terraform destroy
để hủy nó.
Terraform AWS provider & terraform resources
Terraform AWS provider:
Cầu nối Terraform với các dịch vụ AWS.
Cho phép bạn quản lý cơ sở hạ tầng AWS bằng mã Terraform.
Ví dụ: bạn có thể sử dụng AWS provider để tạo instance EC2, S3 buckets, VPC, v.v.
Ngoài AWS Provider, terraform còn support rất nhiều các provider khác, vd: GCP, K8s, Azure,... Xem link này để biết thêm chi tiết: https://registry.terraform.io/browse/providers
Terraform resources:
Đại diện cho các đối tượng cơ sở hạ tầng AWS như instance EC2, S3 buckets, VPC, v.v.
Được mô tả bằng mã HCL, xác định thuộc tính và cấu hình của chúng.
Ví dụ: bạn có thể tạo tài nguyên EC2 Terraform để mô tả loại instance, kích thước, mạng, bảo mật, v.v.
Ví dụ:
Giả sử bạn muốn tạo một instance EC2 đơn giản trên AWS bằng Terraform:
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "My EC2 Instance"
}
}
Giải thích:
provider "aws"
: Khối này xác định provider là AWS.resource "aws_instance" "example"
: Khối này mô tả một tài nguyên (resource) instance EC2 được đặt tên là "example".ami
: ID của Amazon Machine Image (AMI) để sử dụng cho instance.instance_type
: Loại instance EC2 (t2.micro trong ví dụ này).tags
: Nhãn để gắn với instance.
Terraform local backend and S3 backend
Backend:
Nơi Terraform lưu trữ thông tin về trạng thái cơ sở hạ tầng của bạn.
Giúp Terraform theo dõi các thay đổi đã được thực hiện và đảm bảo tính nhất quán.
Hai loại backend phổ biến:
1. Backend cục bộ (local):
Lưu trữ tệp trạng thái Terraform trong local filesystem của bạn.
Lợi ích:
Đơn giản, dễ sử dụng.
Phù hợp cho các dự án nhỏ hoặc cá nhân.
Nhược điểm:
Khi sử dụng
terraform local
backend, nhiều người dùng có thể truy cập và sửa đổi cùng một tệp trạng thái, dẫn đến xung đột và dữ liệu bị hỏng.Rủi ro mất dữ liệu nếu tệp trạng thái bị xóa hoặc hỏng.
2. Backend S3:
Lưu trữ tệp trạng thái Terraform trong Amazon S3 bucket.
Lợi ích:
An toàn, có thể truy cập được từ mọi nơi.
Dễ dàng cộng tác với nhiều người dùng.
Khả năng sao lưu và khôi phục dữ liệu.
Nhược điểm:
Yêu cầu tài khoản AWS và cấu hình S3.
Có thể phức tạp hơn để thiết lập và sử dụng.
Ví dụ:
- Backend cục bộ:
terraform {
backend {
local {
path = "terraform.state"
}
}
}
- Backend S3:
terraform {
backend {
s3 {
bucket = "my-terraform-state-bucket"
key = "terraform.state"
region = "us-east-1"
}
}
}
Mặc dù là optional nhưng S3 backend sẽ thường kết hợp với DynamoDB table để lock state.
DynamoDB Lock:
Sử dụng bảng DynamoDB để lưu trữ khóa cho mỗi tệp trạng thái.
Khi người dùng muốn ghi vào tệp trạng thái, họ phải lấy khóa từ DynamoDB.
Chỉ có một người dùng có thể lấy khóa tại một thời điểm, đảm bảo tính nhất quán của dữ liệu.
Terraform variables, terraform types and values
Biến Terraform:
Giá trị có thể thay đổi được sử dụng trong mã Terraform.
Giúp code linh hoạt, tái sử dụng và dễ dàng quản lý cấu hình.
Ví dụ:
variable "region" {
type = "string"
default = "us-east-1"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
region = var.region
}
variable "region"
: Khai báo biếnregion
với kiểustring
và giá trị mặc định"us-east-1"
.resource "aws_instance" "example"
: Sử dụng biếnvar.region
trong thuộc tínhregion
của tài nguyên instance EC2.
Terraform types:
Xác định loại dữ liệu của biến.
Giúp Terraform kiểm tra tính hợp lệ của giá trị và thực hiện các phép toán phù hợp.
Các types cơ bản:
string
: Chuỗi văn bản (ví dụ: "my-instance", "us-east-1").number
: Số nguyên hoặc số thực (ví dụ: 10, 3.14).bool
: True hoặc False (ví dụ: true, false).list
: Mảng các giá trị cùng kiểu (ví dụ: ["ami-0c55b159cbfafe1f0", "t2.micro"]).tuple
: Mảng cố định các giá trị với thứ tự xác định (ví dụ: ["ami-0c55b159cbfafe1f0", "t2.micro", 1]).set
: Tập hợp các giá trị duy nhất (ví dụ: {1, 2, 3}).map
: Từ điển ánh xạ khóa sang giá trị (ví dụ: {"name": "my-instance", "type": "t2.micro"}).
Ví dụ:
variable "instance_types" {
type = list(string)
default = ["t2.micro", "t2.small"]
}
variable "subnet_ids" {
type = set(string)
default = {subnet-12345678, subnet-87654321}
}
variable "tags" {
type = map(string)
default = {
Name = "My EC2 Instance",
Environment = "production"
}
}
Chuyển đổi kiểu:
Phân loại giá trị sang kiểu mong muốn.
Hữu ích khi giá trị được lấy từ nguồn bên ngoài hoặc thao tác dữ liệu.
Ví dụ:
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "subnet_cidr_prefix" {
type = number
default = 24
}
output "subnet_cidr" {
value = [
"${var.vpc_cidr}/${var.subnet_cidr_prefix}",
"${var.vpc_cidr}/${var.subnet_cidr_prefix + 1}",
"${var.vpc_cidr}/${var.subnet_cidr_prefix + 2}"
]
}
variable "subnet_cidr_prefix"
chuyển đổi giá trị mặc định "24" sang kiểunumber
.output "subnet_cidr"
sử dụngvar.vpc_cidr
vàvar.subnet_cidr_prefix
để tạo danh sách các cidr block subnet.
Terraform object variable:
Cho phép bạn lưu trữ các cấu trúc dữ liệu phức tạp trong biến.
Giúp code gọn gàng, dễ đọc và dễ quản lý cấu hình.
Ví dụ:
variable "network" {
type = map(object({
vpc_id = string
subnet_ids = list(string)
}))
default = {
"production" = {
vpc_id = "vpc-12345678"
subnet_ids = [
"subnet-abcedf123",
"subnet-4567890a"
]
}
}
}
resource "aws_vpc" "production" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "production_subnet_1" {
vpc_id = aws_vpc.production.id
cidr_block = "10.0.1.0/24"
}
resource "aws_subnet" "production_subnet_2" {
vpc_id = aws_vpc.production.id
cidr_block = "10.0.2.0/24"
}
variable "network"
: Khai báo biếnnetwork
với kiểumap(object({...}))
.Mỗi phần tử trong
network
là một đối tượng có các thuộc tínhvpc_id
vàsubnet_ids
.default
: Cung cấp giá trị mặc định chonetwork
, bao gồm mạng "production" với VPC và subnet đã tạo.resource "aws_vpc" "production"
: Tạo VPC với cidr block được xác định.resource "aws_subnet" "production_subnet_1", "production_subnet_2"
: Tạo subnet trong VPC "production".
So sánh với Biến cơ bản:
Tính năng | Biến cơ bản | Biến đối tượng |
Kiểu dữ liệu | String, number, bool, list, tuple, set | Map(object) |
Tổ chức dữ liệu | Đơn giản, phẳng | Phức tạp, phân cấp |
Khả năng tái sử dụng | Thấp | Cao |
Độ phức tạp | Dễ dàng | Khó hơn |
Từ khoá optional trong khai báo type Terraform object variable:
Từ khóaoptional
cho phép bạn xác định các thuộc tính tùy chọn trong kiểu biến đối tượng Terraform.
Ví dụ:
type "network" {
vpc_id = string
subnet_ids = list(string)
# subnet_tags là thuộc tính tùy chọn
subnet_tags = optional(map(string))
}
variable "networks" {
type = map(network)
default = {
"production" = {
vpc_id = "vpc-12345678"
subnet_ids = [
"subnet-abcedf123",
"subnet-4567890a"
]
# subnet_tags là tùy chọn, không cần cung cấp
# subnet_tags = {
# Name = "My Subnet"
# }
}
}
}
Giải thích:
type "network"
: Định nghĩa kiểu biến đối tượngnetwork
.vpc_id
vàsubnet_ids
là các thuộc tính bắt buộc.subnet_tags
được khai báo với từ khóaoptional
, nghĩa là nó không bắt buộc phải cung cấp khi sử dụng kiểunetwork
.Trong
variable "networks"
, mạng "production" không cung cấp giá trị chosubnet_tags
, điều này hoàn toàn hợp lệ vì nó là thuộc tính tùy chọn.
Lợi ích:
Tăng cường tính linh hoạt cho kiểu biến đối tượng.
Cho phép bạn định nghĩa cấu trúc dữ liệu phức tạp mà không cần cung cấp giá trị cho tất cả các thuộc tính mỗi lần.
Các câu lệnh terraform thường dùng
# Khởi tạo thư mục Terraform
terraform init
# Kiểm tra cấu hình Terraform
# Đây là một câu lệnh hữu ích, nhất là khi viết terraform module
# Khi chạy `terraform validate`, nếu có lỗi cú pháp thì sẽ có thông báo cho bạn
# Nếu không có lỗi về mặt cú pháp, thì sẽ hiển thị thông báo:
# Success! The configuration is valid.
terraform validate
# Hiển thị kế hoạch cho các thay đổi
terraform plan
# Áp dụng các thay đổi
terraform apply
# Định dạng mã Terraform
terraform fmt
# Phá hủy cơ sở hạ tầng
terraform destroy
Terraform state & terraform import
Terraform State:
Là tập tin lưu trữ thông tin về trạng thái hạ tầng được quản lý bởi Terraform.
Bao gồm chi tiết như tên, loại, cấu hình và mối quan hệ của tài nguyên.
Giúp theo dõi và quản lý trạng thái hạ tầng, đảm bảo tính nhất quán.
Ví dụ: Terraform State lưu trữ thông tin về máy ảo EC2 (loại, kích thước, vùng).
Giả sử bạn đang sử dụng Terraform để quản lý một nhóm máy ảo EC2 trên AWS. Terraform State sẽ lưu trữ thông tin về mỗi máy ảo, bao gồm:
aws_
instance.example1.id
: ID của máy ảoaws_instance.example1.ami
: ID của hình ảnh máy ảo (AMI) được sử dụng để tạo máy ảoaws_instance.example1.instance_type
: Loại máy ảo (ví dụ: t2.micro)aws_instance.example1.vpc_security_group_ids
: Danh sách các nhóm bảo mật VPC áp dụng cho máy ảo
Bạn có thể sử dụng lệnh terraform state show
để xem thông tin này:
terraform state show
Kết quả sẽ hiển thị chi tiết về tất cả các máy ảo EC2 được quản lý bởi Terraform, bao gồm cả aws_instance.example1
.
Lock/Unlock Terraform State:
Lock: Đảm bảo chỉ một người/quy trình sửa đổi State tại một thời điểm.
Unlock: Cho phép bất kỳ người dùng/quy trình nào có quyền truy cập sửa đổi State.
Để force-unlock:
terraform force-unlock LOCK_ID
Tuy nhiên, không nên làm điều này trừ khi chắc chắn về hệ quả của nó.
Chi tiết xem thêm: https://developer.hashicorp.com/terraform/cli/commands/force-unlock
Hiển thị Terraform State:
- Lệnh
terraform state show
hiển thị nội dung State, cung cấp thông tin chi tiết về tài nguyên.
Terraform Output:
Cho phép lấy giá trị từ thuộc tính tài nguyên và lưu trữ dưới dạng biến truy cập được.
Hữu ích cho việc truy xuất thông tin về hạ tầng sau khi triển khai.
Ví dụ: Lấy giá trị output:
Giả sử bạn đã sử dụng Terraform để tạo một cụm Amazon Redshift. Bạn có thể tạo Terraform Output để lưu trữ URL kết nối với cụm:
output "redshift_endpoint" {
value = aws_redshift_cluster.example.endpoint
}
Sau đó, bạn có thể truy cập URL kết nối bằng cách sử dụng lệnh sau:
terraform output redshift_endpoint
Kết quả sẽ hiển thị URL của cụm Redshift.
Xóa Terraform State:
Lệnh
terraform state rm
xóa tài nguyên khỏi State.Cảnh báo: Sao lưu State trước khi sử dụng.
Đọc thêm: https://developer.hashicorp.com/terraform/cli/commands/state/rm#command-state-rm
Ví dụ: Xóa tài nguyên S3 khỏi state:
Giả sử bạn muốn xóa một S3 Bucket không còn muốn manage bởi Terraform nữa. Bạn có thể sử dụng lệnh terraform state rm
để xóa Bucket khỏi Terraform State:
terraform state rm aws_s3_bucket.example
Lệnh này sẽ xóa Bucket khỏi State.
Terraform import:
Lệnh
terraform import
thêm tài nguyên hiện có vào State.Hữu ích cho việc quản lý tài nguyên tạo ra mà không sử dụng Terraform.
Ví dụ:
Giả sử bạn đã tạo một Load Balancer trên AWS bằng cách sử dụng bảng điều khiển AWS. Bạn có thể sử dụng lệnh terraform import
để thêm Load Balancer vào Terraform State:
terraform import aws_lb.example arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app-lb/example
Lệnh này sẽ thêm Load Balancer vào Terraform State với tên "example" và ARN "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app-lb/example".
Lưu ý: Bạn cần cung cấp ARN chính xác của Load Balancer khi sử dụng terraform import
.
Terraform default tags
Terraform Default Tags là tính năng của Terraform AWS Provider cho phép bạn tự động áp dụng bộ tag mặc định cho tất cả các tài nguyên AWS được tạo ra trong cấu hình Terraform.
Lợi ích:
Giảm thiểu lỗi: Giúp đảm bảo rằng tất cả các tài nguyên đều được gắn thẻ theo một cách nhất quán, tránh lỗi do thiếu hoặc sai sót tag.
Tăng hiệu quả: Loại bỏ việc phải thêm thủ công tag cho từng tài nguyên, tiết kiệm thời gian và công sức.
Tuân thủ: Hỗ trợ tuân thủ các tiêu chuẩn và quy định về quản lý tài nguyên.
Cách sử dụng:
Cài đặt Terraform AWS Provider v3.38.0 trở lên.
Thêm khối
provider
vào tệp cấu hình Terraform.Thiết lập thuộc tính
default_tags
trong khốiprovider
với các tag mong muốn.Chạy
terraform apply
để áp dụng tag cho các tài nguyên hiện có và các tài nguyên mới được tạo.
Ví dụ:
provider "aws" {
region = "us-east-1"
default_tags = {
Name = "My-Terraform-Resources"
Environment = "production"
CostCenter = "12345"
}
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Lưu ý:
Default tags không áp dụng cho các tag bắt đầu bằng
aws:
, vì đây là các tag do AWS quản lý.Default tags có thể được ghi đè cho các tag được xác định cụ thể cho tài nguyên.
Default tags không áp dụng cho các Auto Scaling Group do tính chất động của chúng.
Tài liệu tham khảo:
Terraform dynamic block
Dynamic Block là một tính năng mạnh mẽ trong Terraform cho phép bạn tạo ra nhiều tài nguyên dựa trên một danh sách hoặc cấu trúc dữ liệu. Nó giúp bạn tự động hóa việc tạo và quản lý nhiều tài nguyên giống nhau mà không cần viết mã lặp lại cho từng tài nguyên.
Ví dụ minh họa:
Giả sử bạn muốn tạo ra 10 máy ảo EC2 với các cấu hình giống nhau, chỉ khác nhau về tên và subnet ID. Thay vì viết mã lặp lại cho từng máy ảo, bạn có thể sử dụng Dynamic Block để tạo ra 10 tài nguyên EC2 tự động:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
dynamic "subnet" {
for_each = { for subnet_id in var.subnet_ids : subnet_id => {} }
content {
subnet_id = subnet.key
}
}
}
Giải thích:
dynamic "subnet"
khai báo một Dynamic Block cho thuộc tínhsubnet
của tài nguyênaws_instance
.for_each
xác định cấu trúc dữ liệu sẽ được sử dụng để tạo ra các tài nguyên con. Trong trường hợp này,var.subnet_ids
là một danh sách subnet ID.content
xác định cấu hình cụ thể cho từng tài nguyên con. Trong trường hợp này,subnet_id
được đặt thành khóa của từng subnet ID trong danh sáchvar.subnet_ids
.
Ví dụ 2: Giả sử bạn muốn tạo ra một số lượng không cố định các "ingress rules" cho một security group trong AWS, dựa trên một danh sách các cổng (ports) được cung cấp. Bạn có thể sử dụng dynamic block như sau:
resource "aws_security_group" "example" {
name_prefix = "example-"
dynamic "ingress" {
for_each = var.ingress_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
variable "ingress_ports" {
type = list(number)
default = [80, 443]
}
Trong ví dụ này, chúng ta sử dụng một dynamic block ingress
để tạo ra các "ingress rules" cho security group. Khối ingress
sẽ được lặp lại cho mỗi phần tử trong danh sách ingress_ports
bằng cách sử dụng từ khóa for_each
. Bên trong khối content
, chúng ta định nghĩa các thuộc tính cho mỗi "ingress rule", bao gồm cổng đến (from_port
), cổng đi (to_port
), giao thức (protocol
) và khối địa chỉ IP (cidr_blocks
).
Trong trường hợp này, với ingress_ports = [80, 443]
, Terraform sẽ tạo ra hai "ingress rules" cho security group, một cho cổng 80 và một cho cổng 443, cho phép truy cập từ bất kỳ địa chỉ IP nào.
Dynamic block cung cấp một cách linh hoạt để tạo ra các cấu hình phức tạp và có thể thay đổi động, giúp đơn giản hóa việc quản lý và triển khai các tài nguyên trong Terraform.
Lợi ích của Dynamic Block:
Giảm thiểu mã lặp lại: Dynamic Block giúp bạn tránh viết mã lặp lại cho nhiều tài nguyên giống nhau.
Tăng tính linh hoạt: Dynamic Block cho phép bạn tạo ra nhiều tài nguyên dựa trên bất kỳ cấu trúc dữ liệu nào, bao gồm danh sách, bản đồ và kết quả của các hàm.
Dễ dàng quản lý: Dynamic Block giúp bạn dễ dàng quản lý nhiều tài nguyên cùng lúc.
Lưu ý:
Dynamic Block chỉ có thể được sử dụng cho các thuộc tính tùy chọn của tài nguyên.
Dynamic Block có thể làm cho mã Terraform của bạn phức tạp hơn, do đó bạn nên sử dụng nó một cách cẩn thận.
Terraform Conditional Expressions
Terraform Conditional Expressions
Conditional Expressions trong Terraform cho phép bạn thực hiện các tính toán và điều khiển luồng dựa trên các điều kiện nhất định. Chúng rất hữu ích khi bạn muốn điều khiển cách một tài nguyên được tạo hoặc cấu hình dựa trên các giá trị của biến hoặc dữ liệu đầu vào.
Ví dụ, giả sử bạn muốn tạo một S3 bucket với khả năng ghi nhật ký (logging) được bật nếu biến enable_logging
được đặt là true
, ngược lại thì tắt chức năng này. Bạn có thể sử dụng Conditional Expression như sau:
resource "aws_s3_bucket" "example" {
bucket = "example-bucket"
acl = "private"
logging {
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "log/"
}
# Conditional Expression
logging = var.enable_logging ? { ... } : {}
}
Trong ví dụ trên, khối logging
sẽ chỉ được tạo ra nếu var.enable_logging
là true
. Nếu không, khối logging
sẽ là một khối rỗng {}
.
Conditional Resource Creation
Ngoài việc sử dụng Conditional Expressions để điều khiển cách một tài nguyên được cấu hình, bạn cũng có thể sử dụng chúng để quyết định liệu một tài nguyên có được tạo ra hay không.
Ví dụ, giả sử bạn chỉ muốn tạo một DynamoDB table nếu biến create_table
được đặt là true
. Bạn có thể sử dụng một Conditional Expression như sau:
resource "aws_dynamodb_table" "example" {
count = var.create_table ? 1 : 0
name = "example-table"
billing_mode = "PROVISIONED"
read_capacity = 5
write_capacity = 5
hash_key = "id"
attribute {
name = "id"
type = "S"
}
}
Trong ví dụ này, chúng ta sử dụng biểu thức count = var.create_table ? 1 : 0
để quyết định xem tài nguyên aws_dynamodb_table
có được tạo ra hay không. Nếu var.create_table
là true
, count
sẽ là 1, và tài nguyên sẽ được tạo ra. Nếu var.create_table
là false
, count
sẽ là 0, và tài nguyên sẽ không được tạo ra.
Terraform data sources
Data Sources trong Terraform là một cách để lấy thông tin về các tài nguyên đã tồn tại bên ngoài Terraform. Chúng cho phép bạn truy vấn và sử dụng dữ liệu từ các nguồn khác nhau, chẳng hạn như các dịch vụ đám mây hoặc API, để tạo và cấu hình các tài nguyên mới trong Terraform.
Ví dụ, giả sử bạn muốn tạo một EC2 instance trong AWS và gán nó cho một VPC đã tồn tại. Thay vì cung cấp ID của VPC bằng cách nhập thủ công, bạn có thể sử dụng Data Source để lấy thông tin về VPC đó:
data "aws_vpc" "selected" {
default = true
}
resource "aws_instance" "example" {
ami = "ami-0c94855ba95c71c99"
instance_type = "t2.micro"
vpc_id = data.aws_vpc.selected.id
}
Trong ví dụ này, data "aws_vpc" "selected"
là một Data Source được sử dụng để lấy thông tin về VPC mặc định (default VPC). Sau đó, chúng ta sử dụng data.aws
_
vpc.selected.id
để lấy ID của VPC và gán nó cho thuộc tính vpc_id
của tài nguyên aws_instance
.
Một số ví dụ khác về việc sử dụng Data Sources:
Lấy thông tin về các subnets, security groups, hoặc các tài nguyên khác trong AWS.
Lấy thông tin về các tài nguyên trong các dịch vụ đám mây khác như GCP hoặc Azure.
Lấy dữ liệu từ các API bên ngoài, như API của một dịch vụ web hoặc cơ sở dữ liệu.
Data Sources cung cấp một cách linh hoạt để tận dụng dữ liệu hiện có trong quá trình tạo và cấu hình các tài nguyên mới, giúp đơn giản hóa và tự động hóa quá trình triển khai.
Một lưu ý quan trọng: Dữ liệu từ Data Sources chỉ được đọc trong quá trình khởi tạo (init) và áp dụng (apply) của Terraform. Nếu dữ liệu nguồn thay đổi sau đó, bạn cần chạy lệnh terraform apply
để Terraform cập nhật dữ liệu mới.
Terraform local values
Terraform Local Values là một cách để định nghĩa các giá trị tạm thời trong quá trình chạy Terraform. Chúng được sử dụng để tính toán, lưu trữ và truyền dữ liệu giữa các tài nguyên mà không cần phải tạo ra một tài nguyên thực tế.
Ví dụ, giả sử bạn muốn tạo một số tài nguyên với cùng một tiền tố, bạn có thể định nghĩa một local value như sau:
locals {
prefix = "myproject-${var.environment}"
}
resource "aws_s3_bucket" "data" {
bucket = "${local.prefix}-data"
acl = "private"
}
resource "aws_instance" "app" {
ami = "ami-0c94855ba95c71c99"
instance_type = "t2.micro"
tags = {
Name = "${local.prefix}-app"
}
}
Trong ví dụ này, chúng ta định nghĩa một local value prefix
dựa trên biến var.environment
. Sau đó, chúng ta sử dụng local value này để đặt tên cho bucket S3 và tag cho EC2 instance.
Local values có thể được sử dụng để tính toán các giá trị phức tạp, chẳng hạn như danh sách hoặc ánh xạ, dựa trên các giá trị đầu vào hoặc tài nguyên khác. Chúng cũng có thể được sử dụng để lưu trữ các giá trị trung gian trong quá trình tính toán, giúp làm cho mã Terraform dễ đọc và dễ bảo trì hơn.
Một số ví dụ khác về việc sử dụng local values:
Tính toán danh sách các cổng (ports) dựa trên các giá trị đầu vào.
Lưu trữ một danh sách các ID của các tài nguyên đã được tạo ra.
Xây dựng một chuỗi định dạng đặc biệt dựa trên các giá trị đầu vào.
Local values không tạo ra bất kỳ tài nguyên thực tế nào, chúng chỉ là một cách để tổ chức và tính toán dữ liệu trong quá trình chạy Terraform. Chúng rất hữu ích để làm cho mã Terraform dễ đọc và dễ bảo trì hơn, đồng thời tăng cường khả năng tái sử dụng và mô-đun hóa.
Terraform for expressions
Terraform For Expressions cung cấp một cách ngắn gọn để tạo ra các chuỗi, danh sách và ánh xạ bằng cách lặp lại trên một chuỗi, danh sách hoặc ánh xạ khác.
For Expressions với Chuỗi
>> ["a", "b", "c"]
[
"a",
"b",
"c",
]
>> [for s in ["a", "b", "c"] : upper(s)]
[
"A",
"B",
"C",
]
Ví dụ trên tạo ra một danh sách mới bằng cách áp dụng hàm upper
cho từng phần tử trong danh sách ban đầu ["a", "b", "c"]
.
For Expressions với Danh Sách
>> [for x in [1, 2, 3] : x * x]
[
1,
4,
9,
]
Ví dụ này tạo ra một danh sách mới bằng cách nhân đôi từng phần tử trong danh sách ban đầu.
For Expressions với Ánh Xạ
>> {for name, age in {a = 24, b = 25, c = 26} : name => age+1}
{
"a" = 25,
"b" = 26,
"c" = 27,
}
Ví dụ trên tạo ra một ánh xạ mới, với các khóa giống danh sách ban đầu, nhưng giá trị được tăng lên 1.
Ngoài ra, các For Expressions có thể lồng nhau:
>> [for x in [1, 2] : [for y in [1, 2] : x * y]]
[
[1, 2],
[2, 4],
]
Với việc kết hợp các toán tử như ...
để nối các danh sách, For Expressions trở thành một công cụ rất linh hoạt và mạnh mẽ để tạo ra các cấu trúc dữ liệu phức tạp một cách ngắn gọn và dễ hiểu.
Ví dụ thực tế:
locals {
env_maps = {
prod = {
instance_type = "m5.large"
scale = 5
}
dev = {
instance_type = "t2.micro"
scale = 2
}
}
}
output "prod_instance_type" {
value = local.env_maps.prod.instance_type
}
output "all_instance_types" {
value = [for s in local.env_maps : s.instance_type]
}
Terraform splat expressions
Biểu thức "splat" trong Terraform là một cách để truy cập và lấy giá trị từ các phần tử trong một danh sách, tập hợp hoặc tuple. Nó cung cấp một cú pháp ngắn gọn và tiện lợi để truy xuất các thuộc tính, chỉ số hoặc phần tử trong các cấu trúc dữ liệu phức tạp.
Cú pháp chính của biểu thức splat là [*]
. Khi áp dụng biểu thức splat vào một giá trị không phải là danh sách, tập hợp hoặc tuple, nó sẽ chuyển đổi giá trị đó thành một danh sách gồm một phần tử duy nhất (tuple gồm một phần tử). Nếu giá trị là null, biểu thức splat sẽ trả về một tuple rỗng.
Ví dụ, giả sử bạn có một danh sách các đối tượng và bạn muốn truy cập thuộc tính id
của mỗi đối tượng trong danh sách đó. Bằng cách sử dụng biểu thức splat, bạn có thể làm như sau:
var.list[*].id
Biểu thức trên sẽ trả về một danh sách gồm các giá trị của thuộc tính id
từ tất cả các đối tượng trong var.list
.
Ngoài ra, biểu thức splat cũng có thể được sử dụng để truy xuất các thuộc tính và chỉ số từ các danh sách hoặc tập hợp dữ liệu phức tạp. Ví dụ:
var.list[*].interfaces[0].name
Trong ví dụ trên, biểu thức splat sẽ truy cập thuộc tính name
của phần tử đầu tiên ([0]
) trong danh sách interfaces
của mỗi đối tượng trong var.list
.
Xem thêm: https://developer.hashicorp.com/terraform/language/expressions/splat
Terraform provisioner
Terraform Provisioner là một tính năng cho phép bạn thực hiện các tác vụ cấu hình hoặc quản lý trên các tài nguyên đã được tạo ra bởi Terraform. Các tác vụ này có thể bao gồm cài đặt phần mềm, sao chép tệp, khởi tạo cơ sở dữ liệu, và nhiều hơn nữa. Provisioner được thực thi sau khi tài nguyên đã được tạo và có thể được sử dụng để chuẩn bị môi trường cho tài nguyên đó.
Ví dụ 1: Giả sử bạn muốn cài đặt Apache trên một EC2 instance mà bạn đã tạo bằng Terraform. Bạn có thể sử dụng remote-exec
provisioner để thực hiện việc này:
resource "aws_instance" "web_server" {
ami = "ami-0c94855ba95c71c99"
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = [
"sudo yum install -y httpd",
"sudo systemctl start httpd"
]
connection {
type = "ssh"
user = "ec2-user"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
}
Trong ví dụ này, remote-exec
provisioner chạy hai lệnh trên EC2 instance sau khi nó đã được tạo: cài đặt Apache (httpd) và khởi động dịch vụ. Khối connection
cung cấp thông tin kết nối SSH để provisioner có thể truy cập vào instance.
Ví dụ 2:
Giả sử bạn muốn tạo ra một tài nguyên EC2 instance và cài đặt web server Nginx trên đó. Bạn có thể sử dụng Provisioner để thực hiện các bước sau:
- Cài đặt Nginx:
resource "null_resource" "install_nginx" {
provisioner "shell" {
command = <<-EOF
sudo apt-get update && sudo apt-get install -y nginx
EOF
}
}
- Cấu hình Nginx:
resource "null_resource" "configure_nginx" {
provisioner "file" {
source = "nginx.conf"
destination = "/etc/nginx/nginx.conf"
}
}
- Khởi động lại Nginx:
resource "null_resource" "restart_nginx" {
provisioner "shell" {
command = <<-EOF
sudo systemctl restart nginx
EOF
}
}
Giải thích:
resource "null_resource"
là một tài nguyên "null" được sử dụng để thực thi các provisioner.provisioner "shell"
khai báo một provisioner shell.command
xác định lệnh shell sẽ được thực thi.provisioner "file"
khai báo một provisioner file.source
xác định đường dẫn đến tệp nguồn.destination
xác định đường dẫn đến vị trí đích.
Lưu ý:
- Provisioner nên được sử dụng một cách cẩn thận, vì chúng có thể gây ra lỗi nếu không được thực thi chính xác.
Tham khảo:
- Terraform Provisioners: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
Terraform variables custom validation rules
Trong Terraform, bạn có thể sử dụng Custom Validation Rules để xác minh và kiểm tra tính hợp lệ của biến (variables) trước khi sử dụng chúng để tạo hoặc cập nhật các tài nguyên. Điều này giúp đảm bảo rằng giá trị của biến phù hợp với các quy tắc và ràng buộc mà bạn đã định nghĩa.
Để định nghĩa Custom Validation Rules cho một biến, bạn sử dụng khối validation
trong việc khai báo biến đó. Cú pháp như sau:
variable "instance_type" {
type = string
description = "EC2 instance type"
validation {
condition = length(var.instance_type) > 4 && substr(var.instance_type, 0, 2) == "t2"
error_message = "The instance type must be a valid T2 type, e.g. t2.micro."
}
}
Trong ví dụ trên:
condition
là một biểu thức bool để kiểm tra tính hợp lệ của giá trị biến. Nếu biểu thức này trả vềtrue
, giá trị biến được coi là hợp lệ, ngược lại sẽ trả về lỗi.error_message
là thông báo lỗi sẽ hiển thị nếu giá trị biến không hợp lệ.
Một số ví dụ thực tế về Custom Validation Rules:
- Kiểm tra định dạng chuỗi:
variable "domain_name" {
type = string
description = "Domain name for the website"
validation {
condition = can(regex("^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$", var.domain_name))
error_message = "Invalid domain name format. Domain name must be a valid URL."
}
}
- Kiểm tra giá trị trong một danh sách cho phép:
variable "env" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.env)
error_message = "Environment must be one of: dev, staging, prod."
}
}
- Kiểm tra số lượng phần tử trong một danh sách:
variable "cidrs" {
type = list(string)
description = "List of CIDR blocks"
validation {
condition = length(var.cidrs) > 0
error_message = "At least one CIDR block must be provided."
}
}
Custom Validation Rules giúp bạn kiểm soát chặt chẽ hơn đối với các giá trị đầu vào, đảm bảo rằng chúng phù hợp với các quy tắc và ràng buộc của ứng dụng hoặc hệ thống của bạn. Điều này giúp tránh các lỗi không mong muốn và đảm bảo tính nhất quán trong quá trình triển khai.
Xem thêm: https://developer.hashicorp.com/terraform/language/values/variables#custom-validation-rules
Terraform modules
Terraform module là một tính năng mạnh mẽ giúp bạn tổ chức và tái sử dụng mã Terraform một cách hiệu quả. Nó cho phép bạn đóng gói các cấu hình cơ sở hạ tầng phức tạp thành các đơn vị riêng biệt, dễ quản lý và có thể sử dụng lại nhiều lần trong các dự án khác nhau.
Các thành phần của Terraform module:
Thư mục module: Chứa tất cả các tệp mã Terraform (.tf) và các thư mục con cần thiết cho module.
Tệp
main.tf
: Phiên bản chính của module, nơi bạn định nghĩa các tài nguyên cơ sở hạ tầng và logic cấu hình.Tệp
variables.tf
: Liệt kê các biến đầu vào mà module cần để hoạt động.Tệp
outputs.tf
: Xác định các giá trị đầu ra mà module cung cấp cho các module khác sử dụng.Ngoài các file trên, tùy vào mục đích, có thể có thêm các file khác như locals.tf, versions.tf, providers.tf,....
Cách tạo Terraform module:
Tạo thư mục cho module của bạn.
Tạo tệp
main.tf
và viết mã Terraform cho các tài nguyên cơ sở hạ tầng bạn muốn đóng gói.Tạo tệp
variables.tf
và liệt kê các biến đầu vào cần thiết cho module.Tạo tệp
outputs.tf
và xác định các giá trị đầu ra mà module sẽ cung cấp.Tạo thêm các file khác như locals.tf, versions.tf, providers.tf,.... nếu cần thiết
Cách sử dụng Terraform module:
Thêm module vào tệp
main.tf
của dự án chính.Cung cấp các giá trị cho các biến đầu vào của module.
Sử dụng các giá trị đầu ra của module trong code Terraform của dự án chính.
Tham khảo giá trị của Terraform module khác:
Để sử dụng giá trị đầu ra của một module khác, bạn có thể sử dụng cú pháp module.<tên_module>.<tên_đầu_ra>
. Ví dụ: module.vpc.subnet_ids
.
Ví dụ thực tế:
Giả sử bạn muốn tạo một module Terraform để quản lý VPC (Virtual Private Cloud) trên AWS. Module này sẽ có các biến đầu vào cho CIDR block và vùng khả dụng, và sẽ cung cấp các giá trị đầu ra cho ID VPC và ID subnet.
Thư mục module: vpc-module
Tệp main.tf:
resource "aws_vpc" "vpc" {
cidr_block = var.cidr_block
subnet_ids = [var.subnet_ids[0], var.subnet_ids[1]]
}
resource "aws_subnet" "public_subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_subnet_cidr_block
availability_zone = var.public_subnet_availability_zone
}
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.private_subnet_cidr_block
availability_zone = var.private_subnet_availability_zone
}
Tệp variables.tf:
variable "cidr_block" {
type = string
default = "10.0.0.0/16"
}
variable "subnet_ids" {
type = list(string)
}
variable "public_subnet_cidr_block" {
type = string
default = "10.0.0.0/24"
}
variable "public_subnet_availability_zone" {
type = string
default = "us-east-1a"
}
variable "private_subnet_cidr_block" {
type = string
default = "10.0.1.0/24"
}
variable "private_subnet_availability_zone" {
type = string
default = "us-east-1b"
}
Tệp outputs.tf:
output "vpc_id" {
value = aws_vpc.vpc.id
}
output "subnet_ids" {
value = [aws_subnet.public_subnet.id, aws_subnet.private_subnet.id]
}
Để sử dụng module này trong dự án chính của bạn, bạn có thể thực hiện như sau:
module "vpc" {
source = "./vpc-module"
cidr_block = "192.168.0.0/16"
subnet_ids = ["subnet-12345678", "subnet-87654321"]
}
Sử dụng giá trị đầu ra của modulevpc
:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = module.vpc.subnet_ids[0]
tags = {
Name = "web-server"
}
}
Trong ví dụ này, module vpc
được sử dụng để tạo VPC và subnet trong AWS. Dự án chính sử dụng các giá trị đầu ra của module vpc
(vpc_id
và subnet_ids
) để tạo instance EC2 trong subnet public.
Cách tạo Terraform module dựa vào terraform docs
Để tạo được terraform module, thì chắc chắn đầu tiên bạn phải có hiểu biết căn bản về các resource sẽ được tạo ra. Sau đó, bạn cần refer vào docs của Terraform để có thể viết được các module. Giả sử, mình tạo terraform module tạo ra các AWS EC2 resources.
Mình sẽ tìm kiếm EC2 trên docs Terraform AWS Provider:
Tại đây tôi sẽ có thể biết được tất cả các resource mà AWS, cũng sẽ là các resource mà tôi sẽ viết trong Terraform module.
Đối với từng resource, tôi sẽ định nghĩa variables được phép sử dụng dựa vào Argument Reference. Ví dụ, với resource EIP:
Vậy tức là tôi sẽ có thể định nghĩa các variables: address, associate_with_private_ip,.... trong module của tôi.
Để định nghĩa các outputs cho module, thì refer tới phần Attribute Reference. Đây là các outputs sẽ xuất hiện trong file tfstate để ta có thể sử dụng.
Ví dụ đối với EIP, thì ta sẽ có các outputs sau:
Một số người mới thích gõ <tên service> + terraform module để xem các modules có sẵn trên mạng rồi viết theo. Việc này OK, nhưng song song với đó bạn sẽ cần kiểm tra lại trong docs để có được thông tin cập nhật nhất về các resources mà Terraform support, đảm bảo module của mình hữu dụng nhất.
Cách refer attributes của module khác
Trong Terraform, việc refer (tham chiếu) đến output của một module khác là một kỹ thuật rất hữu ích, cho phép bạn tái sử dụng và chia sẻ dữ liệu giữa các module. Điều này giúp duy trì tính mô-đun hóa và tái sử dụng của mã nguồn Terraform.
Để refer đến output của một module khác, bạn sử dụng cú pháp sau:
module.<module_name>.<output_name>
Trong đó:
module
là từ khóa để chỉ rằng bạn đang tham chiếu đến một module.<module_name>
là tên của module mà bạn muốn tham chiếu đến output.<output_name>
là tên của output mà bạn muốn lấy giá trị.
Ví dụ, giả sử bạn có một module vpc
với output như sau:
# vpc/outputs.tf
output "vpc_id" {
value = aws_vpc.main.id
description = "The ID of the VPC"
}
Và trong module khác, ví dụ app
, bạn muốn sử dụng vpc_id
này để tạo một security group:
# app/main.tf
module "vpc" {
source = "../vpc"
...
}
resource "aws_security_group" "app" {
name_prefix = "app-"
vpc_id = module.vpc.vpc_id # Refer tới output của module vpc
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Trong ví dụ này, module.vpc.vpc_id
đang tham chiệu đến output vpc_id
của module vpc
. Bằng cách này, bạn có thể tái sử dụng và chia sẻ dữ liệu giữa các module khác nhau một cách dễ dàng.
Lưu ý rằng để có thể refer đến output của một module khác, module đó phải được khai báo và khởi tạo trước khi sử dụng output của nó.
Terraform console và ứng dụng terraform console
Câu lệnh terraform console
cung cấp một interactive console để đánh giá các biểu thức Terraform.
Lưu ý: Cần chạy lệnh terraform init
trước.
Vì có thể sử dụng đánh giá các biểu thức, nên bạn có thể tận dụng để debug, cũng như đơn giản hóa việc sử dụng các biểu thức, các built-in functions khi viết code.
Ví dụ tôi có đoạn code như sau:
locals {
create = var.create
archive_filename = try(data.external.archive_prepare[0].result.filename, null)
archive_filename_string = local.archive_filename != null ? local.archive_filename : ""
archive_was_missing = try(data.external.archive_prepare[0].result.was_missing, false)
# Use a generated filename to determine when the source code has changed.
# filename - to get package from local
filename = var.local_existing_package != null ? var.local_existing_package : (var.store_on_s3 ? null : local.archive_filename)
was_missing = var.local_existing_package != null ? !fileexists(var.local_existing_package) : local.archive_was_missing
# s3_* - to get package from S3
s3_bucket = var.s3_existing_package != null ? try(var.s3_existing_package.bucket, null) : (var.store_on_s3 ? var.s3_bucket : null)
s3_key = var.s3_existing_package != null ? try(var.s3_existing_package.key, null) : (var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null)
s3_object_version = var.s3_existing_package != null ? try(var.s3_existing_package.version_id, null) : (var.store_on_s3 ? try(aws_s3_object.lambda_package[0].version_id, null) : null)
}
Đoạn code trên sử dụng khá nhiều logic, và mới tiếp cận Terraform có thể sẽ thấy khá khó hiểu, vậy chúng ta có thể tận dụng terraform console để dễ hiểu hơn. Ví dụ dòng code sau:
s3_key = var.s3_existing_package != null ? try(var.s3_existing_package.key, null) : (var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null)
Thử đánh giá var.s3_existing_package:
Ta thấy rằng var.s3_existing_package là null, vậy tức là chúng ta sẽ đánh giá tiếp theo logic sau:
(var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null)
Vậy tức là chúng ta biết được rằng, với các khai báo hiện tại, thì source của Lambda sẽ được lưu trên S3, trong prefix lambda-builds, và có key như sau:
format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", ""))
Từ 1 dòng code nhiều logic khó đọc, đã trở nên đơn giản hơn rất nhiều. Bạn cũng có thể sử dụng terraform console để nháp các biểu thức xử lý, các điều kiện, xem trước các behavior của terraform functions để có thể viết code dễ hơn, hiệu quả hơn. Ngoài ra, khi cần debug logic code, Terraform console cũng là một công cụ hữu ích.
Hướng dẫn sử dụng Terraform depends_on
depends_on
là một thuộc tính trong Terraform giúp bạn xác định thứ tự tạo tài nguyên. Nó đảm bảo rằng một tài nguyên chỉ được tạo sau khi tất cả các tài nguyên mà nó phụ thuộc đã được tạo thành công.
Ví dụ thực tế:
Giả sử bạn muốn tạo một instance EC2 trên AWS bằng Terraform. Instance EC2 này cần được kết nối với một subnet và VPC hiện có.
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = aws_subnet.example.id
}
resource "aws_subnet" "example" {
vpc_id = aws_vpc.example.id
cidr_block = "10.0.0.0/16"
tags = {
Name = "example-subnet"
}
}
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "example-vpc"
}
}
Trong ví dụ này, instance EC2 example
phụ thuộc vào subnet example
. Điều này có nghĩa là instance EC2 sẽ không được tạo cho đến khi subnet example
được tạo thành công.
Cách sử dụngdepends_on
:
Bạn có thể sử dụng depends_on
cho bất kỳ tài nguyên Terraform nào. Để sử dụng depends_on
, bạn cần chỉ định ID của các tài nguyên mà tài nguyên hiện tại phụ thuộc vào.
resource "resource_type" "resource_name" {
# ... các thuộc tính khác ...
depends_on = [
aws_subnet.example.id,
]
}
Terraform functions
Terraform functions là các khối mã được xây dựng sẵn trong Terraform, giúp thực hiện các tác vụ logic và thao tác dữ liệu trong cấu hình Terraform.
Lợi ích sử dụng Terraform functions:
Tăng tính linh hoạt: Giúp bạn viết mã Terraform linh hoạt và có thể tái sử dụng được.
Giảm độ phức tạp: Giúp đơn giản hóa mã Terraform, làm cho nó dễ đọc và dễ quản lý hơn.
Tăng hiệu quả: Giúp bạn tự động hóa các tác vụ và tiết kiệm thời gian khi viết mã Terraform.
Các loại Terraform functions:
Terraform cung cấp nhiều loại functions khác nhau, được phân loại thành các nhóm sau:
String Functions: Thực hiện các thao tác trên chuỗi, ví dụ như so sánh, cắt xén, định dạng, v.v.
Collection Functions: Thực hiện các thao tác trên các tập hợp dữ liệu, ví dụ như lọc, sắp xếp, truy xuất phần tử, v.v.
Encoding Functions: Mã hóa và giải mã dữ liệu.
Filesystem Functions: Tương tác với hệ thống tệp, ví dụ như tạo đường dẫn, đọc tệp, v.v.
Numeric Functions: Thực hiện các thao tác toán học trên số.
Date and Time Functions: Thao tác với ngày giờ.
Hash and Crypto Functions: Tạo hash và mã hóa dữ liệu.
IP Network Functions: Thực hiện các thao tác trên mạng IP.
Ví dụ sử dụng:
Giả sử bạn muốn tạo một biến Terraform lấy ngày tháng hiện tại. Bạn có thể sử dụng function timestamp
như sau:
Terraform
variable "current_date" {
type = string
default = timestamp()
}
Biến current_date
sẽ chứa giá trị ngày tháng hiện tại khi Terraform khởi tạo.
Ngoài ra, bạn có thể tham khảo thêm tài liệu chính thức của Terraform về functions:
Một số functions thường thấy sử dụng trong các Terraform module:
format, formatlist, join, split, alltrue, anytrue, coalesce, compact, concat, contains, element, flatten, index, keys, length, lookup, merge, one, setproduct, values, file, fileexists, filebase64, templatefile, filemd5, cidrsubnet, sensitive, tolist, tomap, toset, try, type
Bạn nên tìm hiểu, và thực hiện làm demo các function trên để hiểu rõ và có thể ứng dụng trong thực tế.
Ví dụ ứng dụng thực tế của formatlist():
data "aws_iam_policy_document" "lambda_function_policy_s3" {
count = local.create_lambda_role && var.attach_s3_policy ? 1 : 0
statement {
actions = [
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketAcl",
"s3:ListBucketVersions",
"s3:ListBucket",
"s3:GetBucketLocation",
]
resources = local.bucket_arns
}
statement {
actions = [
"s3:GetObjectAcl",
"s3:GetObject",
"s3:GetObjectVersionTagging",
"s3:GetObjectVersionAcl",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:DeleteObjectTagging",
"s3:PutObject",
"s3:DeleteObjectVersion",
"s3:PutObjectVersionAcl",
"s3:PutObjectVersionTagging",
"s3:PutObjectTagging",
"s3:DeleteObjectVersionTagging",
"s3:DeleteObject",
"s3:PutObjectAcl"
]
resources = local.bucket_content_arns
}
}
bucket_arns = formatlist("arn:aws:s3:::%s", var.allowed_s3_bucket_names)
bucket_content_arns = formatlist("arn:aws:s3:::%s/*", var.allowed_s3_bucket_names)
variable "allowed_s3_bucket_names" {
type = list(string)
description = "List of bucket that lambda function allowed to access"
default = []
}
terraform.tfvars
allowed_s3_bucket_names = ["yentth25-test-existing-source-bucket"]
Bằng cách trên, bạn có thể thiết lập điều kiện cho AWS IAM policy theo S3 bucket name mà người dùng nhập vào trong tình huống:
Giả sử bạn cần tạo template có chứa các policy liên quan đến S3, nhưng lại không biết trước được S3 bucket tên là gì. Mà IAM policy thì cần chỉ định đến arn của resource, nếu để dạng:
arn:aws:s3:::*
thì không ổn chút nào, bạn muốn dạng:
arn:aws:s3:::<tên bucket>
arn:aws:s3:::<tên bucket>/*
2.Terragrunt
Giới thiệu về Terragrunt
Terragrunt là một công cụ mã nguồn mở giúp quản lý các mô-đun Terraform một cách hiệu quả. Nó cung cấp một lớp trừu tượng trên Terraform CLI, giúp đơn giản hóa việc tổ chức, cấu hình và triển khai các mô-đun Terraform phức tạp.
Lợi ích sử dụng Terragrunt:
Tổ chức: Giúp bạn tổ chức các mô-đun Terraform thành một cấu trúc thư mục hợp lý, dễ quản lý.
Tái sử dụng: Cho phép bạn tái sử dụng các mô-đun Terraform đã được thử nghiệm và kiểm tra trong nhiều dự án khác nhau.
Tính linh hoạt: Giúp bạn dễ dàng tùy chỉnh và mở rộng các mô-đun Terraform.
Tự động hóa: Cung cấp các tính năng tự động hóa giúp tiết kiệm thời gian và công sức khi triển khai các mô-đun Terraform.
Ví dụ sử dụng:
Giả sử bạn có một dự án Terraform phức tạp bao gồm nhiều mô-đun cho các thành phần cơ sở hạ tầng khác nhau như VPC, subnet, instance EC2, v.v. Terragrunt có thể giúp bạn tổ chức các mô-đun này thành một cấu trúc thư mục hợp lý, dễ quản lý. Bạn có thể sử dụng Terragrunt để cấu hình các biến và đầu ra cho từng mô-đun, cũng như tự động hóa việc triển khai các mô-đun này.
Cấu trúc thư mục tiêu chuẩn của terragrunt
Giả sử 1 thư mục có cấu trúc như sau:
Ta sẽ chú ý tới những files và folder chính sau:
terragrunt.hcl ở ngoài cùng: Lưu trữ các configure global parameter, các configure liên quan việc tự động lưu trữ tfstate ở S3 bucket
account.hcl: Lưu trữ thông tin aws account
region.hcl: Lưu trữ thông tin liên quan aws region
env.hcl và sub_env.hcl: Lưu trữ các biến môi trường
Thư mục _components: Hãy tưởng tượng các file .hcl trong thư mục này lưu trữ các thông tin biến global trong các code thông thường vậy. Những giá trị được define trên thư mục này, là những giá trị chung nhất, có thể dùng đi dùng lại cho nhiều môi trường dev, uat, prod,...
Các file terragrunt.hcl trong thư mục con của region ap-southeast-1: Lưu trữ các khai báo specific cho từng môi trường. Có thể tưởng tượng giống như local variables. Nếu khai báo biến trùng với giá trị của biến trên file .hcl ở thư mục components, thì terragrunt sẽ ưu tiên giá trị được khai báo ở file này.
Lưu ý: Nếu bạn sử dụng S3 để lưu trữ state infra provision bằng Terragrunt, thì state sẽ được lưu trữ tương tự như thư mục terragrunt, nếu bạn muốn thay đổi, chỉnh sửa thư mục thì cần lên S3 move state sang thư mục tương ứng, nếu không terragrunt sẽ không tìm thấy resource quản lý trong state, và sẽ hiểu là cần tạo thêm resource
Khai báo biến trong terragrunt
Mỗi một file terragrunt.hcl thường sẽ tạo ra 1 hoặc nhiều resource phục vụ 1 nhiệm vụ nào đó. Bạncó thể xem được các giá trị có thể khai báo cho resource của mình ở file variable.tf
trong các terraform module.
Những giá trị chung, sử dụng đi sử dụng lại nhiều môi trường được thì sẽ thường khai ở các file trong thư mục _components
, specific từng môi trường thì ở file terragrunt.hcl
Trường hợp muốn overwrite lại giá trị khai báo ở file trong thư mục _components
, thì cũng có thể khai báo lại biến muốn overwrite + giá trị mới ở file terragrunt.hcl
, terragrunt sẽ ưu tiên giá trị trong file terragrunt.hcl
trong trường hợp có các giá trị khác nhau cho cùng 1 biến.
Chẳng hạn:
Trong file ec2.hcl
trong thư mục _components
đang khai báo:
instance_type = "r6i.2xlarge"
Nhưng ở môi trường non prod, muốn sử dụng 1 type khác: t3.medium
thì ở file terragrunt.hcl provision ra ec2 trong thư mục nonprod
khai lại như sau:
instance_type = "t3.medium"
Làm như này thì instance được tạo ra ở non prod sẽ là t3.medium, không phải r6i.2xlarge
nữa.
Câu hỏi: Tôi đã thấy ở terraform module có giá trị default cho biến, vậy nếu tôi muốn thay đổi giá trị định nghĩa default thì tôi có cần sửa terraform module không?
Trả lời: Không cần sửa terraform module!
Giá trị default của biến trong terraform chỉ là để cho mục đích khi người dùng không khai báo giá trị biến đó, thì nó sẽ lấy giá trị default thôi.
Xem thêm tại: https://developer.hashicorp.com/terraform/language/values/variables#default-values
Do đó, nếu muốn overwrite giá trị default:
Trường hợp sử dụng chỉ Terraform: Bạn khai báo lại giá trị biến đó ở file .tfvars, là Terraform sẽ nhận giá trị khai ở trong .tfvars, không nhận default nữa
Trường hợp sử dụng wrapper của Terraform là Terragrunt: Bạn khai báo lại giá trị biến đó ở file .hcl của Terragrunt, là Terragrunt sẽ không nhận default nữa
Trường hợp muốn lấy giá trị outputs của module khác
Khai báo dependency trong file .hcl gọi đến module khác như sau:
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "temporary-dummy-id"
}
}
Các mock_outputs là các output mock, không phải output thực tế.
\=> câu hỏi đặt ra là làm sao biết là sẽ dùng topic_arn, id, hay cái gì đó tương tự như vậy? => Trả lời: xem file outputs.tf
của repo terraform.
Xem thêm về terragrunt: https://terragrunt.gruntwork.io/docs/
Một số trường hợp dù không lấy outputs, nhưng cũng có thể định nghĩa dependency, để báo hiệu với terragrunt rằng cần phải tạo module tiên quyết trước. Hành động này tương tự như hành động ta khai báo depends_on trong terraform
Sử dụng các lệnh Terraform với Terragrunt có được không?
Hầu hết mọi câu lệnh của Terraform như terraform plan
, terraform apply
, terraform destroy
, terraform output
, terraform force-unlock
, terraform import
,... đều có thể sử dụng với Terragrunt. Bạn chỉ cần thay terraform
thành terragrunt
là được. Giả sử với lệnh để unlock state ở terraform sẽ là:
terraform force-unlock LOCK_ID
thì dùng với Terragrunt sẽ là:
terragrunt force-unlock LOCK_ID
3.Một số plugins
terraform-docs
Link: https://terraform-docs.io/
Sử dụng: Generate terraform docs (các file readme)
Checkov
Checkov quét các cấu hình cơ sở hạ tầng đám mây để tìm các lỗi cấu hình trước khi chúng được triển khai. Checkov sử dụng một giao diện dòng lệnh chung để quản lý và phân tích kết quả quét Cơ sở hạ tầng dưới dạng Mã (IaC) trên nhiều nền tảng như Terraform, CloudFormation, Kubernetes, Helm, ARM Templates và Serverless framework.
Infracost
Công cụ ước tính giá hạ tầng cloud trước khi deploy
\=======
Bạn có thể tham khảo video hướng dẫn của mình tại đây: https://youtu.be/d8AvyyFQUvo