AWS

[re:Invent 2024] EKS Auto Mode, 어디까지 Auto일까

<들어가기 앞서>


평소 필자는 EKS 기반의 워크로드들을 운영하면서 아래와 같은 생각들을 종종 했습니다.

– AWS Load Balancer Controller 같이 대중적으로 많이 쓰이는 추가 기능들은 Managed 에드온으로 관리하고 싶다…
– Karpenter 혹은 Cluster Autoscaler 같은 영역도 Managed로 관리할 수는 없을까?

그러다 때마침 AWS re:Invent 2024에서의 EKS Auto Mode 발표를 보고 매우 흥미로웠습니다.

기대했던 부분보다 더 넓은 영역을 관리해줄 수 있는 것처럼 보였기 때문이죠. 그와 동시에 어디까지 자동인데..? 라는 순수한 의문점과 기대가 생겼습니다.🤣

그래서 이 글을 통해 EKS Auto Mode에 대해 살펴보고 실습하면서 평소 EKS에 대해 관심있으신 분들에게 도움이 되고자 합니다.

그러면 바로 시작하겠습니다! 🏃‍➡️🏃‍➡️🏃‍➡️ (실습 단계에선 AWS, EKS 환경에 대한 사전 경험이 필요합니다)

1. 훑어 보기

1) 출시 배경

기존 Amazon EKS는 Kubernetes의 Control Plane을 관리하지만 사용자가 Data Plane의 여러 구성 요소를 스스로 관리해야 됐습니다.

이에 출시된 Amazon EKS Auto Mode는 AWS에서 Kubernetes 클러스터 관리를 획기적으로 간소화하기 위한 새로운 기능이며 EKS 사용자들에게 클러스터 운영의 복잡성을 크게 줄이면서 Kubernetes의 강력한 기능은 최대한 활용할 수 있도록 설계가 되었습니다.

문서에서는 EKS 애드온의 자동 설치 및 관리, GPU 설정, 보안 구성, 비용 최적화 등 다양한 영역을 자동으로 처리를 한다고 나와있습니다.

그러면 관리되는 범위를 조금 더 자세히 살펴볼까요?

2) 관리되는 범위

아래는 EKS 환경을 구성한다고 했을 때, 기본적인 환경 구성 예시 중 하나입니다.


보시는 것처럼 Control Plane을 제외한 Worker Node/Pod 및 Addon의 관리는 온전히 사용자의 몫이였죠.

EKS Auto Mode에서는 위의 빨간색으로 표시된 영역중 에드온과 노드에 대한 부분을 AWS가 관리하게 됩니다.


또한, CoreDNS, Amazon VPC CNI, kube-proxy 같이 주요 에드온들은 파드 형태가 아닌 노드 내부에서 systemd 형태로 실행되어 사용자는 보다 애플리케이션 파드에 대한 관리만 집중적으로 할 수 있게 됩니다.
(반대로 사용자가 직접 커스텀할 수 있는 영역이 좁아진 것이기도 합니다)

3) 특징

그렇다면 어떤 특징들을 갖고 동시에 어떤 제한이 생길까요? 눈에 띄는 주요 특징을 정리해보겠습니다.


Managed Nodegroup과 Auto Mode에서의 전반적인 컴퓨팅 옵션 비교는 해당 링크를 참고해주세요.

컴퓨팅 (참조)

Karpenter

  • 자동으로 노드 스케일링 적용되며 최대 21일의 노드 수명을 적용
  • EKS Auto Mode에서 사용되는 NodeClass의 API 버전 eks.amazonaws.com/v1과 OSS Karpenter에서 사용되는 NodeClass API 버전 karpenter.k8s.aws/v1 이 다름

노드

  • AMI는 Bottlerocket OS기반의 이미지로 동작되며 AWS에 의해서 관리됨 (사용자 정의 불가)
  • 노드에 직접 액세스(SSM, SSH)하거나 소프트웨어 설치 불가
  • NVIDIA 및 Neuron GPU에 대한 별도의 커널 드라이버와 플러그인 제공

로드밸런싱 (참조)

AWS LoadBalancer Controller

  • 기본적으로 IP모드이며 기존 타겟 그룹을 가져올 수 없음

네트워킹 (참조)

VPC CNI

  • 사용자 지정 네트워킹이 지원 불가 (즉, Pod 및 노드의 IP 주소는 동일한 CIDR 블록에서 진행되어야 합니다)
  • 접두사 모드나 Warm IP, Warm Prefix 및 Warm ENI 등의 구성이 불가
    • 기본적으로 파드 IP는 EC2 ENI에 Prefix 모드(/28)로 할당되며 노드당 최대 파드 수는 110개로 제한됩니다.

비용 (참조)

기존 비용 중, EC2 인스턴스(워커 노드) 비용의 12% 정도 추가 비용 발생 (us-west-2 기준)

  • 즉, “기존 비용(=EKS 클러스터 비용 + EC2 인스턴스 비용) + 추가 관리 비용(=EC2 인스턴스 비용*0.12)” 이 청구됩니다.

4) 중간 정리

🤔 왜 등장하게 되었을까

  • 기본적으로 쿠버네티스 클러스터 관리는 복잡합니다.
  • Control Plane의 영역을 관리해주는 Amazon EKS에서도 어려움을 표하는 사용자가 많기도 했고요.
  • 이에 Kubernetes 클러스터 관리의 복잡성을 줄이고 사용자들의 진입 장벽을 낮추고자 등장하게 되었습니다.

💡 어떤 경우에 사용하면 좋을까

  • 인프라 관리에 시간을 덜 쓰고 애플리케이션 개발에 집중하고 싶은 조직, 복잡한 설정 없이 Kubernetes를 운영하고자 하는 조직들이 우선적으로 도입을 고려해볼 수 있겠습니다.

🔒 적용하기 위한 조건

  • Auto Mode 적용을 위한 조건은 각 워크로드마다 상이하겠지만 공통적으로 안정적인 클러스터 버전 업데이트 및 노드 스케일링 위해 Karpenter의 동작 과정 이해는 필수가 되고 추가 관리 비용이 1차 관문으로 될 것 같습니다.
  • 기존 Karpenter, Manged Nodegroup 및 EKS Fargate에서 EKS AutoMode로 전환하는 경우 필요 조건은 해당 문서를 참고해주세요.

2. 경험해 보기

실습을 안해볼 수 없겠죠? 최종적으로 생성할 실습 환경은 아래 아키텍처와 같습니다.


– CloudFormation을 통해 VPC, IAM Role(EKS Clsuter, EKS Node) 생성
– eksctl을 통해 EKS Auto Mode 클러스터 구축

단, 해당 단계를 진행하려면 아래 전제 조건이 필요합니다.


– 관리자 권한이 있는 AWS 계정이 있고 접근 권한이 있는 위치에서 진행 (필자는 mac 로컬 환경에서 진행합니다)
– 필요 설치 도구: Helm 3.9+, kubectl, eksctl 0.195.0+, and AWS Command Line Interface (AWS CLI)

1) 구축

VPC 환경 구축

  • 생성할 VPC 리소스 항목
    1. VPC (CIDR: 10.2.0.0/16)
    2. 4개의 서브넷
      • 2개의 퍼블릭 서브넷 (ap-northeast-2a, ap-northeast-2c) 10.2.0.0/20 10.2.16.0/20
      • 2개의 프라이빗 서브넷 (ap-northeast-2a, ap-northeast-2c) 10.2.128.0/20, 10.2.144.0/20
    3. 인터넷 게이트웨이
    4. NAT 게이트웨이 (ap-northeast-2a)
    5. 라우팅 테이블 (퍼블릭 1개, 프라이빗 2개)
CloudFormation 파일 확인

AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC Infrastructure for EKS'

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.2.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: gsn-eks-vpc

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.2.0.0/20
      AvailabilityZone: ap-northeast-2a
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: gsn-eks-subnet-public1-ap-northeast-2a

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.2.16.0/20
      AvailabilityZone: ap-northeast-2c
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: gsn-eks-subnet-public2-ap-northeast-2c

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.2.128.0/20
      AvailabilityZone: ap-northeast-2a
      Tags:
        - Key: Name
          Value: gsn-eks-subnet-private1-ap-northeast-2a

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.2.144.0/20
      AvailabilityZone: ap-northeast-2c
      Tags:
        - Key: Name
          Value: gsn-eks-subnet-private2-ap-northeast-2c

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: gsn-eks-igw

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: gsn-eks-eip-ap-northeast-2a

  NATGateway:
    Type: AWS::EC2::NatGateway
    DependsOn: AttachGateway
    Properties:
      AllocationId: !GetAtt EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: gsn-eks-nat-public1-ap-northeast-2a

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: gsn-eks-rtb-public

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: gsn-eks-rtb-private1-ap-northeast-2a

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: gsn-eks-rtb-private2-ap-northeast-2c

  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable2

IAM Role 생성

생성할 IAM 리소스 항목EKS Controle Plane과 Data Plane에 연결할 IAM Role을 생성해보겠습니다.

IAM Role신뢰 관계권한 정책설명
ROLE-EKS-CLUSTER-AUTOMODEeks.amazonaws.comAmazonEBSCSIDriverPolicy
AmazonEKSClusterPolicy
AmazonEKSVPCResourceController
ElasticLoadBalancingFullAccess
AmazonEKSVPCResourceController
EKS 클러스터에 연결할 IAM 역할
ROLE-EKS-NODE-AUTOMODEec2.amazonaws.comAmazonEC2ContainerRegistryPullOnly
AmazonEKSWorkerNodeMinimalPolicy
EKS 노드에 연결할 IAM 역할
CloudFormation 파일 확인

AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template to create IAM Roles for EKS Cluster and Node Group'

Resources:
  ROLEEKSClusterAutomode:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'ROLE-EKS-CLUSTER-AUTOMODE'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: eks.amazonaws.com
            Action:
              - 'sts:AssumeRole'
              - 'sts:TagSession'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
        - arn:aws:iam::aws:policy/AmazonEKSComputePolicy
        - arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy
        - arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy
        - arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy

  ROLEEKSNodeAutomode:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'ROLE-EKS-NODE-AUTOMODE'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy

Outputs:
  ClusterRoleArn:
    Description: ARN of the EKS Cluster Role
    Value: !GetAtt ROLEEKSClusterAutomode.Arn
    Export:
      Name: EKSClusterRoleArn

  NodeRoleArn:
    Description: ARN of the EKS Node Role
    Value: !GetAtt ROLEEKSNodeAutomode.Arn
    Export:
      Name: EKSNodeRoleArn

클러스터 구축

아래 yaml 파일을 확인하고 eksctl 명령어를 실행하여 새로운 EKS Auto Mode 클러스터를 생성해보겠습니다.

eks-auto-cluster.yaml 파일 확인

# A sample ClusterConfig file that creates a cluster with Auto Mode enabled.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: GSN-AUTO-EKS
  region: ap-northeast-2
  version: "1.30"

# CloudFormation으로 생성된 IAM Role ARN 기입 (Cluster)
iam:
  serviceRoleARN: arn:aws:iam::000000000000:role/ROLE-EKS-CLUSTER-AUTOMODE

# CloudFormation으로 생성된 VPC, Subnet id 기입
vpc:
  id: "vpc-0775dcbf137ccfe00"
  clusterEndpoints:
    publicAccess: true
    privateAccess: false
  subnets:
    private:
      ap-northeast-2a:
          id: "subnet-0fb22b4b76037a045"
      ap-northeast-2c:
          id: "subnet-008be1cb5e3d63db2"

accessConfig:
  bootstrapClusterCreatorAdminPermissions: true # EKS 클러스터 생성자의 접근 권한 유무
  authenticationMode: API_AND_CONFIG_MAP
  accessEntries:
    - principalARN: arn:aws:iam::000000000000:user/gsn-user # AWS 콘솔 사용자 EKS 접근 허용
      accessPolicies:
        - policyARN: arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
          accessScope:
            type: cluster

autoModeConfig:
  # defaults to false
  enabled: true
  # optional, defaults to [general-purpose, system].
  # To disable creation of nodePools, set it to the empty array ([]).
  nodePools: [general-purpose, system]
  # CloudFormation으로 생성된 IAM Role ARN 기입 (Node)
  nodeRoleARN: arn:aws:iam::000000000000:role/ROLE-EKS-NODE-AUTOMODE

eksctl 명령어 실행

# 클러스터 생성
eksctl create cluster -f eks-auto-cluster.yaml

2) 생성된 자원 확인

파드 및 노드

해당 명령어로 전체 네임스페이스 단위에서 파드 조회를 하면 아래와 같이 아무 리소스도 없는 모습을 볼 수 있습니다.


# 전체 네임스페이스의 파드 조회
kubectl get pod -A

노드 또한 단 하나의 노드도 없는 모습입니다.
이로써 사용자는 서비스할 파드들에 대해서만 관리하면 된다는 것을 체감할 수 있습니다💡


# 노드 조회
kubectl get node

Karpenter 리소스

추가로 Karpenter에서 기본적으로 생성되는 Nodeclass, NodePool 까지만 더 확인해보겠습니다.


🚨 미리 알려드립니다! 아래 기본적으로 생성되는 NodePool, NodeClasss는 수정이 불가합니다!
(수정한다고 해도 다시 원상복구되니 참고해주세요)
NodeClass default

---
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
  name: default
  labels:
    app.kubernetes.io/managed-by: eks
spec:
  # EBS 볼륨 설정
  ephemeralStorage:
    iops: 3000          # 초당 I/O 작업 수
    size: 80Gi          # 스토리지 크기
    throughput: 125     # 처리량(MiB/s)
  
  networkPolicy: DefaultAllow        # 기본 네트워크 정책 허용
  networkPolicyEventLogs: Disabled   # 네트워크 정책 이벤트 로깅 비활성화
  role: ROLE-EKS-NODE-AUTOMODE       # 노드에 적용될 IAM 역할
  
  # 보안 그룹 설정
  securityGroupSelectorTerms:
  - id: sg-03795c3ae3fec1252
  
  snatPolicy: Random    # SNAT 정책 설정
  
  # 서브넷 설정 (다중 AZ 구성)
  subnetSelectorTerms:
  - id: subnet-0fb22b4b76037a045
  - id: subnet-008be1cb5e3d63db2

AMI Selector가 없으며 subnetSelectorTerms, securityGroupSelectorTerms, Network Policy 설정을 지원하고 있네요.

기존 EC2NodeClass에 비해 간소화된게 느껴집니다.

NodePool general-purpose, system

---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  labels:
    app.kubernetes.io/managed-by: eks    # EKS에 의해 관리됨을 나타내는 라벨
  name: general-purpose                  # 일반 용도의 노드풀 이름
spec:
  # 노드 중단 관련 설정
  disruption:
    budgets:
    - nodes: 10%                         # 동시 중단 가능한 최대 노드 비율
    consolidateAfter: 30s                # 노드 통합 전 대기 시간
    consolidationPolicy: WhenEmptyOrUnderutilized  # 노드가 비었거나 활용도가 낮을 때 통합
  
  template:
    spec:
      expireAfter: 336h                  # 노드 만료 시간 (2주)
      nodeClassRef:                      # NodeClass 참조 설정
        group: eks.amazonaws.com
        kind: NodeClass
        name: default
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values: [on-demand]              # 온디맨드 인스턴스만 사용
      - key: eks.amazonaws.com/instance-category
        operator: In
        values: [c, m, r]                # c: 컴퓨팅 최적화, m: 범용, r: 메모리 최적화 인스턴스
      - key: eks.amazonaws.com/instance-generation
        operator: Gt
        values: ["4"]                    # 5세대 이상 인스턴스 사용
      - key: kubernetes.io/arch
        operator: In
        values: [amd64]                  # AMD64 아키텍처만 지원
      - key: kubernetes.io/os
        operator: In
        values: [linux]                  # Linux 운영체제만 지원
      terminationGracePeriod: 24h0m0s    # 노드 종료 전 유예 기간
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  labels:
    app.kubernetes.io/managed-by: eks
  name: system
spec:
  # 노드 중단 관련 설정
  disruption:
    budgets:
    - nodes: 10%                     # 동시에 중단될 수 있는 최대 노드 비율
    consolidateAfter: 30s            # 노드 통합 전 대기 시간
    consolidationPolicy: WhenEmptyOrUnderutilized  # 노드가 비어있거나 활용도가 낮을 때 통합

  template:
    spec:
      expireAfter: 336h                  # 노드 만료 시간 (2주)
      nodeClassRef:                      # NodeClass 참조 설정
        group: eks.amazonaws.com
        kind: NodeClass
        name: default
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values: [on-demand]              # 온디맨드 인스턴스만 사용
      - key: eks.amazonaws.com/instance-category
        operator: In
        values: [c, m, r]                # c: 컴퓨팅 최적화, m: 범용, r: 메모리 최적화 인스턴스
      - key: eks.amazonaws.com/instance-generation
        operator: Gt
        values: ["4"]                    # 5세대 이상 인스턴스 사용
      - key: kubernetes.io/arch
        operator: In
        values: [amd64, arm64]           # AMD64, ARM64 아키텍처 모두 지원
      - key: kubernetes.io/os
        operator: In
        values: [linux]                  # Linux 운영체제만 지원
      # system 전용 워크로드 설정
      taints:                            
      - effect: NoSchedule
        key: CriticalAddonsOnly          # CriticalAddonsOnly taint를 통해 일반 워크로드의 스케줄링을 방지

      terminationGracePeriod: 24h0m0s    # 노드 종료 전 유예 기간 (draining)

기본적으로 weight=0 이며 Karpenter의 NodePool과 동일합니다. (EKS Auto Mode에서 지원되는 label은 해당 링크를 참조해주세요)

두 개의 노드풀 모두 유사하나 system 노드풀에서의 차이점은 아래와 같습니다


– taint CriticalAddonsOnly 설정
– ARM64 아키텍처도 지원 (amd64, arm64

3) 워크로드 실행

그렇다면 워크로드(파드 등)를 실행시켜 실제로 어떻게 동작하는지 확인해봐야겠죠?

샘플 애플리케이션 배포

다음 Kubernetes 리소스(Namespace, Deployment, Service)를 검토하고 game-2048-service.yaml로 저장하여 배포해 보겠습니다.

game-2048-service.yaml

# game-2048-service.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: game-2048
  labels:
    name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game-2048
  name: deployment-2048
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048
  replicas: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2048
    spec:
      containers:
        - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
          imagePullPolicy: Always
          name: app-2048
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "1"       # 1vCPU
              memory: "2Gi"  # 2GiB
---
apiVersion: v1
kind: Service
metadata:
  namespace: game-2048
  name: service-2048
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
  selector:
    app.kubernetes.io/name: app-2048

(배포 이후 예상)


game-2048-service.yaml 파일에선 파드마다 request.cpu=1, request.memory=2Gi로 설정되어 있고 Deployment의 총 리소스 요청량은 3vCPU, 6Gi 인 것을 유추할 수 있습니다.

그렇다면 “Karpenter는 리소스 요청량에 가장 Fit하게 C타입 + large(2) 혹은 xlarge(1) 스펙을 갖는 노드를 가져오지 않을까?” 라는 개인적인 예상을 해봅니다.

이제 클러스터에 배포를 해보겠습니다!

kubectl apply -f game-2048-service.yaml	

Kubernetes event를 확인하여 새 노드 생성 관련 이벤트를 자세히 확인해볼 수도 있습니다.

kubectl get events -w --sort-by '.lastTimestamp'

생성된 노드 및 파드 확인

kubectl get pod -o wide -n game-2048
kubectl get node -o wide

결과는 C5a.xlarge (4vCPU, 8GiB) 의 인스턴스 하나만 실행되네요:)

로드밸런서 배포

이어서 Kubernetes 리소스(IngressClass, Ingress)를 검토하고 game-2048-ingress.yaml로 저장하여 배포해 보겠습니다.

game-2048-ingress.yaml

# game-2048-ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  namespace: game-2048
  labels:
    app.kubernetes.io/name: LoadBalancerController
  name: alb
spec:
  controller: eks.amazonaws.com/alb
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game-2048
  name: ingress-2048
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    # CloudFormation으로 생성된 Public Subnet 입력
    alb.ingress.kubernetes.io/subnets: subnet-05e52aea3d8b92bd6, subnet-001a37e7fa004843b
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: service-2048
                port:
                  number: 80

.

ALB가 생성되고 대상 파드를 등록할 때까지 2~3분 정도 대기한뒤 아래 명령어를 통해 ALB 엔드포인트를 가져옵니다.

kubectl get ingress -n game-2048

웹 브라우저 접속 및 서비스 동작 확인

NodePool(system)에 에드온 실행

이번엔 클러스터의 리소스 사용량을 확인하기 위한 metric-serverhelm을 통해 배포하고 배포 위치를 NodePool(system) 노드로 지정해보겠습니다.

따라서, NodePool(system) 노드에 스케줄링되기 위해 nodeSelector, toleration 설정을 추가하였습니다. (참고)

Helm Install

# metrics-server repo 추가
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/

# helm install
helm upgrade --install metrics-server metrics-server/metrics-server \
  --namespace kube-system \
  --set nodeSelector."karpenter\.sh/nodepool"=system \
  --set 'tolerations[0].key=CriticalAddonsOnly' \
  --set 'tolerations[0].operator=Exists'

# helm delete (참고)
helm uninstall metrics-server -n kube-system

.

아래와 같이 NodePool(system)에서 노드가 생성되고 그 안의 metric-server 파드가 실행중인 것을 확인할 수 있습니다.

kubectl get pod -n kube-system -o wide
kubectl top node

Custom NodePool/NodeClass 생성

기본적인 구성으로 운영이 가능한 것을 확인했지만 실제 환경에서는 특정 운영 요구 사항이 발생할 수 있죠.

예를 들어 노드별 OS 분리, 스팟 구성, 팀별 분리, 보안 요건, 노드풀 마이그레이션 등 여러 조건에서 필요해질 수 있는 부분이라고 생각합니다.

EKS Auto Mode에서도 해당 경우들을 고려하여 추가 구성을 통해 잘 동작하는지 확인을 해보겠습니다.

스팟 구성을 예시로 들어 NodePool/NodeClass를 추가로 생성해보았습니다.

.

(1) 스팟 워크로드 구성

테스트 목적이므로 작성하였고 반드시 이렇게 하실 필요는 없습니다.


NodeClass(spot-class)는 기존 default와 값이 동일하지만 기존 label 값을 gsn으로 바꾸어 봤습니다.
NodePool(spot-purpose)는 weight=10 을 설정하여 스팟 노드가 우선적으로 스케일링되도록 하고 expireAfter=480h 로 노드 만료 시간을 늘려보았습니다.

다음 Kubernetes 커스텀 리소스(NodeClass, NodePool)를 검토하고 spot-nodeclass_nodepool.yaml로 저장하여 배포해 보겠습니다.

spot-nodeclass_nodepool.yaml

# spot-nodeclass_nodepool.yaml
---
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
  name: spot-class
  labels:
    app.kubernetes.io/managed-by: gsn
spec:
  # EBS 볼륨 설정 (동일)
  ephemeralStorage:
    iops: 3000
    size: 30Gi
    throughput: 125
  # networkPolicy 설정 (동일)
  networkPolicy: DefaultAllow
  networkPolicyEventLogs: Disabled
  # 노드에 적용될 IAM 역할 (동일)
  role: ROLE-EKS-NODE-AUTOMODE
  # 보안 그룹 설정 (동일)
  securityGroupSelectorTerms:
  - id: sg-03795c3ae3fec1252
  snatPolicy: Random    
  # 서브넷 설정 (동일)
  subnetSelectorTerms:
  - id: subnet-0fb22b4b76037a045
  - id: subnet-008be1cb5e3d63db2
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot-purpose
  labels:
    app.kubernetes.io/managed-by: gsn
spec:
  weight: 10                             # 노드풀 가중치 설정 (우선적으로 Spot이 생성되도록 설정)
  disruption:
    budgets:
    - nodes: 10%
    consolidateAfter: 30s
    consolidationPolicy: WhenEmptyOrUnderutilized
  template:
    spec:
      expireAfter: 480h                  # 노드 만료 시간 (기존 설정과 차이를 두기위해 해당 값 수정, 21일 초과 불가)
      nodeClassRef:
        group: eks.amazonaws.com
        kind: NodeClass
        name: spot-class
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values: [spot]                   # 스팟 인스턴스만 사용 (Spot 설정)
      - key: eks.amazonaws.com/instance-category
        operator: In
        values: [c, m, r]
      - key: eks.amazonaws.com/instance-generation
        operator: Gt
        values: ["4"]
      - key: kubernetes.io/arch
        operator: In
        values: [amd64]
      - key: kubernetes.io/os
        operator: In
        values: [linux]
      terminationGracePeriod: 24h0m0s

.

이제 해당 구성을 적용 및 성공적으로 생성되었는지 확인해보겠습니다!

kubectl apply -f spot-nodeclass_nodepool.yaml

잘 생성된 것을 확인할 수 있습니다.

kubectl get nodeclasses.eks.amazonaws.com
kubectl get nodepools.karpenter.sh -o wide

그렇다면 기존 생성하였던 Deployment 파드 수를 증가시켜 실제 스팟 노드가 추가로 생성되는지 확인해 보아야 겠죠?

kubectl scale deployment/deployment-2048 -n game-2048 --replicas=6

아래 보시는 것처럼 스팟 노드가 우선적으로 스케일링되는 모습을 볼 수 있습니다.

kubectl get pod -o wide -n game-2048
kubectl get node -o wide

4) 버전 업데이트

마지막으로 EKS 버전 업데이트까지 진행해보도록 하겠습니다.


EKS Auto Mode에서는 Karpenter Drift feature gate 기능이 활성화되어 있으므로 Control Plane 버전 업데이트를 진행하면 이를 감지하여 기존 노드의 프로비저닝을 해제하고 Control Plane 버전에 맞는 새로운 노드로 교체되기 시작하게 됩니다.

🚨 교체 기간동안 최소한의 지속적 운영을 위해 PDB를 추가하여 Karpenter가 PDB를 존중하는지도 확인해보겠습니다.

✅ PDB 생성

다음 Kubernetes 리소스(PodDisruptionBudget)를 검토하고 game-2048-pdb.yaml로 저장하여 배포해 보겠습니다.

game-2048-pdb.yaml

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb-2048
  namespace: game-2048
spec:
  minAvailable: 1 # 최소 1개의 파드는 실행
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048

Upgrade EKS Control Plane

작성했던 eks-auto-cluster.yaml 파일에서 버전을 1.31로 변경하고 eksctl 명령어를 입력합니다.

eks-auto-cluster.yaml

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: GSN-AUTO-EKS
  region: ap-northeast-2
  version: "1.31" # 변경(v1.30→v1.31)
... # 이하 동일

eksctl upgrade cluster -f eks-auto-cluster.yaml --approve

완료되면 콘솔창에서도 Control Plane 버전이 1.31로 변경된 것을 확인할 수 있습니다. (약 10분 소요)

Karpenter Drift 확인

Control Plane 버전 업데이트가 완료되었으므로 이를 감지하여 노드가 교체되는지 확인합니다.거의 1분만에 바로 Drift를 인지하고 새로운 노드들이 올라오는 모습입니다.

해당 명령어로 노드 상태 및 파드 이동 상황을 모니터링 해봅니다.

kubectl get nodes -A -o wide --watch
kubectl get pods -A -o wide --watch

기존 노드로 새 파드가 스케줄링 되는 것을 방지하기 위해 cordon 상태(SchedulingDisabled)가 되고 쿠버네티스 Eviction API를 사용하여 파드가 제거되는 모습을 볼 수 있습니다.

PDB가 적용되어 모든 파드가 한번에 새 노드로 이동되지 않고 최소 파드 1개 이상은 기존 노드에 유지되며 단계적으로 교체되는 모습을 볼 수 있습니다.

아래는 결과 화면 입니다.

5) 실습 종료

향후 비용이 발생하지 않도록 해당 게시물의 일부로 생성된 리소스를 삭제해주세요!

EKS 클러스터 삭제

eksctl delete cluster -f eks-auto-cluster.yaml --disable-nodegroup-eviction

VPC, IAM 삭제

AWS Console에서 생성된 CloudFormation 스택을 찾아 삭제시켜줍니다.

3. 정리

  • EKS Auto Mode는 AWS Kubernetes 생태계에 있어 중요한 전환점이 될 수 있을 것 같습니다.
  • 기존 EKS가 가지고 있던 운영상의 복잡성과 전문성 요구 문제를 상당 부분 해결하면서도, Kubernetes의 핵심 기능은 그대로 유지하고 있기 때문입니다.
  • 하지만, EKS 환경에 대한 이해도가 충분하고 Right Sizing이 이미 잘 되어있다면 필수적인 요소는 아니라고 판단됩니다.
  • 특히 기존 비용의 약 10%에 해당되는 추가 비용은 인프라 규모가 커질 수록 더욱 부담이 되니까요.
  • Kubernetes를 처음 접하는 팀부터 수 년간의 운영 경험이 있는 팀까지 모두에게 가치를 제공해 줄 수 있는 EKS Auto Mode. 여러분의 생각은 어떠신가요? 이것으로 글을 마치겠습니다. 감사합니다🙇‍♂️

5/5 - (평가 개수 : 4)

필자: 이 도원

전체 게시물수 : 1

전체 조회수 : 324

게시물 공유하기