CloudNet@ Team Study/EKS Workshop 4th Cohort

EKS Nodes Upgrade : Managed NodeGroup Blue/Green Upgrade

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

 

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

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

열강 해주셨는데요,

오늘은 NodeGroup Upgrade 중, Blue/Green 배포 관련하여

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

 

 

blue-mng은 왜 In-Place로 올릴 수 없는가?

blue-mng에는 세 가지 특성이 동시에 걸려 있습니다.

 

1) 버전이 코드에 직접 고정되어 있습니다.

cluster_version = "1.30"이 base.tf에 박혀 있어 mng_cluster_version 변수를

아무리 바꿔도 이 노드그룹은 반응하지 않습니다.

 

2) Stateful 워크로드가 고정 배치되어 있습니다.

dedicated=OrdersApp:NoSchedule Taint와 type=OrdersMNG Label로 orders, orders-mysql 파드가

이 노드에만 뜨도록 설계되어 있습니다. orders-mysql은 EBS PVC를 사용하는 Stateful 워크로드입니다.

 

3) 단일 AZ에 고정되어 있습니다

subnet_ids = [module.vpc.private_subnets[0]]로 us-west-2a 한 곳에만 노드가 프로비저닝됩니다.

EBS 볼륨은 AZ를 넘어갈 수 없으므로, green-mng도 동일한 AZ에 만들어야 기존 PV를 이어받을 수 있습니다.

 

In-Place 롤링 업데이트로 접근하면 기존 노드를 drain하는 순간 orders-mysql 파드가 뜰 수 있는

노드가 없어지고, EBS 볼륨도 다른 AZ 노드에는 붙지 않습니다.

새 노드를 먼저 준비한 뒤 파드를 옮기고 기존 노드를 삭제하는 Blue/Green 방식이 이 시나리오에 적합합니다.

 

blue-mng 현재 상태 확인

$ kubectl get nodes -l type=OrdersMNG

NAME                                      STATUS   VERSION
ip-10-0-7-77.us-west-2.compute.internal   Ready    v1.30.14-eks-bbe087e
kubectl get nodes -l type=OrdersMNG \
  -o jsonpath="{range .items[*]}{.metadata.name} {.spec.taints[?(@.effect=='NoSchedule')]}{\"\n\"}{end}"
  
ip-10-0-7-77.us-west-2.compute.internal {"effect":"NoSchedule","key":"dedicated","value":"OrdersApp"}

Non-terminated Pods 섹션을 보면 이 노드 위에서 실행 중인 파드가 확인됩니다.

 

$ kubectl describe node -l type=OrdersMNG

Namespace    Name                       
orders       orders-5b97745747-mxg72    ← Orders API
orders       orders-mysql-b9b997d9d     ← MySQL (EBS PVC 사용 중)
kube-system  aws-node, ebs-csi-node, kube-proxy ...

$ kubectl get pvc -n orders

NAME              STATUS   VOLUME                                      CAPACITY   STORAGECLASS
order-mysql-pvc   Bound    pvc-875d1ca7-e28b-421a-8a7e-2ae9d7480b9a   4Gi        gp3

orders-mysql이 us-west-2a의 EBS 볼륨에 묶여 있습니다.

이 볼륨을 끊지 않고 마이그레이션하려면 green-mng도 동일한 AZ에 만들어야 합니다.

 

orders와 orders-mysql의 Deployment를 확인하면 nodeSelector와 toleration이 설정되어 있습니다.

cat ~/environment/eks-gitops-repo/apps/orders/deployment.yaml

spec:
  nodeSelector:
    type: OrdersMNG        # 이 레이블이 있는 노드에만 스케줄링
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "OrdersApp"
    effect: "NoSchedule"  # 이 toleration이 있어야 dedicated taint 노드에 진입 가능

green-mng에 동일한 레이블과 Taint를 설정해야 orders 파드가 정상적으로 이동할 수 있습니다.

 

Step1. green-mng 생성

base.tf의 blue-mng 블록 아래에 green-mng을 추가합니다.

cluster_version을 명시하지 않아서 eks_managed_node_group_defaults의 값(1.31)을 자동으로 따릅니다.

green-mng = {
  instance_types = ["m5.large", "m6a.large", "m6i.large"]
  subnet_ids     = [module.vpc.private_subnets[0]]  # blue-mng과 동일한 AZ
  min_size     = 1
  max_size     = 2
  desired_size = 1
  update_config = {
    max_unavailable_percentage = 35
  }
  labels = {
    type = "OrdersMNG"  # orders 파드가 이 노드로 스케줄링될 수 있도록
  }
  taints = [
    {
      key    = "dedicated"
      value  = "OrdersApp"
      effect = "NO_SCHEDULE"
    }
  ]
  # cluster_version 없음 → 1.31 자동 적용
}

blue-mng 코드와 비교했을 때 cluster_version = "1.30" 한 줄이 없는 것이 전부입니다.

이 차이 하나로 두 노드그룹의 K8s 버전이 달라집니다.

 

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

업그레이드가 진행되는 동안 노드 상태 변화를 실시간으로 관찰합니다.

-L eks.amazonaws.com/nodegroup 옵션은 각 노드가 어느 노드그룹에 속하는지를 컬럼으로 추가해서 보여줍니다.
업그레이드 중 Scale Up 단계에서 새 노드가 추가되는 시점, 기존 노드가 SchedulingDisabled 상태로 바뀌는 시점,
그리고 최종적으로 기존 노드가 사라지고 새 노드만 남는 시점을 이 출력으로 확인할 수 있습니다.

'terraform apply -auto-approve' 명령으로 apply !

 

생성 완료 후 확인해봅시다.

$ kubectl get node -l type=OrdersMNG -o wide

NAME                                       STATUS   VERSION
ip-10-0-7-77.us-west-2.compute.internal    Ready    v1.30.14-eks-bbe087e   # blue-mng
ip-10-0-0-159.us-west-2.compute.internal   Ready    v1.31.14-eks-bbe087e   # green-mng (신규)

$ kubectl get node -l type=OrdersMNG -L topology.kubernetes.io/zone

NAME                                       VERSION                ZONE
ip-10-0-7-77.us-west-2.compute.internal    v1.30.14-eks-bbe087e   us-west-2a
ip-10-0-0-159.us-west-2.compute.internal   v1.31.14-eks-bbe087e   us-west-2a

두 노드 모두 us-west-2a입니다.

green-mng 노드가 orders-mysql의 EBS 볼륨에 접근할 수 있는 조건이 갖춰졌습니다.

 

Taint도 동일하게 적용됐는지 확인합니다.

kubectl get nodes -l type=OrdersMNG \
  -o jsonpath="{range .items[*]}{.metadata.name} {.spec.taints[?(@.effect=='NoSchedule')]}{\"\n\"}{end}"
  
ip-10-0-7-77...   {"effect":"NoSchedule","key":"dedicated","value":"OrdersApp"}
ip-10-0-0-159...  {"effect":"NoSchedule","key":"dedicated","value":"OrdersApp"}

이제 orders 파드가 두 노드 중 어디든 스케줄링될 수 있는 상태입니다.

 

Step2. PDB 대응 : orders 레플리카 증가

blue-mng을 삭제하기 전에 PDB 상태부터 확인합니다.

$ kubectl get pdb -n orders

NAME         MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS
orders-pdb   1               N/A               0

ALLOWED DISRUPTIONS: 0입니다.

현재 orders 파드가 1개이고 minAvailable: 1이기 때문에 지금 당장 하나라도 축출하면 PDB를 위반합니다.

이 상태에서 blue-mng을 삭제하면 drain이 블로킹되어 노드그룹 삭제 작업 자체가 멈춥니다.

 

replica를 2로 늘리면 orders 파드 하나가 green-mng에 추가로 뜨고,

그 상태에서 blue-mng의 파드를 축출해도 나머지 하나가 살아 있어 PDB 조건을 충족합니다.

 

GitOps 환경이므로 코드 변경 후 커밋합니다.

cd ~/environment/eks-gitops-repo/
sed -i 's/replicas: 1/replicas: 2/' apps/orders/deployment.yaml

git add apps/orders/deployment.yaml
git commit -m "Increase orders replicas 2"
git push

argocd app sync orders

ArgoCD Application에 ignoreDifferences로 replicas가 무시되도록 설정되어 있다면 직접 올립니다.

kubectl scale deploy -n orders orders --replicas 2
kubectl get pod -n orders -o wide

orders 파드 하나가 green-mng 노드에 떠 있는 것을 확인합니다.

이제 ALLOWED DISRUPTIONS가 1이 되어 drain이 진행될 수 있습니다.

 

Step3. blue-mng 삭제 및 마이그레이션

삭제 과정을 실시간으로 모니터링합니다.

while true; do
  kubectl get node -l type=OrdersMNG
  echo
  kubectl get pod -n orders -l app.kubernetes.io/component=service -o wide
  echo
  kubectl get pdb -n orders
  echo; date
done

base.tf에서 blue-mng 블록을 제거하고 apply합니다.

cd ~/environment/terraform/
terraform apply -auto-approve  # 약 10분 소요

EKS가 blue-mng 노드에 대해 자동으로 처리하는 순서는 이벤트 로그로 확인할 수 있습니다.

$ kubectl get events --sort-by='.lastTimestamp' --watch

Normal  NodeNotSchedulable  node/ip-10-0-7-77...  Node status is now: NodeNotSchedulable
Normal  NodeNotReady        node/ip-10-0-7-77...  Node status is now: NodeNotReady
Normal  DeletingNode        node/ip-10-0-7-77...  Deleting node because it does not exist in the cloud provider
Normal  RemovingNode        node/ip-10-0-7-77...  Removing Node from Controller

Cordon → drain → 종료 순서로 진행됩니다. drain 시점에 blue-mng에 있던 orders 파드가 축출되고,

nodeSelector와 toleration 조건을 만족하는 green-mng 노드로 재스케줄링됩니다.

 

Migration 결과 확인

$ kubectl get node -l type=OrdersMNG

NAME                                       STATUS   VERSION
ip-10-0-0-159.us-west-2.compute.internal   Ready    v1.31.14-eks-bbe087e

type=OrdersMNG 노드가 green-mng의 v1.31 노드 하나만 남았습니다.

kubectl get pods -n orders -o wide

orders와 orders-mysql 파드 모두 green-mng 노드에서 실행 중인 것을 확인합니다.

aws eks list-nodegroups --cluster-name $EKS_CLUSTER_NAME

{
    "nodegroups": [
        "green-mng-20260424075552962200000007",
        "initial-2026042209254538750000002b"
    ]
}

blue-mng이 삭제되고 initial과 green-mng 두 노드그룹만 남았으며, 둘 다 1.31입니다.

모든 Managed Node Group 업그레이드가 완료됐습니다.

 


긴 글 읽어주셔서 감사합니다.

 

다음 섹션에서는 Karpenter Node Upgrade에 대해서 포스팅하도록 하겠습니다.