Bài 21: Triển Khai Hạ Tầng Container Với Terraform (ECS, EKS)


Danh sách bài viết trong series Terraform Associate (003)

Hạ Tầng Container Là Gì?

e7272138-2b99-455f-aa3b-62e4299fbda9

Hạ tầng container là mô hình triển khai ứng dụng sử dụng các container (VD: Docker) để đóng gói mã và phụ thuộc, đảm bảo ứng dụng chạy nhất quán trên mọi môi trường. AWS cung cấp hai dịch vụ phổ biến để quản lý container: ECS (Elastic Container Service) cho việc quản lý container đơn giản và EKS (Elastic Kubernetes Service) cho các hệ thống phức tạp hơn với Kubernetes. Terraform giúp triển khai và quản lý các dịch vụ container này dưới dạng mã (Infrastructure as Code), đảm bảo tính tự động hóa và nhất quán.

Tại Sao Sử Dụng Terraform Cho Container?

  • Tự động hóa triển khai: Tạo và quản lý ECS/EKS cluster, task, và node group bằng mã.
  • Tính nhất quán: Đảm bảo cấu hình giống nhau giữa các môi trường (dev, prod).
  • Tích hợp với hạ tầng khác: Dễ dàng kết hợp với VPC, IAM, và các dịch vụ AWS khác.
  • Quản lý dễ dàng: Cập nhật hoặc xóa tài nguyên container chỉ với vài lệnh Terraform.
  • Tích hợp CI/CD: Tự động triển khai container thông qua pipeline.

Triển Khai Container Với Terraform

Triển Khai ECS (Elastic Container Service)

  • Các bước chính:
    1. Tạo ECS cluster.
    2. Định nghĩa task definition (mô tả container).
    3. Tạo service để chạy task trên ECS.
    4. Cấu hình VPC và IAM role.

Triển Khai EKS (Elastic Kubernetes Service)

  • Các bước chính:
    1. Tạo EKS cluster.
    2. Tạo node group để chạy worker nodes.
    3. Cấu hình VPC, IAM role, và kubectl để quản lý cluster.
    4. Triển khai ứng dụng lên Kubernetes.

Ví Dụ Thực Tế: Triển Khai ECS Và EKS Với Terraform

Phần 1: Triển Khai ECS

  • Chuẩn bị:

    • Đảm bảo bạn có một container image (VD: nginx) được đẩy lên Amazon ECR (Elastic Container Registry).
    • Giả sử image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-nginx:latest.
  • File ecs.tf:

    terraform {
    backend "s3" {
      bucket         = "my-terraform-state"
      key            = "state/ecs.tfstate"
      region         = "us-east-1"
      dynamodb_table = "terraform-locks"
      encrypt        = true
    }
    }
    
    provider "aws" {
    region = "us-east-1"
    }
    
    # VPC và Subnets
    resource "aws_vpc" "main" {
    cidr_block = "10.0.0.0/16"
    tags = {
      Name = "ecs-vpc"
    }
    }
    
    resource "aws_subnet" "public" {
    count                   = 2
    vpc_id                  = aws_vpc.main.id
    cidr_block              = "10.0.${count.index}.0/24"
    availability_zone       = element(["us-east-1a", "us-east-1b"], count.index)
    map_public_ip_on_launch = true
    tags = {
      Name = "public-subnet-${count.index}"
    }
    }
    
    resource "aws_internet_gateway" "igw" {
    vpc_id = aws_vpc.main.id
    tags = {
      Name = "ecs-igw"
    }
    }
    
    resource "aws_route_table" "public" {
    vpc_id = aws_vpc.main.id
    route {
      cidr_block = "0.0.0.0/0"
      gateway_id = aws_internet_gateway.igw.id
    }
    tags = {
      Name = "public-route-table"
    }
    }
    
    resource "aws_route_table_association" "public" {
    count          = 2
    subnet_id      = aws_subnet.public[count.index].id
    route_table_id = aws_route_table.public.id
    }
    
    # ECS Cluster
    resource "aws_ecs_cluster" "main" {
    name = "my-ecs-cluster"
    }
    
    # IAM Role cho ECS Task
    resource "aws_iam_role" "ecs_task_execution" {
    name = "ecs_task_execution_role"
    
    assume_role_policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = {
            Service = "ecs-tasks.amazonaws.com"
          }
        }
      ]
    })
    }
    
    resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy" {
    role       = aws_iam_role.ecs_task_execution.name
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
    }
    
    # Task Definition
    resource "aws_ecs_task_definition" "nginx" {
    family                   = "nginx-task"
    network_mode             = "awsvpc"
    requires_compatibilities = ["FARGATE"]
    cpu                      = "256"
    memory                   = "512"
    execution_role_arn       = aws_iam_role.ecs_task_execution.arn
    
    container_definitions = jsonencode([
      {
        name  = "nginx"
        image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-nginx:latest"
        essential = true
        portMappings = [
          {
            containerPort = 80
            hostPort      = 80
          }
        ]
      }
    ])
    }
    
    # ECS Service
    resource "aws_ecs_service" "nginx" {
    name            = "nginx-service"
    cluster         = aws_ecs_cluster.main.id
    task_definition = aws_ecs_task_definition.nginx.arn
    desired_count   = 1
    launch_type     = "FARGATE"
    
    network_configuration {
      subnets          = aws_subnet.public[*].id
      assign_public_ip = true
    }
    }
    
    output "ecs_cluster_name" {
    value = aws_ecs_cluster.main.name
    }
    
    output "ecs_service_name" {
    value = aws_ecs_service.nginx.name
    }
  • Quy trình ECS:

    1. Khởi tạo backend:

      terraform init

      Output:

      Initializing the backend...
      Successfully configured the backend "s3"! Terraform will automatically
      use this backend unless the backend configuration changes.
      
      Terraform has been successfully initialized!
    2. Áp dụng cấu hình:

      terraform apply -auto-approve

      Output:

      aws_vpc.main: Creating...
      aws_vpc.main: Creation complete after 3s [id=vpc-12345678]
      
      aws_subnet.public[0]: Creating...
      aws_subnet.public[1]: Creating...
      aws_subnet.public[0]: Creation complete after 2s [id=subnet-12345678]
      aws_subnet.public[1]: Creation complete after 2s [id=subnet-87654321]
      
      aws_internet_gateway.igw: Creating...
      aws_internet_gateway.igw: Creation complete after 1s [id=igw-12345678]
      
      aws_route_table.public: Creating...
      aws_route_table.public: Creation complete after 1s [id=rtb-12345678]
      
      aws_route_table_association.public[0]: Creating...
      aws_route_table_association.public[1]: Creating...
      aws_route_table_association.public[0]: Creation complete after 1s
      aws_route_table_association.public[1]: Creation complete after 1s
      
      aws_ecs_cluster.main: Creating...
      aws_ecs_cluster.main: Creation complete after 1s [id=my-ecs-cluster]
      
      aws_iam_role.ecs_task_execution: Creating...
      aws_iam_role.ecs_task_execution: Creation complete after 2s [id=ecs_task_execution_role]
      
      aws_iam_role_policy_attachment.ecs_task_execution_policy: Creating...
      aws_iam_role_policy_attachment.ecs_task_execution_policy: Creation complete after 1s
      
      aws_ecs_task_definition.nginx: Creating...
      aws_ecs_task_definition.nginx: Creation complete after 1s [id=nginx-task]
      
      aws_ecs_service.nginx: Creating...
      aws_ecs_service.nginx: Creation complete after 5s [id=nginx-service]
      
      Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
      
      Outputs:
      ecs_cluster_name = "my-ecs-cluster"
      ecs_service_name = "nginx-service"

Phần 2: Triển Khai EKS

  • File eks.tf:

    terraform {
    backend "s3" {
      bucket         = "my-terraform-state"
      key            = "state/eks.tfstate"
      region         = "us-east-1"
      dynamodb_table = "terraform-locks"
      encrypt        = true
    }
    }
    
    provider "aws" {
    region = "us-east-1"
    }
    
    # VPC và Subnets (tương tự như ECS, tái sử dụng VPC nếu cần)
    resource "aws_vpc" "eks_vpc" {
    cidr_block = "10.1.0.0/16"
    tags = {
      Name = "eks-vpc"
    }
    }
    
    resource "aws_subnet" "eks_public" {
    count                   = 2
    vpc_id                  = aws_vpc.eks_vpc.id
    cidr_block              = "10.1.${count.index}.0/24"
    availability_zone       = element(["us-east-1a", "us-east-1b"], count.index)
    map_public_ip_on_launch = true
    tags = {
      "Name" = "eks-public-subnet-${count.index}"
      "kubernetes.io/cluster/my-eks-cluster" = "shared"
    }
    }
    
    resource "aws_internet_gateway" "eks_igw" {
    vpc_id = aws_vpc.eks_vpc.id
    tags = {
      Name = "eks-igw"
    }
    }
    
    resource "aws_route_table" "eks_public" {
    vpc_id = aws_vpc.eks_vpc.id
    route {
      cidr_block = "0.0.0.0/0"
      gateway_id = aws_internet_gateway.eks_igw.id
    }
    tags = {
      Name = "eks-public-route-table"
    }
    }
    
    resource "aws_route_table_association" "eks_public" {
    count          = 2
    subnet_id      = aws_subnet.eks_public[count.index].id
    route_table_id = aws_route_table.eks_public.id
    }
    
    # EKS Cluster
    resource "aws_eks_cluster" "main" {
    name     = "my-eks-cluster"
    role_arn = aws_iam_role.eks_cluster.arn
    
    vpc_config {
      subnet_ids = aws_subnet.eks_public[*].id
    }
    
    depends_on = [
      aws_iam_role_policy_attachment.eks_cluster_policy
    ]
    }
    
    # IAM Role cho EKS Cluster
    resource "aws_iam_role" "eks_cluster" {
    name = "eks_cluster_role"
    
    assume_role_policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = {
            Service = "eks.amazonaws.com"
          }
        }
      ]
    })
    }
    
    resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
    role       = aws_iam_role.eks_cluster.name
    policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
    }
    
    # Node Group
    resource "aws_eks_node_group" "main" {
    cluster_name    = aws_eks_cluster.main.name
    node_group_name = "my-node-group"
    node_role_arn   = aws_iam_role.eks_node.arn
    subnet_ids      = aws_subnet.eks_public[*].id
    
    scaling_config {
      desired_size = 2
      max_size     = 3
      min_size     = 1
    }
    
    depends_on = [
      aws_iam_role_policy_attachment.eks_node_policy
    ]
    }
    
    # IAM Role cho EKS Node Group
    resource "aws_iam_role" "eks_node" {
    name = "eks_node_role"
    
    assume_role_policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = {
            Service = "ec2.amazonaws.com"
          }
        }
      ]
    })
    }
    
    resource "aws_iam_role_policy_attachment" "eks_node_policy" {
    role       = aws_iam_role.eks_node.name
    policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
    }
    
    resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
    role       = aws_iam_role.eks_node.name
    policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
    }
    
    resource "aws_iam_role_policy_attachment" "eks_registry_policy" {
    role       = aws_iam_role.eks_node.name
    policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
    }
    
    output "eks_cluster_name" {
    value = aws_eks_cluster.main.name
    }
    
    output "eks_cluster_endpoint" {
    value = aws_eks_cluster.main.endpoint
    }
  • Quy trình EKS:

    1. Khởi tạo backend:

      terraform init

      Output:

      Initializing the backend...
      Successfully configured the backend "s3"! Terraform will automatically
      use this backend unless the backend configuration changes.
      
      Terraform has been successfully initialized!
    2. Áp dụng cấu hình:

      terraform apply -auto-approve

      Output:

      aws_vpc.eks_vpc: Creating...
      aws_vpc.eks_vpc: Creation complete after 3s [id=vpc-87654321]
      
      aws_subnet.eks_public[0]: Creating...
      aws_subnet.eks_public[1]: Creating...
      aws_subnet.eks_public[0]: Creation complete after 2s [id=subnet-abcdef12]
      aws_subnet.eks_public[1]: Creation complete after 2s [id=subnet-fedcba21]
      
      aws_internet_gateway.eks_igw: Creating...
      aws_internet_gateway.eks_igw: Creation complete after 1s [id=igw-87654321]
      
      aws_route_table.eks_public: Creating...
      aws_route_table.eks_public: Creation complete after 1s [id=rtb-87654321]
      
      aws_route_table_association.eks_public[0]: Creating...
      aws_route_table_association.eks_public[1]: Creating...
      aws_route_table_association.eks_public[0]: Creation complete after 1s
      aws_route_table_association.eks_public[1]: Creation complete after 1s
      
      aws_iam_role.eks_cluster: Creating...
      aws_iam_role.eks_cluster: Creation complete after 2s [id=eks_cluster_role]
      
      aws_iam_role_policy_attachment.eks_cluster_policy: Creating...
      aws_iam_role_policy_attachment.eks_cluster_policy: Creation complete after 1s
      
      aws_eks_cluster.main: Creating...
      aws_eks_cluster.main: Creation complete after 8m [id=my-eks-cluster]
      
      aws_iam_role.eks_node: Creating...
      aws_iam_role.eks_node: Creation complete after 2s [id=eks_node_role]
      
      aws_iam_role_policy_attachment.eks_node_policy: Creating...
      aws_iam_role_policy_attachment.eks_cni_policy: Creating...
      aws_iam_role_policy_attachment.eks_registry_policy: Creating...
      aws_iam_role_policy_attachment.eks_node_policy: Creation complete after 1s
      aws_iam_role_policy_attachment.eks_cni_policy: Creation complete after 1s
      aws_iam_role_policy_attachment.eks_registry_policy: Creation complete after 1s
      
      aws_eks_node_group.main: Creating...
      aws_eks_node_group.main: Creation complete after 5m [id=my-eks-cluster:my-node-group]
      
      Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
      
      Outputs:
      eks_cluster_name = "my-eks-cluster"
      eks_cluster_endpoint = "https://my-eks-cluster.yl4.us-east-1.eks.amazonaws.com"
    3. Cấu hình kubectl để quản lý EKS:
      aws eks --region us-east-1 update-kubeconfig --name my-eks-cluster

      Output:

      Added new context arn:aws:eks:us-east-1:123456789012:cluster/my-eks-cluster to /Users/user/.kube/config
      • Sau đó, bạn có thể triển khai ứng dụng lên EKS bằng kubectl.

Lưu Ý Quan Trọng Khi Triển Khai Container Với Terraform

  • Quyền IAM: Đảm bảo các role cho ECS và EKS có quyền cần thiết (VD: truy cập ECR, CloudWatch).
  • VPC cấu hình đúng: Subnet cần có internet access để ECS Fargate hoặc EKS node group hoạt động.
  • Chi phí container: Theo dõi chi phí ECS Fargate hoặc EKS node group, đặc biệt với quy mô lớn.
  • Tích hợp CI/CD: Dùng pipeline để tự động triển khai container image và cập nhật task definition.
  • Debug container: Sử dụng CloudWatch Logs hoặc kubectl logs để kiểm tra lỗi nếu container không chạy.

Liên Hệ Với Chứng Chỉ Terraform Associate (003)

Kỳ thi Terraform Associate (003) không kiểm tra trực tiếp triển khai container, nhưng hiểu cách quản lý tài nguyên ECS/EKS sẽ giúp bạn áp dụng kiến thức thực tiễn vào mục tiêu 7 và 8. Bạn cần nắm:

  • Khái niệm liên quan:
    • Quản lý tài nguyên container (ECS task, EKS node group).
    • Tích hợp tài nguyên với VPC và IAM role.
  • Thực hành: Biết cách triển khai container sẽ giúp bạn xử lý tốt hơn trong các tình huống thực tế.

Kết Luận

Triển khai hạ tầng container với Terraform giúp quản lý ECS và EKS hiệu quả, đảm bảo tính tự động hóa và mở rộng. Sử dụng Terraform để tạo cluster, task definition, và node group sẽ giúp bạn xây dựng hệ thống container mạnh mẽ. Dù không phải trọng tâm của kỳ thi Terraform Associate (003), kỹ năng này rất hữu ích trong thực tế. Ở bài tiếp theo, chúng ta sẽ tìm hiểu cách sử dụng Terraform để triển khai hạ tầng đa đám mây (multi-cloud).

Điều hướng chuỗi bài viết<< Bài 20: Triển Khai Hạ Tầng Serverless Với Terraform
>> Bài 22: Triển Khai Hạ Tầng Multi-Cloud Với Terraform
Article Thumbnail
Article Thumbnail
Datadog Webinar: Modernize AWS Logs at Scale
Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận