iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0
Cloud Native

EKS in Practice:IaC × GitOps 實戰 30 天系列 第 8

[Day 8] 實戰:Build EKS Cluster

  • 分享至 

  • xImage
  •  

前言

昨天我們把 VPC 架起來,等於把基礎網路的地基鋪好。今天要正式進入 EKS 的重點:建立 EKS Control Plane 與 Worker Node Group。在 Kubernetes 的世界裡,Control Plane 就像「大腦」,負責 API 與資源調度;Worker Node 則是「手腳」,負責實際跑 Pod。

如果只建了 Control Plane 沒有 Node,那集群還是空的;反之,如果只有 Node 沒有 Control Plane,它們就只是一群無法被調度的 EC2。今天的目標,就是透過 Terraform 把兩者一次建好,讓我們的第一個完整 EKS Cluster 正式上線。

Terraform 建立 EKS Cluster

和建立 VPC 時一樣,要自己建立所有 EKS 相關資源不是不行,但是會花費太多時間盤點需要開的資源。因此今天也會採用社群維護的 module,使用的是 terraform-aws-eks。接下來我會針對程式碼逐段作說明:

Key Point 1: Public / Private Endpoint

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"
  
  # This module always follows the latest version.
  # https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions-standard.html
  cluster_version                 = "1.32"
  cluster_name                    = var.eks_name
  #### KEY POINT 1
  cluster_endpoint_private_access = true
  cluster_endpoint_public_access  = true
  cluster_endpoint_public_access_cidrs = [
    "0.0.0.0/0"
  ]
  cluster_ip_family         = "ipv4"
  cluster_service_ipv4_cidr = var.eks_service_cidr

  vpc_id      = data.aws_vpc.this.id
  subnet_ids  = data.aws_subnets.private_subnets.ids
  enable_irsa = true
  # ...(下段續)

現在的 EKS 同時開啟 public 及 private endpoint,主要使用上的差異會如下表所示:

in-cluster request kubectl request
only public 不走 VPC,走 AWS 內網 可以從 Internet 存取
public + private 走 VPC (private endpoint) 可以從 Internet 存取
only private 走 VPC (private endpoint) 只能從 VPC 內連線

這邊主要是想要討論 public / private access endpoint 的選擇,個人認為是兩個都開比較適合,原因是:

  1. AWS 每半年會更新一次 EKS(包含 control plane/RBAC/IAM … etc.):如果只開 private endpoint,之後要處理升級或除錯時,都必須先想辦法進到 VPC 裡操作,徒增麻煩。兩個 endpoint 都開,可以避免這種阻塞情況。
  2. 可以降低跳到 VPC 裡面時的 attacking surface:有些人會覺得「乾脆只開 private,比較安全」。但實際上這代表你需要準備 bastion host 或 VPN 來進入 VPC,等於多暴露一個進入點。如果兩個都開,日常操作直接走 public endpoint,就不需要額外維護這些「跳板」,反而能少掉潛在風險。
  3. 跳到 VPC 裡面的機制要額外維運:維護一套「進 VPC」的機制(例如 VPN、Direct Connect、堡壘機)並不是零成本:你要配置、監控、更新,還要處理人員存取權限。對團隊來說,這些工作量不小。兩個 endpoint 同時開啟,可以大幅減少這方面的維運負擔。

Key Point 2: Addon 設定

  # ...(續上一段)
  cluster_addons = {
    # DaemonSet: aws-node
    vpc-cni = {
      addon_version            = "v1.19.3-eksbuild.1"
      service_account_role_arn = module.irsa_vpc_cni.iam_role_arn
    }
    # Deployment: coredns
    coredns = {
      addon_version = "v1.11.4-eksbuild.2"
      configuration_values = jsonencode({
        tolerations = [
          {
            key      = "SystemOnly"
            operator = "Exists"
          }
        ]
      })
    }
    # DaemonSet: ebs-csi-node & Deployment: ebs-csi-controller
    aws-ebs-csi-driver = {
      addon_version            = "v1.40.1-eksbuild.1"
      service_account_role_arn = module.irsa_ebs_csi.iam_role_arn
    }
    kube-proxy = {
      addon_version = "v1.32.0-eksbuild.2"
    }
  }
  # ...(下段續)

可以在 addon 指定 toleration 和 QoS 之類的設定(像是在 coredns 裡面的 configuration_values),在這個 eks module 和 AWS console 上都有這些細部的 addon 設定可以做調整。

Key Point 3: Node Group Default 設定

這段是所有 node group 共享的 default 設定,另外在 block_device_mappings 區塊,因為公司內部有導入資安掃描工具,其中有項規則是 EBS 必須要加密,因此會在 node group default 設定加上加密的設置

  # ...(續上一段)
  # https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/modules/eks-managed-node-group/variables.tf
  # this block is default setting value for other node groups
  eks_managed_node_group_defaults = {
    subnet_ids                 = data.aws_subnets.private_subnets.ids
    create_launch_template     = true
    use_custom_launch_template = true
    update_config = {
      max_unavailable_percentage = 25
    }
    block_device_mappings = {
      xvda = {
        device_name = "/dev/xvda"
        ebs = {
          volume_size           = 20
          volume_type           = "gp2"
          encrypted             = true
          delete_on_termination = true
        }
      }
    }
  }

  eks_managed_node_groups = {
    general = {
      name           = "general"
      min_size       = 2
      max_size       = 2
      desired_size   = 2
      disk_size      = 20
      capacity_type  = "SPOT"
      instance_types = ["t4g.medium", "t4g.small"]
      ami_type       = "AL2_ARM_64"
      labels = {
        "usage" = "general"
      }
    }
  }
  # ...(下段續)

Key Point 4: Security Groups 設置

這個 eks module 預設會建立:

  1. 掛在 EKS owned ENI 上的 cluster security group:只允許 node security group 的 443 inbound
  2. 掛在 node groups 上的 node security group:允許
    (1) cluster security group 的 443 inbound
    (2) API server ↔ kubelet 的 10250 inbound
    (3) CoreDNS 所需要的 self(both tcp/udp) 53 inbound

而我們後面用 Karpenter 建立新節點時,需要給新結點掛上同樣的 node security group,用以確保 AWS managed node group 和 Karpenter 長出來的節點可以透過相同的 security groups 設定,來打通網路。

此外,在後續使用 Load balancer controller 建立 NLB 時,因為 NLB 預期 node 只會有一個 security group 包含 kubernetes.io/cluster/${cluster_name} 這個 tag;但是 cluster security group 和 node security group 都會包含這個 tag,導致當節點身上同時有 cluster/node security group 時會無法註冊 nginx(也就是 Load Balancer 的 target),因此我們必須指定節點身上都只有 node security group,並且能夠與 cluster security group 互相溝通。

  # ...(續上一段)
  node_security_group_tags = {
    // karpenter find out which sg should be attach according to this tag
    "karpenter.sh/discovery" = var.eks_name
  }
  // without this rule, kubectl proxy might not work properly
  node_security_group_additional_rules = {
    AllowAllTrafficFromCluster = {
      description                   = "Allow all traffic from cluster"
      protocol                      = "all"
      from_port                     = -1
      to_port                       = -1
      type                          = "ingress"
      source_cluster_security_group = true
    }
  }
  # ...(下段續)

Key Point 5: EKS module v20.x

最後是從 EKS module v19 升版到 v20 的設置:

  # ...(續上一段)
  # Upgrade to 20.x
  kms_key_enable_default_policy = true
  authentication_mode           = "API"
  access_entries                = local.access_entries
}

AWS 表示 aws-auth ConfigMap 已被 deprecated。此外,在 access entry release 前建立的 cluster,設定 auth 的方式只能從 ConfigMap 改成 ConfigMap & API(access entry),而 ConfigMap & API 只能改成 API,沒辦法退回 ConfigMap

至於為什麼 aws-auth ConfigMap 不是一個好的權限管理方式,主要是因為手動編輯容易出錯,而且在新增角色的步驟上非常繁瑣,詳細的設置可以參考我之前寫過的文章:EKS tf module v20 Survey

Terraform Apply 流程

這裡要注意,Control Plane 建立時間大約 10–15 分鐘,加上 Node Group 之後,可能會接近 20 分鐘。過程中 Terraform 會先建立 Cluster,再建立 Node IAM Role,最後才建立 Node Group 並把它們註冊進 Cluster。

驗證

Terraform 建立完成後,我們可以接著更新本地端的 kubeconfig

aws eks update-kubeconfig --name eks-in-practice --alias eks-in-practice

接著測試連線:

$ kubectl cluster-info
# 會顯示 API Server 與 CoreDNS 的 endpoint:
Kubernetes control plane is running at https://XXXXXEKSIDXXXX.xxx.ap-northeast-1.eks.amazonaws.com
CoreDNS is running at https://XXXXXEKSIDXXXX.xxx.ap-northeast-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

接著預期可以看到 cluster 內的節點們:

$ kubectl get nodes

NAME                                             STATUS   ROLES    AGE    VERSION
ip-10-0-2-xxx.ap-northeast-1.compute.internal   Ready    <none>   31h    v1.32.1-eks-5d632ec
ip-10-0-2-xxx.ap-northeast-1.compute.internal   Ready    <none>   136d   v1.32.1-eks-5d632ec
...

代表 Worker Node 已經成功註冊進 EKS Control Plane 囉!

結語

到這裡,我們已經完成了 EKS Cluster 的建立:不只是 Control Plane,還包括能實際承載 workload 的 Node Group。透過 Terraform module,我們把許多繁瑣的設定(像是 endpoint、addon、security group 與 node group 預設值)統一自動化管理,讓整個建置流程更簡潔、更穩定。

不過,EKS 叢集建起來之後,還有一個很關鍵的問題:Pod 要怎麼安全地存取 AWS 資源? 例如 VPC CNI、EBS CSI Driver、Load Balancer Controller 這些元件,都需要跟 AWS API 打交道。明天我們就會進一步介紹 IAM Role for Service Account (IRSA) 的概念,看看怎麼把 Pod 的 ServiceAccount 和 IAM Role 綁在一起,讓 Pod 能安全地取得所需的最小權限。


上一篇
[Day 7] 實戰:Build VPC with Terraform module
系列文
EKS in Practice:IaC × GitOps 實戰 30 天8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言