Cạm bẫy ProvisionerCuối cùng bạn đã hoàn thành Terraform manifest. Bạn chạy terraform apply, nhìn instance khởi động, và rồi... im lặng. Terminal treo trong năm phút trước khi thông báo lỗi quen thuộc:
Error: timeout - last error: dial tcp 10.0.1.5:22: connect: connection refused
Điều này thường có nghĩa là Terraform runner của bạn—cho dù đó là máy tính cá nhân, GitHub Actions agent, hay CI/CD pipeline—không thể kết nối tới cổng SSH. Đây là một trở ngại khó chịu, nhưng thường khá dễ giải quyết. Dưới đây là cách debug mà không làm bạn mất kiên nhẫn.
TL;DR: Kiểm tra nhanh trong 60 giây- Security Groups: Cổng 22 đã được mở cho IP cụ thể của bạn chưa? Hãy kiểm tra các ingress rule.- Lựa chọn IP: Bạn có đang cố gắng truy cập một IP private từ internet không? Hãy đổi host thành public_ip.- Độ trễ khi khởi động: VM có thể báo trạng thái 'running' trong AWS console, nhưng sshd có thể vẫn đang trong quá trình khởi tạo.- Username: Bạn có đang dùng ubuntu cho Amazon Linux AMI không? (Đúng ra phải là ec2-user).### 1. Khoảng cách định tuyến: Public IP vs. Private IPHãy nhìn kỹ vào IP trong lỗi: 10.0.1.5. Nếu bạn đang chạy Terraform từ máy cục bộ, bạn không thể định tuyến traffic đến địa chỉ đó. Đó là một IP nội bộ (private). Terraform thường lấy IP đầu tiên mà nó tìm thấy, và đó thường là IP nội bộ.
Provisioner remote-exec cần một đường truyền trực tiếp. Trừ khi bạn đang dùng VPN hoặc sử dụng bastion host, bạn phải sử dụng IP public của instance.
Giải pháp:Ép khối connection sử dụng thuộc tính public IP.
resource "aws_instance" "web" {
# ... cấu hình ...
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/deploy_key")
host = self.public_ip # Đừng để mặc cho hên xui
}
inline = ["sudo apt-get update"]
}
}
2. Security Groups: Bức tường vô hìnhNgay cả với đúng IP, lỗi timeout cho thấy firewall đang âm thầm chặn gói tin của bạn. Tuy nhiên, connection refused nghĩa là bạn đã chạm tới server nhưng bị từ chối. Hầu hết các nhà cung cấp cloud mặc định 'chặn tất cả' (deny all) cho traffic đi vào.
Bước tiếp theo:Xác minh xem Security Group hoặc Network Security Group của bạn có cho phép cổng TCP 22 hay không. Để test, bạn có thể dùng 0.0.0.0/0, nhưng với môi trường production, hãy giới hạn trong dải IP cụ thể của bạn (ví dụ: 203.0.113.5/32).
resource "aws_security_group" "ssh_access" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["your_ip_here/32"]
}
}
3. Race Condition: Thực tế thời gian khởi độngCloud API thì nhanh, nhưng Linux kernel thì chậm hơn. Một instance AWS t3.micro có thể báo trạng thái 'Running' trong vòng 20 giây, nhưng cloud-init và sshd thường cần thêm 40 đến 60 giây nữa để khởi động hoàn toàn. Nếu Terraform cố gắng kết nối quá sớm, nó có thể dùng hết số lần thử lại.
Cách khắc phục:Tăng thời gian timeout của kết nối để hệ điều hành có thời gian "thở". Khoảng thời gian từ 5 đến 10 phút thường là an toàn cho hầu hết các image tiêu chuẩn.
connection {
type = "ssh"
user = "ec2-user"
host = self.public_ip
timeout = "10m"
}
4. Firewall nội bộ (UFW/Firewalld)Một số AMI bảo mật cao đi kèm với firewall nội bộ được kích hoạt. Ngay cả khi Security Group ở cấp độ cloud được mở toang, ufw (trên Ubuntu) hoặc firewalld (trên RHEL) vẫn có thể đang chặn cổng 22.
Chạy kiểm tra thủ công để loại trừ các vấn đề đặc thù của Terraform:
ssh -i ~/.ssh/key.pem user@1.2.3.4
Nếu lệnh thủ công này thất bại, vấn đề nằm ở mạng hoặc hệ điều hành, không phải do code Terraform của bạn.
Mẹo nâng cao: Bỏ qua ProvisionerHashiCorp coi provisioner là 'giải pháp cuối cùng'. Chúng không nằm trong Terraform state, điều này khiến chúng dễ bị lỗi. Nếu vấn đề về mạng xảy ra thường xuyên, hãy chuyển logic cài đặt vào user_data.
Cloud-init chạy cục bộ trên máy. Nó không cần đường truyền SSH từ laptop của bạn, giúp việc khởi tạo instance tin cậy hơn gấp 10 lần.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
EOF
}

