CloudNet@ Team Study/EKS Workshop 4th Cohort

EKS Nodes Upgrade : Managed NodeGroup In-Place Upgrade

"Everything about infra" 2026. 5. 11. 15:11
포스팅은 CloudNet@팀 서종호(Gasida)님이 진행하시는
AWS EKS Workshop Study 내용을 참고하여 작성합니다.

 

이번 강의는 AWS 정영준님께서

EKS 클러스터 업그레이드의 모범 사례에 대해서

열강 해주셨는데요, 오늘은 NodeGroup Upgrade 관련하여

포스팅을 해보도록 하겠습니다.

 

 

 

이번 실습에서는 아래 내용을 다루려고 합니다.

컨트롤 플레인과 Addon 업그레이드가 끝난 지금, 클러스터의 노드들은 아직 전부 1.30입니다.
노드그룹별로 업그레이드 방식이 달라지기 때문에 먼저 현재 클러스터에 어떤 노드그룹이 있고
각각 왜 다르게 다뤄야 하는지 짚고 가겠습니다.

 

01. Managed Node Group 업그레이드 시 내부 동작

Managed Node Group 업그레이드는 AWS가 4단계로 자동 처리합니다.

사용자가 Terraform apply를 실행하면 이 과정이 백그라운드에서 순서대로 진행됩니다.

 

Step1. Setup

노드 그룹에 연결된 Auto Scaling Group의 새 Launch Template 버전을 생성합니다.

대상 AMI 또는 커스텀 Launch Template 변경 사항이 여기에 반영됩니다.

이후 updateConfig에 정의된 max_unavailable_percentage 값을 기준으로

병렬 업그레이드할 최대 노드 수를 결정합니다.

 

Step2. Scale Up

Auto Scaling Group의 최대/원하는 크기를 일시적으로 늘립니다. 새 버전의 노드가 Ready 상태가 될 때까지

대기하고, 기존 노드를 Unschedulable로 표시해 로드 밸런서에서 제거합니다.

 

Step3. Upgrade

max_unavailable 수만큼 기존 노드를 선택해 drain합니다. 이 시점에 PDB가 설정되어 있으면

drain이 PDB를 존중합니다. 15분 내에 파드가 축출되지 않으면 PodEvictionFailure 에러가 발생합니다.

모든 파드가 축출된 후 노드를 cordon하고 Auto Scaling Group에 종료 요청을 보냅니다.

이전 버전 노드가 모두 교체될 때까지 반복합니다.

 

Step4. Scale Down

업그레이드 전 원래 크기로 Auto Scaling Group을 되돌립니다.

이 흐름 덕분에 Managed Node Group 업그레이드 중에 서비스가 중단되지 않습니다.
단, PDB가 없는 파드는 drain 시 즉시 축출되므로 전 포스팅에서 설정한 PDB가 이 단계에서 실제로 역할을 합니다.

 

실습 환경의 노드그룹 구성

현재 클러스터에는 두 개의 Managed Node Group이 있습니다. base.tf에서 확인합니다.

terraform state list | grep node_group

# base.tf
eks_managed_node_group_defaults = {
  cluster_version = var.mng_cluster_version  # 현재 "1.30"
}

eks_managed_node_groups = {
  initial = {
    instance_types = ["m5.large", "m6a.large", "m6i.large"]
    min_size     = 2
    max_size     = 10
    desired_size = 2
    update_config = {
      max_unavailable_percentage = 35
    }
    # cluster_version 미지정 → eks_managed_node_group_defaults 값 사용
  }

  blue-mng = {
    instance_types  = ["m5.large", "m6a.large", "m6i.large"]
    cluster_version = "1.30"  # 버전 직접 고정
    min_size     = 1
    max_size     = 2
    desired_size = 1
    labels = { type = "OrdersMNG" }
    subnet_ids = [module.vpc.private_subnets[0]]  # 단일 AZ 고정
    taints = [{
      key    = "dedicated"
      value  = "OrdersApp"
      effect = "NO_SCHEDULE"
    }]
  }
}

두 노드그룹의 버전 관리 방식이 다릅니다.

NodeGroup AMI 방식 버전 관리
initial EKS 관리형 기본 AMI mng_cluster_version 변수를 따름
blue-mng EKS 관리형 기본 AMI cluster_version = "1.30" 직접 고정

 

initial 노드그룹은 범용 워크로드용입니다.

cluster_version을 명시하지 않아 eks_managed_node_group_defaults의 mng_cluster_version 변수를 따릅니다.
변수 하나만 바꾸면 업그레이드가 트리거됩니다.

blue-mng 노드그룹은 Orders 앱 전용입니다.

EBS PVC를 사용하는 orders-mysql이 여기 배치되어 있고, 단일 AZ에 고정되어 있으며,
dedicated=OrdersApp:NoSchedule Taint로 다른 워크로드가 들어오지 못하게 격리되어 있습니다.

cluster_version = "1.30" 이 코드에 직접 박혀 있어서 mng_cluster_version 변수 변경의 영향을 받지 않습니다.
이 노드그룹은 In-Place가 아닌 Blue/Green 방식으로 업그레이드해야 하고, 다음 섹션에서 따로 다룹니다.

 

이번 실습에서는 두 가지 시나리오를 동시에 검증합니다.

시나리오 노드 그룹 업그레이드 방법
기본 AMI initial mng_cluster_version 변수를 1.31로 변경
커스텀 AMI custom (신규 생성) ami_id 변수를 1.31 AMI ID로 직접 지정

 

왜 커스텀 노드그룹을 일부러 만드는가?

이건 실제 운영에 필요한 노드그룹이 아니라 기본 AMI 방식과 커스텀 AMI 방식의 업그레이드 동작 차이를

한 실습 안에서 직접 비교하기 위한 것입니다.

 

실제 운영 환경에서 보안 요건이나 컴플라이언스 때문에 EKS Optimized AMI를 그대로 쓰지 못하고 자체 AMI를

구워서 쓰는 팀이 꽤 있습니다. 이 경우 ami_id를 직접 지정하게 되는데, 업데이트 시 동작이 기본 AMI와 다릅니다.

  • 기본 AMI 방식 (initial) : mng_cluster_version 변수만 바꾸면 EKS가 알아서 해당 버전의 최신 Optimized AMI를 선택해 롤링 업데이트
  • 커스텀 AMI 방식 (custom) : ami_id가 직접 지정되어 있어서 mng_cluster_version 변경에 반응하지 않음. 1.31용 AMI ID를 직접 조회해서 변수를 교체해줘야만 업그레이드가 진행됨

이 차이를 terraform plan 출력으로 직접 눈으로 확인하는 것이 이번 실습의 핵심입니다.

 

사전 준비: 커스텀 AMI 노드그룹 생성

커스텀 AMI 시나리오 테스트를 위해 먼저 1.30 기준 커스텀 AMI 노드그룹을 하나 추가합니다.

 

1) 1.30 버전 AMI ID 조회

SSM Parameter Store에서 EKS Optimized AMI ID를 조회합니다.

aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.30/amazon-linux-2023/x86_64/standard/recommended/image_id \
  --region $AWS_REGION \
  --query "Parameter.Value" --output text
  
ami-0c42b1f4678fc81d1

SSM에서 조회되는 AMI ID는 EKS Optimized AMI 패치 시점마다 달라집니다.

반드시 본인 환경에서 직접 조회한 값을 사용해야 합니다.

 

2) variables.tf에 ami_id 변수 추가

variable "ami_id" {
  description = "EKS AMI ID for node groups"
  type        = string
  default     = "ami-0c42b1f4678fc81d1"   # 위에서 조회한 1.30 AMI ID
}

ami_id를 특정 값으로 지정하는 순간, EKS가 해당 노드그룹의 AMI를 자동으로 관리하지 않습니다.

이후 실습에서 이 값을 1.31용 AMI ID로 교체하면서 커스텀 AMI 기반 노드그룹의 수동 업그레이드 흐름을

직접 체험하는 것이 목적입니다.

 

3) base.tf에 custom 노드그룹 추가

custom = {
  instance_types             = ["t3.medium"]
  min_size                   = 1
  max_size                   = 2
  desired_size               = 1
  update_config = {
    max_unavailable_percentage = 35
  }
  ami_id                     = try(var.ami_id)
  ami_type                   = "AL2023_x86_64_STANDARD"
  enable_bootstrap_user_data = true
}

initial과 달리 ami_id가 명시적으로 지정되어 있습니다.

이 순간부터 EKS는 이 노드그룹의 AMI를 자동으로 관리하지 않습니다.

 

모니터링 창을 열어두고 apply합니다.

while true; do
  aws autoscaling describe-auto-scaling-groups \
    --query 'AutoScalingGroups[*].AutoScalingGroupName' --output json | jq
  echo
  kubectl get node -L eks.amazonaws.com/nodegroup
  echo
  kubectl get node -L eks.amazonaws.com/nodegroup-image | grep ami
  echo; date; sleep 1
done
terraform apply -auto-approve  # 약 2분 소요

생성된 custom 노드의 레이블을 확인하면 어떤 AMI로 떴는지 확인할 수 있습니다.

kubectl describe node <custom-node-name>

Labels:
  eks.amazonaws.com/nodegroup=custom-20250325154855579500000007
  eks.amazonaws.com/nodegroup-image=ami-06441265f3c3cef0a   ← 직접 지정한 AMI
  eks.amazonaws.com/sourceLaunchTemplateId=lt-0db2ddc3fdfb0afb1
  eks.amazonaws.com/sourceLaunchTemplateVersion=1

이제 클러스터에는 세 개의 Managed Node Group이 있습니다.

노드그룹 용도 AMI 방식 k8s 버전
initial 범용 워크로드 EKS 관리형 기본 AMI 1.30
blue-mng Orders 전용 (Stateful) EKS 관리형, 버전 고정 1.30
custom 비교 실습용 커스텀 AMI 직접 지정 1.30

 

mng_cluster_version만 바꾸면 어떻게 되는가?

variables.tf에서 mng_cluster_version을 "1.31"로 변경하고 plan을 먼저 확인합니다.

variable "mng_cluster_version" {
  default = "1.31"
}

terraform plan -no-color > plan-output.txt

plan 결과를 보면 세 노드그룹의 반응이 다릅니다.

# initial → 업그레이드 대상
~ resource "aws_eks_node_group" "this" {
    ~ version = "1.30" -> "1.31"

# custom → 변화 없음 (ami_id가 직접 지정되어 있어 version 변수와 무관)
# blue-mng → 변화 없음 (cluster_version = "1.30" 직접 고정)

initial만 plan에 잡힙니다. custom은 AMI ID가 코드에 박혀 있어서 버전 변수가 바뀌어도 아무 반응이 없습니다.

blue-mng은 cluster_version이 직접 고정되어 있어서 마찬가지입니다.

 

이게 실습에서 커스텀 노드그룹을 만든 이유입니다.

"나는 mng_cluster_version 변수를 바꿨는데 왜 custom 노드그룹은 안 올라가지?" 라는 상황을

plan으로 직접 확인하는 것입니다.

 

initial + custom 동시 업그레이드

custom까지 함께 올리려면 1.31 AMI ID를 조회해서 ami_id 변수도 같이 바꿔야 합니다.

aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.31/amazon-linux-2023/x86_64/standard/recommended/image_id \
  --region $AWS_REGION \
  --query "Parameter.Value" --output text
  
ami-00e0cfd6e5895fe3a

 

variables.tf에서 두 변수를 함께 변경합니다.

variable "mng_cluster_version" {
  default = "1.31"
}

variable "ami_id" {
  default = "ami-00e0cfd6e5895fe3a"  # 1.31 AMI ID로 교체
}

모니터링 창을 열어두고 apply합니다. 업그레이드 진행 중 노드 상태 변화를 실시간으로 볼 수 있습니다.

while true; do
  kubectl get node -L eks.amazonaws.com/nodegroup-image | grep ami
  echo; date; sleep 1
done

업그레이드가 진행되면 다음과 같은 상태 변화가 관찰됩니다.

 

# Scale Up 단계 : 새 노드 추가
ip-10-0-10-86...   Ready                    v1.31.x   ami-00e0cfd6e5895fe3a  ← 신규
ip-10-0-13-217...  Ready,SchedulingDisabled v1.30.x   ami-06441265f3c3cef0a  ← 기존 (cordon됨)

# Upgrade 단계 : 기존 노드 drain 후 종료
ip-10-0-2-59...    NotReady,SchedulingDisabled v1.30.x  ami-06441265f3c3cef0a  ← drain 중

# Scale Down 단계 : 기존 노드 모두 종료, 원래 desired 수로 복귀
ip-10-0-10-86...   Ready   v1.31.x   ami-00e0cfd6e5895fe3a
ip-10-0-17-199...  Ready   v1.31.x   ami-00e0cfd6e5895fe3a

SchedulingDisabled는 해당 노드가 cordon된 상태입니다.

새 파드가 스케줄링되지 않도록 막아둔 채로 기존 파드를 drain하는 중입니다

Scale Up → 기존 노드 cordon → drain → 종료 → Scale Down 흐름이 실제 출력에서 그대로 보입니다.

 

약 20분 후 업그레이드가 완료됩니다.

kubectl get nodes -o wide

NAME                                       STATUS   VERSION
ip-10-0-8-17.us-west-2.compute.internal    Ready    v1.31.14-eks-bbe087e   # initial
ip-10-0-22-172.us-west-2.compute.internal  Ready    v1.31.14-eks-bbe087e   # custom
ip-10-0-7-77.us-west-2.compute.internal    Ready    v1.30.14-eks-bbe087e   # blue-mng (의도적으로 유지)

initial과 custom이 1.31로 올라갔고,

blue-mng은 cluster_version = "1.30" 고정 때문에 의도대로 그대로입니다.

 

Cleanup : custom 노드그룹 삭제

비교 실험 목적이었던 custom 노드그룹을 삭제합니다. base.tf에서 custom 블록을 제거하고 apply합니다.

terraform apply -auto-approve

이후, 다음 섹션에서 Blue/Green으로 처리되는 것을 다음 섹션에서 다뤄보도록 하겠습니다.