Chuyển tới nội dung

LAB HCL: Triển khai Web Server AWS EC2 với SSH Key tự động bằng Terraform

🎯 Mục tiêu Lab

Sau khi hoàn thành lab này, bạn sẽ:

  • Hiểu cách HCL tổ chức cấu hình Terraform
  • Biết cách sử dụng Block – Attribute – Data Type – Function – Condition – Dependency
  • Tự động:
    • Sinh SSH Key
    • Tạo EC2 Ubuntu
    • Cài Nginx bằng user_data
    • Xuất lệnh SSH & URL Web

📌 Điều kiện tiên quyết

  • Đã cài:
    • Terraform ≥ 1.0
    • AWS CLI
  • AWS credentials đã cấu hình (~/.aws/credentials)
  • Region sử dụng: ap-southeast-1 (Singapore)

🧩 Phần 1: Terraform Block – Khai báo nền tảng

terraform {
  required_version = ">= 1.0.0"
  required_providers {
    aws   = { source = "hashicorp/aws", version = "~> 5.0" }
    local = { source = "hashicorp/local", version = "~> 2.0" }
    tls   = { source = "hashicorp/tls", version = "~> 4.0" }
    random = { source = "hashicorp/random", version = "~> 3.0" }
  }
}

👉 Ý nghĩa HCL:

  • terraform {}: block gốc
  • required_providers: định nghĩa dependency giống package.json
  • Version constraint giúp tránh lỗi môi trường

🌍 Phần 2: Provider Block – Kết nối AWS

provider "aws" {
  region = "ap-southeast-1"
}

👉 Terraform sử dụng block này để:

  • Biết deploy tài nguyên ở đâu
  • Giao tiếp với AWS API

🧠 Phần 3: Variable với Complex Data Type (Object)

variable "project_config" {
  type = object({
    env           = string
    instance_size = map(string)
    whitelist_ips = list(string)
  })
}

👉 Đây là HCL nâng cao:

  • object: gom nhiều cấu hình liên quan
  • map: ánh xạ env → instance type
  • list: danh sách IP cho SSH

🔍 Phần 4: Data Source – Lấy AMI động

data "aws_ami" "ubuntu" {
  most_recent = true
  owners = ["099720109477"]
}

👉 Không tạo tài nguyên, chỉ đọc dữ liệu

  • Luôn dùng Ubuntu mới nhất
  • Tránh lỗi AMI cũ

🔐 Phần 5: Resource – Tạo SSH Key tự động

1️⃣ Sinh private/public key

resource "tls_private_key" "ssh_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

2️⃣ Upload public key lên AWS

resource "aws_key_pair" "generated_key" {
  key_name   = format("beobeo-lab-key-%s", var.project_config.env)
  public_key = tls_private_key.ssh_key.public_key_openssh
}

👉 Minh họa:

  • Function: format()
  • Implicit dependency: Terraform tự hiểu thứ tự

3️⃣ Lưu private key ra file

resource "local_file" "private_key_pem" {
  filename = "generated-key.pem"
}

🛡 Phần 6: Security Group – Network Layer

resource "aws_security_group" "web_sg" {
  ingress { from_port = 80 }
  ingress { from_port = 22 }
}

👉 Kiến thức HCL:

  • Nested block (ingress, egress)
  • Dùng variable cho whitelist IP → linh hoạt

🖥 Phần 7: EC2 Resource – Tổng hợp mọi khái niệm HCL

resource "aws_instance" "web_server" {
  depends_on = [aws_key_pair.generated_key]

  instance_type = var.project_config.env == "prod" ?
                  var.project_config.instance_size["prod"] :
                  var.project_config.instance_size["dev"]

  user_data = <<-EOF
    #!/bin/bash
    apt install -y nginx
  EOF
}

👉 Áp dụng:

  • Explicit dependency: depends_on
  • Condition (Ternary operator)
  • Here-doc string
  • Reference resource (aws_security_group.web_sg.id)

📤 Phần 8: Output – Trả kết quả cho người dùng

output "ssh_command" {
  value = "ssh -i generated-key.pem ubuntu@${aws_instance.web_server.public_ip}"
}
output "web_url" {
  description = "Link truy cập web"
  value       = "http://${aws_instance.web_server.public_ip}"
}

👉 Output giúp:

  • Dễ dùng
  • Không cần tra IP thủ công
# [1] BLOCK: Terraform configuration
terraform {
  required_version = ">= 1.0.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
    tls = {
      source  = "hashicorp/tls"
      version = "~> 4.0"
    }
    # [FIX] Thêm random để tránh lỗi lock file nếu môi trường cũ còn lưu vết
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = "ap-southeast-1"
  # profile = "default" 
}

# --- INPUT VARIABLES ---

variable "project_config" {
  description = "Cấu hình dự án (Complex Data Type)"
  # [3] DATA TYPE: Object (Struct) kết hợp Map và List
  type = object({
    env           = string
    instance_size = map(string)
    whitelist_ips = list(string)
  })

  default = {
    env = "dev"
    instance_size = {
      dev  = "t3.micro"
      prod = "t3.micro"
    }
    whitelist_ips = ["0.0.0.0/0"] # Lab thì mở hết
  }
}

# --- DATA SOURCE ---

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# --- RESOURCES (SSH KEY GENERATION) ---

resource "tls_private_key" "ssh_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "generated_key" {
  # [6] FUNCTION: format()
  key_name = format("beobeo-lab-key-%s", var.project_config.env)
  # [2] ATTRIBUTE: Tham chiếu
  public_key = tls_private_key.ssh_key.public_key_openssh
}

resource "local_file" "private_key_pem" {
  content         = tls_private_key.ssh_key.private_key_pem
  filename        = "${path.module}/generated-key.pem"
  file_permission = "0400"
}

# --- RESOURCES (INFRASTRUCTURE) ---

resource "aws_security_group" "web_sg" {
  name        = "web-sg-${var.project_config.env}"
  description = "Allow HTTP and SSH"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.project_config.whitelist_ips
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "web_server" {
  # [7] DEPENDENCY (Explicit)
  depends_on = [aws_key_pair.generated_key]

  ami = data.aws_ami.ubuntu.id

  # [4] CONDITION (Ternary Operator)
  instance_type = var.project_config.env == "prod" ? var.project_config.instance_size["prod"] : var.project_config.instance_size["dev"]

  # [7] DEPENDENCY (Implicit)
  vpc_security_group_ids = [aws_security_group.web_sg.id]
  key_name               = aws_key_pair.generated_key.key_name

  user_data = <<-EOF
              #!/bin/bash
              sudo apt-get update
              sudo apt-get install -y nginx
              echo "<h1>Hello BeoBeo Terraform Class!</h1><p>Environment: ${var.project_config.env}</p>" > /var/www/html/index.html
              sudo systemctl enable nginx
              sudo systemctl start nginx
              EOF

  tags = {
    Name = "Web-${upper(var.project_config.env)}-01"
  }
}

# --- OUTPUTS ---

output "ssh_command" {
  description = "Lệnh để SSH vào server"
  value       = "ssh -i generated-key.pem ubuntu@${aws_instance.web_server.public_ip}"
}

output "web_url" {
  description = "Link truy cập web"
  value       = "http://${aws_instance.web_server.public_ip}"
}

Kết quả:

Liên hệ