Giới thiệu
Hiện nay việc định nghĩa và xây dựng infrastructure trên các cloud platform sử dụng ngôn ngữ IaC (Infrastructure as Code) như là Terraform đã quá quen thuộc với mọi người. Gần đây, mình có cơ hội được sử dụng ngôn ngữ này trong dự án thực tế, nên muốn chia sẻ những điều mình đã tìm hiểu và rút ra được. Bài viết sẽ không nhắc lại những kiến thức cơ bản về Terraform, mà sẽ nhắm tới giới thiệu cấu trúc folder DRY cho ngôn ngữ này trong thực tế. (Cụ thể cloud platform trong bài viết sẽ là AWS)
Folder structure
Cụ thể cấu trúc folder mình muốn giới thiệu sẽ như dưới đây.
+- pj-root/ |---- terraform-version | +---- shared/ | +---- main.tf | +---- provider.tf | +---- variables.tf | +---- modules/ | | +---- alb/ | | +----- alb.tf | | +----- listener.tf | | +----- target.tf | | +----- outputs.tf | | +----- variables.tf | | +---- cloudfront/ | | +---- ecs/ | | +---- iam/ | | +---- network/ | | +---- oai/ | | +---- rds/ | | +---- route53/ | | +---- s3/ | | +---- security_group/ +---- env/ | +---- dev/ | | +----- backend.tf | | +----- locals.tf | | +----- terraform.tfvars | | +----- main.tf Symbolic link to /shared/main.tf | | +----- provider.tf Symbolic link to /shared/provider.tf | | +----- variables.tf Symbolic link to /shared/variables.tf | +---- stg/ | +---- prd/ +---- templates/ | +---- ecs/ | +----- sample.json.tftpl +---- backend/ +---- terraform_backend.yaml
Giải thích
-
shared/
- Định nghĩa các files, modules, variables có thể sử dụng chung ở các môi trường.
- Các file
main.tf
,provider.tf
,variables.tf
sẽ được tạo Symbolic Link tương ứng ở từng môi trường (Dev/Stg/Prd). - Thư mục
modules/
định nghĩa các modules cho từng service.
-
env/
- Quản lý các file tf dành riêng cho từng môi trường (Dev/Stg/Prd).
- Các file
main.tf
,provider.tf
,variables.tf
sẽ được link tới các file tương ứng trong thư mục shared bằng Symbolic Link. - Với từng môi trường chỉ cần chỉnh sửa các file
backend.tf
,locals.tf
,terrform.tfvars
.
-
templates/
- Lưu các config files và templates có thể tái sử dụng như là ECS Task definition, Lambda source code, CloudWatch Agent, v.v..
-
backend/
- Lưu CloudFormation template để tạo S3 Bucket và DynamoDB table (nhằm mục đích lưu lại lock state của Terraform Backend)
-
terraform-version
- File lưu lại version terraform mà dự án sử dụng
Symbolic Link
- Một điểm cần chú ý trong cấu trúc này là việc sử dụng Symbolic Link
- Cách tạo Symbolic Link có thể tham khảo ở đây: Symbolic Link
- Trong các folder môi trường dev, stg, prd, ta sẽ không copy paste từng file
main.tf
,provider.tf
,variables.tf
, mà sẽ link tới các file tương ứng trong thư mụcshared/
. - Với cách liên kết này ta có thể đảm bảo quản lý file cho các môi trường một cách đồng bộ và thống nhất.
- Một điều cần chú ý là khi khai báo source cho các module thì ta cần phải lấy đường dẫn dựa trên thư mục
shared/
# Right source path module "sample" { source = "../../shared/modules/sample" sample = local.sample
}
# Wrong source path module "sample" { source = "./modules/sample" sample = local.sample
}
Quản lý tfstate
- Terraform có support quản lý tfstate remote bằng S3 bucket và DynamoDB table. Đây là 1 tính năng rất hữu ích để đảm bảo state được lưu đồng bộ trên AWS, đặc biệt là khi làm việc trong 1 team đông người.
- Để sử dụng được chức năng này ta cần tạo trước 1 S3 Bucket và 1 DynamoDB table nhằm lưu lại state dưới dạng key và hỗ trợ state locking và consistency checking
- Việc tạo S3/DynamoDB này có thể thông qua AWS Console hoặc AWS CLI, tuy nhiên với mục đích sử dụng IaC tối đa có thể, nên trong cấu trúc này đang sử dụng CloudFormation template lưu trong thư mục Backend
- Sau khi tạo xong ta cần khai báo tên bucket và tên table trong file
backend.tf
tương ứng cho từng môi trường - File sample
backend.tf
terraform { backend "s3" { bucket = "sample-bucket" key = "dev.tfstate" dynamodb_table = "sample-table" region = "ap-northeast-1" } }
Sử dụng template
- Ta có thể định nghĩa ra template cho các config file thường lặp đi lặp lại (Ví dụ như ECS task definition), trong đó định nghĩa các variable cho những phần có thể thay đổi
- Sau đó, ở các module sẽ sử dụng function templatefile và truyền vào value các variable này.
VD cách sử dụng template với trường hợp ECS
templates/ecs/sample_task_definition.json.tftpl
[ { "name": "sample_task", "image": "${repository_url}:latest", "cpu": 0, "portMappings": [ { "containerPort": 80, "hostPort": 80, "protocol": "tcp" } ], "essential": true, "environment": [], "mountPoints": [], "volumesFrom": [], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/sample-${environment}-task", "awslogs-region": "${region}", "awslogs-stream-prefix": "ecs" } } }
]
shared/modules/ecs/task_definition.tf
resource "aws_ecs_task_definition" "sample" { family = "sample-${var.environment}-task" container_definitions = templatefile("../../templates/ecs/sample_task_definition.json.tftpl", { region = var.region, environment = var.environment repository_url = aws_ecr_repository.sample.repository_url }) ...
}
DRY syntax
Khi định nghĩa các service trong từng module, ta cần chú ý tận dụng các Meta-Argument và Build-in Function mà Terraform cung cấp để không cần phải lặp đi lặp lại code để định nghĩa cho các instance/resource tương tự nhau. (Đảm bảo tính DRY)
- Functions
- Meta-argument : count, for_each, depends_on, v.v..
Một vài command hữu dụng
Ngoài các command cơ bản như init, plan, apply, mình muốn giới thiệu 1 vài command mình thấy khá hữu dụng khi làm việc với Terraform
1/ Validate syntax in all .tf files
terraform validate
2/ Force unlock Terraform state lock
terraform force-unlock [options] LOCK_ID
3/ List resources within a Terraform state
terraform state list [options] [address...]
4/ Destroy specific resources
terraform destroy -target RESOURCE_TYPE.NAME
Lời kết
Trên đây là cấu trúc folder DRY cho Terraform mà mình đã tìm hiểu được, ngoài ra cũng có một số thông tin mình rút ra được trong quá trình áp dụng cấu trúc này vào dự án thực tế. Mong bài viết sẽ giúp ích cho mọi người trong công việc hoặc đơn giản là trong quá trình tìm hiểu về Terraform.