Vấn đề
Terraform thường khá thông minh về kiểu dữ liệu, nhưng nó rất khắt khe với việc nội suy chuỗi (string interpolation). Bạn có thể gặp lỗi này trong quá trình plan hoặc apply khi cố gắng đưa một cấu trúc dữ liệu phức tạp vào vị trí yêu cầu một chuỗi đơn giản:
Error: Invalid template interpolation value
on main.tf line 42, in resource "aws_instance" "example":
42: user_data = "echo ${var.config_map}"
|----------------
| var.config_map là một object với 2 thuộc tính
Không thể đưa giá trị đã cho vào template chuỗi: yêu cầu kiểu string.
Cú pháp ${...} chỉ có một nhiệm vụ duy nhất: chèn các chuỗi. Nếu bạn truyền cho nó một list, map hoặc object, Terraform sẽ không tự đoán cách định dạng chúng. Nó mong đợi một chuỗi phẳng (flat string), và khi nhận được một object nhiều lớp, quá trình sẽ dừng lại.
Các trường hợp phổ biến
Đây không chỉ là lỗi cú pháp đơn thuần; đó là sự không khớp về kiểu dữ liệu. Bạn sẽ thường xuyên gặp lỗi này khi:
- Truyền một map chứa các biến môi trường vào script shell
user_data. - Cố gắng in một danh sách gồm 5 địa chỉ IP riêng vào một file cấu hình.
- Chèn toàn bộ một resource object vào một tag hoặc trường metadata.
Ba cách để khắc phục
Giải pháp tùy thuộc vào việc bạn muốn dữ liệu trông như thế nào sau khi chuyển đổi. Dưới đây là các phương pháp chuẩn.
1. Sử dụng jsonencode() cho Map và Object
Nếu đích đến của bạn (như một tham số SSM hoặc file cấu hình) yêu cầu dữ liệu có cấu trúc, hãy sử dụng jsonencode(). Hàm này sẽ tự động chuyển đổi HCL object của bạn thành một chuỗi JSON hợp lệ.
Cách làm sai:
resource "aws_ssm_parameter" "config" {
name = "app_settings"
type = "String"
value = "settings = ${var.my_map}" # Dòng này sẽ gây ra lỗi
}
Cách làm đúng:
resource "aws_ssm_parameter" "config" {
name = "app_settings"
type = "String"
value = jsonencode(var.my_map) # Trả về "{\"key\":\"value\"}"
}
2. Sử dụng join() cho List
Làm việc với List có chút phức tạp hơn. Nếu bạn có một danh sách các chuỗi, thông thường bạn sẽ muốn gộp chúng lại với nhau bằng một ký tự phân cách như dấu phẩy hoặc khoảng trắng.
Ví dụ:
resource "aws_instance" "web" {
# Gộp danh sách 3 khối CIDR thành một chuỗi duy nhất cách nhau bởi dấu phẩy
user_data = <<-EOT
#!/bin/bash
echo "Nguồn được phép: ${join(", ", var.external_ips)}"
EOT
}
3. Sử dụng HCL Directive cho định dạng phức tạp
Đôi khi jsonencode tạo ra kết quả quá lộn xộn. Nếu bạn đang sử dụng templatefile() để tạo cấu hình tùy chỉnh (như cấu hình Nginx hoặc HaProxy), hãy sử dụng chỉ thị vòng lặp for ngay bên trong template.
Template của bạn (config.tftpl):
%{ for name, ip in servers ~}
server ${name} ${ip}:8080
%{ endfor ~}
Mã Terraform của bạn:
content = templatefile("config.tftpl", {
servers = { "web01" = "10.0.1.5", "web02" = "10.0.1.6" }
})
Kiểm tra mà không cần deploy
Đừng đợi lệnh terraform plan chậm chạp để xem bản sửa lỗi của bạn có hoạt động hay không. Hãy sử dụng console tích hợp để nhận phản hồi tức thì:
- Chạy
terraform consoletrong terminal của bạn. - Nhập logic của bạn:
join(" | ", ["prod", "staging"]). - Nếu nó xuất ra
"prod | staging", phần nội suy của bạn đã an toàn.
Mẹo từ chuyên gia
- Khai báo kiểu dữ liệu chặt chẽ: Định nghĩa các biến của bạn một cách rõ ràng. Sử dụng
type = map(string)giúp bắt lỗi ngay từ giai đoạn nhập liệu thay vì thất bại giữa chừng khi đang deploy. - Hỗ trợ YAML: Nếu JSON quá cồng kềnh cho log của bạn,
yamlencode()hoạt động hoàn toàn giống như JSON nhưng xuất ra định dạng YAML sạch sẽ hơn. - Bảo mật: Cẩn thận khi dùng
jsonencodetrên các object chứa bí mật (secrets). Nếu một biến được đánh dấusensitive = true, Terraform sẽ ẩn toàn bộ chuỗi trong đầu ra CLI để giữ an toàn cho mật khẩu.

