Complete Terraform - part 6

Complete Terraform - part 6

·

11 min read

Trong bài blog này, chúng ta sẽ tìm hiểu về cách xây dựng một môi trường Amazon Elastic Kubernetes Service (EKS) sử dụng công cụ Hashicorp Terraform. EKS là một dịch vụ quản lý Kubernetes được Amazon Web Services (AWS) cung cấp, cho phép bạn triển khai và quản lý các ứng dụng container một cách dễ dàng trên AWS.

Để xây dựng môi trường EKS, chúng ta sẽ sử dụng tài liệu hướng dẫn có sẵn từ EKS Workshop. Tài liệu này cung cấp cho chúng ta các bước chi tiết và code mẫu để triển khai EKS bằng Terraform.

Terraform là một công cụ mã hóa cơ sở hạ tầng (infrastructure-as-code) mạnh mẽ, giúp tự động hóa quá trình tạo và quản lý các tài nguyên cloud. Với việc sử dụng Terraform, chúng ta có thể định nghĩa cấu hình hạ tầng của môi trường EKS dưới dạng mã nguồn, giúp việc xây dựng và cấu hình trở nên dễ dàng và nhất quán.

Lợi ích của việc sử dụng Terraform cho EKS:

  • Tự động hóa: Terraform tự động hóa các tác vụ thủ công, giúp tiết kiệm thời gian và giảm thiểu sai sót.

  • Tính nhất quán: Terraform đảm bảo môi trường EKS của bạn luôn được cấu hình theo chuẩn mực nhất định.

  • Dễ dàng quản lý: Việc quản lý cấu hình EKS trở nên dễ dàng hơn với các file Terraform có thể đọc và chỉnh sửa.

  • Khả năng lặp lại: Terraform cho phép bạn tái tạo môi trường EKS một cách dễ dàng và nhanh chóng

Understanding Terraform config files

File providers.tf cấu hình các nhà cung cấp Terraform cần thiết để xây dựng cơ sở hạ tầng

provider "aws" {
  default_tags {
    tags = local.tags
  }
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.67.0"
    }
  }

  required_version = ">= 1.4.2"
}

Đoạn code này thiết lập cấu hình cơ bản cho việc sử dụng Terraform để quản lý tài nguyên trên nền tảng Amazon Web Services (AWS).

  • provider "aws" { ... }: Khối này khai báo nhà cung cấp (provider) là AWS.

    • default_tags { ... }: Bên trong khối aws, đoạn này thiết lập các thẻ mặc định (tags) sẽ được gắn cho tất cả tài nguyên AWS được tạo thông qua Terraform. Biến local.tags được tham chiếu, giả sử biến này được định nghĩa ở một nơi khác trong cấu hình Terraform, chứa danh sách các cặp key-value dùng để gắn thẻ cho tài nguyên.

    • Đọc thêm về Tag : https://kiloai.hashnode.dev/complete-terraform-part-3

  • terraform { ... }: Khối này khai báo các cấu hình chung cho toàn bộ file Terraform.

    • required_providers { ... }: Phần này yêu cầu các nhà cung cấp cần thiết.

      • aws = { ... }: Khối con này khai báo nhà cung cấp AWS.

        • source = "hashicorp/aws": Xác định nguồn của plugin Terraform dành cho AWS, được phát triển bởi HashiCorp, công ty cung cấp Terraform.

        • version = ">= 4.67.0": Yêu cầu phiên bản của plugin AWS phải lớn hơn hoặc bằng 4.67.0. Terraform sẽ kiểm tra và cài đặt phiên bản phù hợp nếu cần.

    • required_version = ">= 1.4.2": Thiết lập phiên bản Terraform tối thiểu cần thiết để chạy cấu hình này. Phiên bản này phải lớn hơn hoặc bằng 1.4.2.

File main.tf thiết lập một số nguồn dữ liệu Terraform để chúng ta có thể truy xuất tài khoản AWS hiện tại và AZ đang được sử dụng.

locals {
  tags = {
    created-by = "eks-workshop-v2"
    env        = var.cluster_name
  }
}

Đoạn code này định nghĩa một biến cục bộ (local) trong Terraform, được sử dụng để tạo một tập hợp các thẻ (tags) sẽ được áp dụng cho các tài nguyên AWS được tạo.

  • locals { ... }: Khối này khai báo một biến cục bộ (local) trong Terraform.

  • tags = { ... }: Biến tags được định nghĩa và gán cho một tập hợp các cặp key-value, đại diện cho các thẻ sẽ được sử dụng.

    • created-by = "eks-workshop-v2": Thẻ created-by được gán giá trị "eks-workshop-v2", có thể được sử dụng để theo dõi nguồn gốc tạo ra tài nguyên.

    • env = var.cluster_name: Thẻ env được gán giá trị của biến var.cluster_name. Biến này được giả định là được định nghĩa ở một nơi khác trong cấu hình Terraform, chứa tên cụm EKS. Việc sử dụng biến này cho phép tạo thẻ env động, lấy giá trị từ biến cluster_name

File vpc.tf sẽ cấu hình và đảm bảo rằng cơ sở hạ tầng VPC của chúng ta được tạo.

locals {
  private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 3, k + 3)]
  public_subnets  = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 3, k)]
  azs             = slice(data.aws_availability_zones.available.names, 0, 3)
}

data "aws_availability_zones" "available" {
  state = "available"
}

Đoạn code cung cấp định nghĩa ba biến cục bộ trong khối locals: private_subnets, public_subnetsazs. Hãy cùng phân tích từng biến và chức năng của nó:

  • data.aws_availability_zones.available.names: Phần này truy xuất danh sách tất cả các vùng khả dụng có sẵn từ nhà cung cấp AWS bằng cách sử dụng nguồn dữ liệu data.aws_availability_zones.

  • .names: Truy cập thuộc tính names của tài nguyên data.aws_availability_zones.available, chứa danh sách tên vùng khả dụng.

  • slice(..., 0, 3): Sử dụng hàm slice để trích xuất một phần cụ thể của danh sách. Ở đây, nó chọn ba phần tử đầu tiên (chỉ mục 0, 1 và 2) từ danh sách AZ có sẵn và gán chúng cho biến azs

  • private_subnets

    • [for k, v in local.azs : ...]: Đây là một kiểu danh sách trong Terraform, tương tự như Python. Nó lặp qua từng phần tử (k, v) trong danh sách local.azs.

      • k: Biểu thị chỉ mục (vị trí) của AZ hiện tại trong danh sách.

      • v: Biểu thị tên của AZ hiện tại.

    • cidrsubnet(var.vpc_cidr, 3, k + 3): Lời gọi hàm này tính toán khối CIDR cho subnet private trong VPC. Nó có ba đối số:

      • var.vpc_cidr: Khối CIDR của toàn bộ VPC, được truyền dưới dạng biến.

      • 3: Số octec cần phân bổ cho subnet, tạo ra subnet /24 với 254 địa chỉ IP có thể sử dụng.

      • k + 3: Tạo khối CIDR duy nhất cho mỗi subnet private một cách Dynamic. Việc thêm 3 vào chỉ mục đảm bảo rằng subnet private có phạm vi khác với subnet public (được giải thích sau).

    • Toàn bộ cách hiểu danh sách tạo ra danh sách các khối CIDR subnet riêng tư, một cho mỗi vùng khả dụng trong danh sách local.azs

  • public_subnets

    • Tương tự như private_subnets, nó sử dụng hiểu danh sách để lặp qua local.azs. Tuy nhiên, lời gọi hàm cidrsubnet chỉ sử dụng hai đối số:

      • var.vpc_cidr: Khối CIDR VPC .

      • 3: Bit subnet cho subnet /24.

      • Ở đây không có bù trừ (k + 3) được thêm vào. Điều này đảm bảo rằng subnet công khai có phạm vi riêng biệt với subnet riêng tư, thường được đặt ở đầu khối CIDR VPC.

  • Hãy cùng phân tích cú pháp và chức năng của:

    • for k, v in local.azs:

      • Phần này trong đoạn code lặp qua biến local.azs. Cấu trúc vòng lặp for trong Terraform tương tự như Python hoặc các ngôn ngữ lập trình khác. Nó lặp qua một tập hợp dữ liệu, gán mỗi phần tử cho hai biến: kv.
  • k: Biểu thị chỉ mục hoặc khóa của phần tử hiện tại trong tập hợp local.azs.

  • v: Biểu thị giá trị của phần tử hiện tại trong tập hợp local.azs. Trong trường hợp này, vì local.azs là một tập hợp các AZ, v sẽ là tên của vùng khả dụng.

    • cidrsubnet(var.vpc_cidr, 3, k + 3):
  • Phần này trong đoạn code áp dụng hàm cidrsubnet cho mỗi phần tử (vùng AZ) trong tập hợp local.azs. Hàm cidrsubnet có ba đối số:

  • var.vpc_cidr: Đối số này chỉ định khối CIDR của VPC trong đó subnet sẽ được tạo. Nó được truyền từ biến var.vpc_cidr.

  • 3: Đối số này cho biết số bit cần phân bổ cho mặt nạ subnet. Giá trị 3 tạo ra subnet /24 địa chỉ IP có thể sử dụng.

  • k + 3: Đối số này tính toán bù trừ động cho khối CIDR của mỗi subnet. Biến k biểu thị chỉ mục của vùng khả dụng hiện tại và việc thêm 3 đảm bảo rằng subnet riêng tư có dải CIDR khác với subnet công khai.

Nhìn chung, dòng code này tạo ra danh sách các block CIDR subnet, một cho mỗi AZs khả dụng trong tập hợp local.azs. Các khối CIDR được tính toán động, đảm bảo rằng subnet riêng tư có dải CIDR khác với subnet công khai.

Dưới đây là minh họa đơn giản về cách thức hoạt động của cách hiểu danh sách:

        local.azs = {
          "us-east-1a": "us-east-1a",
          "us-east-1b": "us-east-1b",
          "us-east-1c": "us-east-1c"
        }

        # Lặp qua các vùng AZs
        for k, v in local.azs:
          # Tính toán subnet CIDR cho mỗi vùng AZ
          subnet_cidr = cidrsubnet(var.vpc_cidr, 3, k + 3)

          # Thêm subnet CIDR vào danh sách
          subnet_cidrs.append(subnet_cidr)

        # Danh sách khối CIDR subnet kết quả
        subnet_cidrs = [
          "10.0.0.0/24",  # us-east-1a private subnet
          "10.0.4.0/24",  # us-east-1b private subnet
          "10.0.8.0/24",  # us-east-1c private subnet
          "10.0.12.0/24", # us-east-1a public subnet
          "10.0.16.0/24", # us-east-1b public subnet
          "10.0.20.0/24", # us-east-1c public subnet
        ]

Ví dụ này minh họa cách cách hiểu danh sách tạo ra các khối CIDR subnet động dựa trên các vùng khả dụng local.azs và cấu hình khối CIDR VPC và mặt nạ subnet được cung cấp.

  •   module "vpc" {
        source  = "terraform-aws-modules/vpc/aws"
        version = "~> 5.1"
    
        name = var.cluster_name
        cidr = var.vpc_cidr
    
        azs                   = local.azs
        public_subnets        = local.public_subnets
        private_subnets       = local.private_subnets
        public_subnet_suffix  = "SubnetPublic"
        private_subnet_suffix = "SubnetPrivate"
    
        enable_nat_gateway   = true
        create_igw           = true
        enable_dns_hostnames = true
        single_nat_gateway   = true
    
        # Manage so we can name
        manage_default_network_acl    = true
        default_network_acl_tags      = { Name = "${var.cluster_name}-default" }
        manage_default_route_table    = true
        default_route_table_tags      = { Name = "${var.cluster_name}-default" }
        manage_default_security_group = true
        default_security_group_tags   = { Name = "${var.cluster_name}-default" }
    
        public_subnet_tags = merge(local.tags, {
          "kubernetes.io/role/elb" = "1"
        })
        private_subnet_tags = merge(local.tags, {
          "karpenter.sh/discovery" = var.cluster_name
        })
    
        tags = local.tags
      }
    

    Đoạn code này thiết lập cấu hình cho VPC (Virtual Private Cloud) và subnet trong EKS cluster trên nền tảng AWS.

  • source: Xác định nguồn của module, trong trường hợp này là module vpc từ registry terraform-aws-modules/vpc/aws với phiên bản khoảng ~> 5.1.

  • Các biến đầu vào (name, cidr, azs, ...) truyền thông tin để cấu hình module VPC.

    • azs: Sử dụng biến local.azs để truyền danh sách AZ.

    • public_subnetsprivate_subnets: Sử dụng các biến cùng tên để truyền danh sách subnet.

    • public_subnet_suffixprivate_subnet_suffix: Thiết lập hậu tố - suffix cho tên "SubnetPublic" và "SubnetPrivate".

  • Các biến cấu hình khác thiết lập các tính năng cho VPC:

    • enable_nat_gateway: Bật NAT Gateway .

    • create_igw: Tạo Internet Gateway.

    • enable_dns_hostnames: Bật tính năng sử dụng hostname DNS cho các tài nguyên.

    • single_nat_gateway: Chỉ sử dụng 1 NAT Gateway (nếu có nhiều AZ). Chú ý chỗ này nên đổi thành false để mỗi AZ sẽ có 1 NAT Gateway riêng

File eks.tf quy định cấu hình cụm EKS của chúng ta, bao gồm Managed Node Group.

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 19.16"

  cluster_name                   = var.cluster_name
  cluster_version                = var.cluster_version
  cluster_endpoint_public_access = true

  cluster_addons = {
    vpc-cni = {
      before_compute = true
      most_recent    = true
      configuration_values = jsonencode({
        env = {
          ENABLE_POD_ENI                    = "true"
          ENABLE_PREFIX_DELEGATION          = "true"
          POD_SECURITY_GROUP_ENFORCING_MODE = "standard"
        }
        nodeAgent = {
          enablePolicyEventLogs = "true"
        }
        enableNetworkPolicy = "true"
      })
    }
  }

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  create_cluster_security_group = false
  create_node_security_group    = false

  eks_managed_node_groups = {
    default = {
      instance_types       = ["m5.large"]
      force_update_version = true
      release_version      = var.ami_release_version

      min_size     = 3
      max_size     = 6
      desired_size = 3

      update_config = {
        max_unavailable_percentage = 50
      }

      labels = {
        workshop-default = "yes"
      }
    }
  }

  tags = merge(local.tags, {
    "karpenter.sh/discovery" = var.cluster_name
  })
}

Đoạn code này định nghĩa một module Terraform tên là eks để cấu hình một cụm Amazon Elastic Kubernetes Service (EKS)

  • source: Chỉ định vị trí của module Terraform. Nó trỏ đến module eks chính thức từ kho lưu trữ terraform-aws-modules.

  • version: Xác định phạm vi phiên bản tương thích cho module

  • cluster_name: Tham chiếu đến biến có tên cluster_name được định nghĩa ở nơi khác trong cấu hình Terraform của bạn. Nó đặt tên cho cụm EKS của bạn.

  • cluster_version: Tương tự như cluster_name, nó tham chiếu đến biến cluster_version để xác định phiên bản Kubernetes mong muốn cho cụm.

  • cluster_endpoint_public_access: Đặt quyền truy cập công khai vào endpoint của cụm EKS . Hãy cân nhắc các tác động bảo mật trước khi bật tính năng này trong môi trường production.

  • cluster_addons: Định nghĩa các chức năng bổ sung cho cụm của bạn. Ở đây, nó cấu hình add-on VPC CNI (Container Network Interface). Có thể cấu hình các add-on bổ sung tương tự trong khối này. (Sẽ có bài viết cụ thể cho các add-ons này)

Với cấu hình đã cho, Terraform sẽ tạo môi trường Workshop với các thành phần sau:

  • Tạo một VPC trải rộng trên các AZs (availability zone).

  • Tạo một cụm EKS.

  • Tạo một kết nối IAM OIDC.

  • Thêm một NodeGroup được quản lý tên "default".

  • Cấu hình VPC CNI để sử dụng (prefix delegation).