Infrastructure as Code (IaC): Dùng AI viết Terraform/CDK cho hệ thống phức tạp
7/19/2026 · 14p đọc
title: "Infrastructure as Code (IaC): Dùng AI viết Terraform/CDK cho hệ thống phức tạp"
series: "AI-Native Solutions Architect: Từ Coder đến Kiến trúc sư AI"
season: "Season 2 — AI-Augmented Architecture & System Design"
order: 17
audience: "Software Engineer hướng tới Solutions Architect"
reading_time: "14 phút"
tags: ["infrastructure-as-code", "terraform", "cdk", "aws", "ecs", "devops", "ai-native", "terraform-plan"]
Infrastructure as Code (IaC): Dùng AI viết Terraform/CDK cho hệ thống phức tạp
Một hệ thống có 2 môi trường — staging và production — và một người vừa thêm một service mới cần chạy trên ECS Fargate. Cách nhanh nhất là copy nguyên khối .tf của service cũ, đổi vài giá trị (tên, port, CPU/memory), rồi paste vào cả hai môi trường. Sáu tháng sau, staging có desired_count = 1, production vô tình cũng có desired_count = 1 vì lúc copy quên đổi — và không ai phát hiện ra cho đến khi production sập vì traffic tăng đột biến mà không có phiên bản thứ hai chạy song song.
Đây không phải lỗi logic nghiệp vụ. Đây là lỗi lệch cấu hình giữa các môi trường (configuration drift) — loại lỗi đặc trưng của hạ tầng, không phải của code ứng dụng. Nó xảy ra vì Terraform thủ công cho nhiều môi trường, nhiều module tái sử dụng đòi hỏi kỷ luật rất cao: mỗi thay đổi phải được nhân bản chính xác qua từng environment, mỗi biến hoá (variable) phải đúng ngữ cảnh, và một dấu ngoặc sai chỗ trong file .tf có thể không gây lỗi cú pháp nhưng vẫn tạo ra một terraform plan sai lệch nghiêm trọng so với ý định ban đầu.
Viết Terraform cho một service đơn giản không khó. Cái khó là khi hệ thống có hàng chục module, mỗi module lại có biến thể theo từng environment, và một Solutions Architect phải đảm bảo module nào cũng nhất quán về convention (naming, tagging, review process) mà không tốn hàng giờ đọc lại từng dòng HCL. Đây đúng là chỗ AI hữu ích — sinh code lặp lại theo pattern chuẩn nhanh hơn con người rất nhiều. Nhưng bài này cũng nói thẳng về ranh giới: AI sinh Terraform không có nghĩa là được phép bỏ qua bước review plan trước khi apply — đặc biệt khi thay đổi đó có khả năng phá hủy tài nguyên đang chạy.
Vấn đề
Ba lý do khiến việc viết Terraform thủ công cho hạ tầng phức tạp tốn thời gian và dễ sai:
1. Lặp lại giữa các environment nhưng không được phép giống hệt nhau. Staging và production dùng chung một kiến trúc (cùng loại resource: VPC, ECS cluster, RDS, ALB) nhưng khác nhau về giá trị: số lượng instance, kích thước, retention policy, alarm threshold. Viết tay dễ dẫn đến hai kịch bản tệ như nhau — hoặc copy-paste sai giá trị (staging config lẫn vào production), hoặc hai environment dần dần phân kỳ cấu trúc vì mỗi lần sửa chỉ sửa một nơi.
2. Module tái sử dụng đòi hỏi thiết kế interface (input/output variables) cẩn thận — thứ dễ bị bỏ qua khi vội. Một module Terraform tốt phải tách rõ cái gì cố định (kiến trúc, convention) và cái gì biến thiên (tên, size, tag) qua variables.tf. Khi áp lực deadline, người viết thường hardcode giá trị thẳng vào module thay vì tham số hoá đúng — kết quả là module "tái sử dụng được" trên danh nghĩa nhưng thực tế phải copy sửa tay mỗi lần dùng ở chỗ khác.
3. Sai sót trong Terraform không lộ ra ở compile-time — nó lộ ra ở apply-time, trên hạ tầng thật. Không giống một lỗi TypeScript báo ngay khi build, một file .tf sai logic (ví dụ đặt sai lifecycle block, quên prevent_destroy, hoặc tính sai tham chiếu giữa resource) vẫn "valid" về cú pháp và vẫn chạy terraform plan được — nhưng plan output có thể cho thấy Terraform định destroy và recreate một resource mà lẽ ra chỉ cần update, hoặc tệ hơn, không ai đọc kỹ output đó trước khi gõ apply.
Điểm chung: đây đều là lỗi phát sinh từ khối lượng công việc lặp lại và thiếu review kỷ luật — chính xác là loại việc AI sinh code nhanh hơn con người, nhưng cũng chính xác là loại việc mà tốc độ sinh code nhanh hơn có thể khuyến khích bỏ qua bước review quan trọng nhất.
Kỹ thuật cốt lõi
Nguyên tắc cốt lõi: dùng AI để sinh phần lặp lại theo pattern (module structure, biến hoá theo environment, tagging convention), nhưng giữ nguyên quy trình review con người ở đúng những bước có rủi ro phá hủy tài nguyên.
flowchart TD
A["Mô tả yêu cầu hạ tầng<br/>(loại resource, convention, biến theo env)"] --> B["AI sinh module Terraform<br/>(main.tf / variables.tf / outputs.tf)"]
B --> C["Con người review code<br/>(convention, tagging, security group...)"]
C --> D["terraform plan trên staging"]
D --> E{"Plan output có hợp lý?<br/>Có resource nào bị destroy<br/>ngoài dự kiến không?"}
E -- "Không hợp lý" --> F["Sửa lại module,<br/>quay lại review"]
F --> C
E -- "Hợp lý" --> G["terraform apply trên staging"]
G --> H["Xác nhận hoạt động đúng"]
H --> I["terraform plan trên production<br/>(review riêng, review kỹ hơn)"]
I --> J{"Plan production an toàn?"}
J -- "Không" --> F
J -- "An toàn" --> K["terraform apply trên production"]
style E fill:#c55,stroke:#333,stroke-width:1px
style J fill:#c55,stroke:#333,stroke-width:1px
Ba nguyên tắc khi áp dụng AI vào IaC ở mức module phức tạp:
AI sinh cấu trúc, con người xác nhận giá trị. Yêu cầu AI sinh một module với variables.tf tham số hoá đầy đủ (environment, CPU/memory, desired count, tags...) là việc AI làm rất tốt vì đây là pattern lặp lại có chuẩn rõ ràng. Nhưng giá trị cụ thể cho từng environment (staging chạy 1 task, production chạy 3 task với auto-scaling) phải do con người xác nhận dựa trên yêu cầu nghiệp vụ thật, không phải để AI tự đoán.
Mọi thay đổi đều đi qua terraform plan trước khi apply — không có ngoại lệ, kể cả khi code do AI sinh "nhìn có vẻ đúng". Plan output là nguồn sự thật duy nhất cho biết Terraform sẽ làm gì với hạ tầng thật — không phải đọc code. Nguyên tắc này đúng với cả code do con người viết lẫn code do AI sinh, nhưng quan trọng hơn với code AI sinh vì tốc độ sinh nhanh dễ tạo cảm giác chủ quan "chắc đúng rồi, apply luôn".
Không sửa state thủ công, không để AI đề xuất sửa state trực tiếp. Nếu một prompt yêu cầu AI "sửa terraform.tfstate để khớp với thực tế", đây là dấu hiệu vấn đề đang bị giải quyết sai hướng — hướng đúng là terraform import hoặc điều chỉnh lại code nguồn để plan tự hội tụ đúng, không phải chỉnh tay file state.
Thực hành
Module mẫu: ECS Fargate service tối giản
Dưới đây là một module Terraform minh hoạ loại code AI có thể hỗ trợ sinh ra cho một ECS Fargate service — tham số hoá đầy đủ để dùng lại cho cả staging và production.
modules/ecs-service/variables.tf:
variable "project" {
description = "Tên project, dùng cho naming convention"
type = string
}
variable "environment" {
description = "Environment: staging hoặc production"
type = string
validation {
condition = contains(["staging", "production"], var.environment)
error_message = "environment phải là 'staging' hoặc 'production'."
}
}
variable "service_name" {
description = "Tên service, vd: api, worker, notification-service"
type = string
}
variable "cluster_id" {
description = "ID của ECS cluster đã tồn tại"
type = string
}
variable "container_image" {
description = "Docker image đầy đủ, vd: <ecr_repo>:<tag>"
type = string
}
variable "container_port" {
description = "Port container lắng nghe"
type = number
default = 3000
}
variable "cpu" {
description = "CPU units cho task (Fargate: 256/512/1024/2048/4096)"
type = number
default = 256
}
variable "memory" {
description = "Memory (MB) cho task, phải khớp bảng CPU/memory hợp lệ của Fargate"
type = number
default = 512
}
variable "desired_count" {
description = "Số task chạy song song"
type = number
default = 1
}
variable "subnet_ids" {
description = "Danh sách subnet ID cho task (private subnet)"
type = list(string)
}
variable "security_group_ids" {
description = "Danh sách security group ID gắn cho task"
type = list(string)
}
variable "target_group_arn" {
description = "ARN của ALB target group, để trống nếu service không nhận traffic qua ALB"
type = string
default = null
}
variable "env_vars" {
description = "Map các biến môi trường truyền vào container"
type = map(string)
default = {}
}
variable "tags" {
description = "Tag bổ sung ngoài các tag bắt buộc"
type = map(string)
default = {}
}
modules/ecs-service/main.tf:
locals {
name_prefix = "${var.project}-${var.environment}-${var.service_name}"
required_tags = {
Environment = var.environment
Project = var.project
ManagedBy = "terraform"
Service = var.service_name
}
all_tags = merge(local.required_tags, var.tags)
}
resource "aws_cloudwatch_log_group" "this" {
name = "/ecs/${local.name_prefix}"
retention_in_days = var.environment == "production" ? 90 : 14
tags = local.all_tags
}
resource "aws_ecs_task_definition" "this" {
family = local.name_prefix
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.cpu
memory = var.memory
execution_role_arn = aws_iam_role.execution.arn
task_role_arn = aws_iam_role.task.arn
container_definitions = jsonencode([
{
name = var.service_name
image = var.container_image
essential = true
portMappings = [
{
containerPort = var.container_port
protocol = "tcp"
}
]
environment = [
for k, v in var.env_vars : { name = k, value = v }
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.this.name
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}
])
tags = local.all_tags
}
resource "aws_ecs_service" "this" {
name = local.name_prefix
cluster = var.cluster_id
task_definition = aws_ecs_task_definition.this.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = var.security_group_ids
assign_public_ip = false
}
dynamic "load_balancer" {
for_each = var.target_group_arn != null ? [1] : []
content {
target_group_arn = var.target_group_arn
container_name = var.service_name
container_port = var.container_port
}
}
# Tránh Terraform muốn recreate service mỗi khi desired_count
# bị thay đổi bởi auto-scaling ngoài Terraform (nếu có cấu hình scaling policy riêng).
lifecycle {
ignore_changes = [desired_count]
}
tags = local.all_tags
}
data "aws_region" "current" {}
modules/ecs-service/outputs.tf:
output "service_name" {
value = aws_ecs_service.this.name
}
output "task_definition_arn" {
value = aws_ecs_task_definition.this.arn
}
output "log_group_name" {
value = aws_cloudwatch_log_group.this.name
}
Cách gọi module này cho hai environment khác nhau — đây chính là phần "biến hoá theo environment" mà AI hỗ trợ tốt nhất, vì khối lượng lặp lại giữa environments/staging và environments/production là rất lớn:
environments/staging/main.tf:
module "notification_service" {
source = "../../modules/ecs-service"
project = "crm"
environment = "staging"
service_name = "notification-service"
cluster_id = data.terraform_remote_state.shared.outputs.ecs_cluster_id
container_image = "123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/crm-notification:latest"
cpu = 256
memory = 512
desired_count = 1
subnet_ids = data.terraform_remote_state.shared.outputs.private_subnet_ids
security_group_ids = [data.terraform_remote_state.shared.outputs.ecs_sg_id]
env_vars = {
NODE_ENV = "staging"
}
}
environments/production/main.tf:
module "notification_service" {
source = "../../modules/ecs-service"
project = "crm"
environment = "production"
service_name = "notification-service"
cluster_id = data.terraform_remote_state.shared.outputs.ecs_cluster_id
container_image = "123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/crm-notification:v1.4.2"
cpu = 512
memory = 1024
desired_count = 3
subnet_ids = data.terraform_remote_state.shared.outputs.private_subnet_ids
security_group_ids = [data.terraform_remote_state.shared.outputs.ecs_sg_id]
env_vars = {
NODE_ENV = "production"
}
}
Chú ý: production dùng image tag cụ thể (v1.4.2), không dùng latest — đây là một convention quan trọng mà nếu AI sinh code không được yêu cầu rõ, nó có thể mặc định dùng latest cho cả hai environment, gây ra rủi ro deploy không kiểm soát version trên production.
Prompt mẫu yêu cầu AI sinh module trên
Bạn là một AWS Solutions Architect. Tôi cần một Terraform module tái sử dụng
để định nghĩa một ECS Fargate service, dùng chung cho môi trường staging và
production trong monorepo Terraform (layout: modules/ + environments/staging|production/).
Yêu cầu:
1. Module gồm 3 file: variables.tf, main.tf, outputs.tf.
2. Resource cần có: aws_ecs_task_definition (Fargate, awsvpc network mode),
aws_ecs_service, aws_cloudwatch_log_group cho container log, optional
integration với ALB target group (dùng dynamic block, không bắt buộc
phải có target group).
3. Tham số hoá (qua variables.tf) ít nhất: project, environment, service_name,
cluster_id, container_image, container_port, cpu, memory, desired_count,
subnet_ids, security_group_ids, env_vars (map), tags (map).
4. Validate: variable "environment" chỉ nhận "staging" hoặc "production"
(dùng validation block), báo lỗi rõ ràng nếu sai.
5. Naming convention bắt buộc: "${project}-${environment}-${service_name}"
cho mọi resource name.
6. Tag bắt buộc trên MỌI resource: Environment, Project, ManagedBy = "terraform",
cộng thêm tag Service = service_name. Cho phép merge thêm tag tuỳ chọn qua
variable "tags".
7. Log retention: 90 ngày nếu environment = production, 14 ngày nếu staging
(khác biệt có chủ đích, không phải giá trị cố định).
8. Container image ở environments/production/main.tf phải dùng version tag
cụ thể (không dùng "latest") — nếu tôi quên, hãy nhắc tôi trong phần giải thích.
9. Thêm lifecycle { ignore_changes = [desired_count] } trên aws_ecs_service
để tránh Terraform ghi đè desired_count nếu có auto-scaling policy quản lý
riêng ngoài Terraform.
10. Sau khi sinh module, viết THÊM ví dụ gọi module này cho cả hai environment
(staging: cpu=256/memory=512/desired_count=1, production:
cpu=512/memory=1024/desired_count=3), dùng data.terraform_remote_state
để lấy cluster_id, subnet_ids, security_group_ids từ state đã có sẵn
tên output là ecs_cluster_id, private_subnet_ids, ecs_sg_id.
Không cần viết resource IAM role chi tiết (aws_iam_role.execution/task) —
giả định đã có sẵn, chỉ cần reference đúng tên.
Sau khi sinh xong, liệt kê rõ: (a) resource nào có khả năng bị destroy/recreate
nếu tôi đổi network_mode hoặc launch_type sau này, (b) giá trị nào tôi PHẢI
tự xác nhận trước khi apply lên production (không được để AI tự chọn).
Vì sao prompt này khác với "viết Terraform cho ECS service":
- Yêu cầu (6) và (7) buộc AI tuân thủ đúng convention tagging đã quy định trong dự án (
Environment,Project,ManagedBy=terraform) — nếu không nêu rõ, AI sẽ tự bịa convention riêng, tạo ra sự không nhất quán giữa các module. - Yêu cầu (8) là một dạng "AI tự kiểm tra hộ mình" — đưa vào prompt một cảnh báo nghiệp vụ quan trọng (không dùng
latestcho production) để AI chủ động nhắc lại trong output, giảm khả năng bỏ sót. - Câu hỏi cuối cùng (mục 10, phần "liệt kê") là quan trọng nhất trong toàn bộ prompt: nó buộc AI phải tường minh hoá phần rủi ro destroy/recreate và phần cần con người xác nhận — thay vì để những rủi ro đó ẩn trong code, chỉ lộ ra khi đọc
terraform plan.
Quy trình bắt buộc sau khi có code AI sinh ra
# 1. Format và validate cú pháp trước — bước rẻ, làm luôn
terraform fmt -recursive
terraform validate
# 2. Plan trên staging trước, KHÔNG BAO GIỜ apply thẳng lên production trước
cd environments/staging
terraform plan -out=tfplan-staging
# 3. Đọc kỹ output — đặc biệt tìm dòng có dấu "-/+" (destroy và recreate)
# hoặc "-" (destroy) — đây là dấu hiệu cần dừng lại xem xét, không tự apply.
terraform show tfplan-staging | grep -E "^\s*[-+~]"
# 4. Chỉ apply staging sau khi plan đã được review là hợp lý
terraform apply tfplan-staging
# 5. Sau khi xác nhận staging hoạt động đúng, lặp lại plan riêng cho production
cd ../production
terraform plan -out=tfplan-production
# Review kỹ hơn: production plan luôn cần ít nhất một người khác review cùng.
terraform apply tfplan-production
Cạm bẫy thường gặp
1. Chạy terraform apply trực tiếp từ code AI sinh ra mà không review kỹ plan output. Đây là cạm bẫy nghiêm trọng nhất. AI có thể sinh code cú pháp hoàn toàn hợp lệ nhưng vô tình thay đổi một thuộc tính buộc Terraform phải destroy và recreate resource — ví dụ đổi name của một aws_ecs_task_definition (Terraform tạo revision mới thì không sao, nhưng đổi tên aws_db_instance hay đổi availability_zone của một resource stateful thì có thể dẫn đến mất dữ liệu). Luôn tuân thủ nguyên tắc không sửa state thủ công, luôn review plan output trước khi áp dụng bất kỳ thay đổi hạ tầng nào — kể cả khi thay đổi đó "chỉ là một dòng nhỏ" do AI đề xuất.
2. Tin tưởng module do AI sinh dùng đúng convention chỉ vì "prompt có nhắc tới". AI sinh code dựa trên xác suất pattern phổ biến, không phải dựa trên đọc toàn bộ codebase Terraform hiện có. Nếu không dán kèm ví dụ file .tf thật đang dùng trong dự án (naming convention thực tế, cấu trúc thư mục thực tế, tag bắt buộc thực tế), AI rất dễ sinh ra một module đúng về mặt kỹ thuật nhưng lệch convention — dẫn đến một module "nhìn lạ" giữa hàng chục module khác, gây khó khăn khi bảo trì về sau.
3. Coi biến hoá theo environment (staging/production) là việc chỉ cần đổi số, bỏ qua khác biệt về mức độ rủi ro review. Một thay đổi hợp lý ở staging (ví dụ giảm desired_count xuống 0 để tiết kiệm chi phí qua đêm) áp dụng y hệt logic đó sang production sẽ gây downtime thật. AI sinh code tham số hoá tốt giúp cấu trúc đúng, nhưng giá trị và quy trình review cho production luôn phải nghiêm ngặt hơn staging — điều này không nằm trong code, mà nằm ở quy trình con người: production luôn cần plan riêng, review riêng, và không bao giờ áp dụng thay đổi cùng lúc với staging chỉ vì "đã test ở staging rồi".
🧭 Góc nhìn Solutions Architect
Khi bạn nhận mộtterraform planoutput dài hàng trăm dòng do một thay đổi tưởng chừng nhỏ (đổi một biến trong module dùng chung), bạn có đang thực sự đọc từng dòng có dấu-hay-/+, hay đang lướt qua vì "chắc không có gì thay đổi lớn"? Nếu một module Terraform do AI sinh ra được dùng lại ở mười chỗ khác nhau trong hạ tầng, một lỗi nhỏ trong module đó nhân bản thành mười lỗi cùng lúc — bạn đã kiểm tra kỹ module gốc đến mức nào trước khi để nó lan rộng như vậy?
🔗 Bài viết liên quan
- System Design với AI: AWS/GCP — nền tảng tư duy kiến trúc hạ tầng cloud, là bối cảnh để áp dụng IaC đúng chỗ thay vì chỉ viết Terraform rời rạc theo từng yêu cầu.
- Cost Optimization with AI — dùng AI phân tích chi phí hạ tầng, bổ trợ trực tiếp cho việc chọn giá trị CPU/memory/desired_count hợp lý khi tham số hoá module theo environment.
Bài trước: API First Design · Bài tiếp theo: Scalability Strategy