CloudNet@ Team Study/EKS Workshop 4th Cohort

Introduction to the EKS Cluster Upgrade Sample App.

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

 

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

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

열강 해주셨는데요, 강의 내용을 기반으로 포스팅해보려 합니다.

 

 

01. 실습 환경 개요

이 워크샵의 인프라는 두 가지 도구로 나뉘어 배포됩니다.

 

IDE Server(EC2)는 AWS CloudFormation으로 배포돼요.

code-server, kubectl, eksctl, AWS CLI, argocd CLI, terraform 등 실습에 필요한 도구가 전부 사전 설치되어

있고, IAM Role이 Instance Profile로 붙어 있어서 별도 자격증명 없이 AWS 리소스에 접근 가능합니다.

 

EKS 클러스터 전체는 Terraform으로 배포돼요.

terraform-aws-modules/eks v20.37을 사용하고, tfstate는 S3 버킷에 저장됩니다.

실습 중 업그레이드도 이 Terraform 코드를 수정해서 terraform apply로 수행하는 방식이에요.

 

리전은 us-west-2 (오레곤)이고, 클러스터 이름은 eksworkshop-eksctl, 초기 버전은 1.30입니다.

 

EKS 초기 클러스터 상태 다이어그램

노드 구성에 대해서 다이어그램을 작성해보았는데요, 아래와 같습니다.

 

1) blue-mng는 us-west-2a에만 존재하고 order-mysql-pvc가 붙어 있다.

gp3가 WaitForFirstConsumer라 EBS 볼륨이 이 AZ에 생성되어 있어서,
업그레이드로 노드가 교체되어도 새 노드가 동일 AZ에 뜨기 때문에 PVC 재마운트가 보장된다.

2) self-mng 노드 두 대의 kubelet 버전이 f69f56f로 managed 노드와 다르다.

ami_id가 하드코딩되어 있어서 생긴 차이다. 업그레이드 시 AMI를 교체해야 이 버전도 함께 올라간다.

3) Karpenter Spot 노드(10-0-13-152)에 dedicated=CheckoutApp Taint가 있다.

self-mng와 동일한 Taint라서 checkout 서비스가 self-mng 외에 이 노드에도 스케줄될 수 있다.

 

아래는 인증 구성과 클러스터 전체 컴포넌트 현황입니다.

1) PDB가 시스템 컴포넌트(coredns, alb-controller 등)에만 있고 샘플 앱 서비스에는 없다.

catalog-mysql, rabbitmq, orders-mysql 같은 stateful 서비스도 PDB 없이 드레인 시 강제 eviction됩니다.
업그레이드 중 서비스 영향이 발생하는 게 당연한 구조다.

2) StatefulSet이 세 개(catalog-mysql, rabbitmq, argocd controller)

PVC가 RWO라서 노드 드레인 순서와 PVC AZ가 업그레이드 계획의 핵심 변수다.

 

EKS 초기 클러스터 상태 분석

1) 노드 구성 (AZ별 배치와 의미)

실제 노드 목록을 보면

ip-10-0-7-77    blue-mng   ON_DEMAND  us-west-2a  v1.30.14-eks-bbe087e
ip-10-0-13-152  (Karpenter Spot)      us-west-2a  v1.30.14-eks-f69f56f
ip-10-0-29-250  initial    ON_DEMAND  us-west-2b  v1.30.14-eks-bbe087e
ip-10-0-18-209  self-mng              us-west-2b  v1.30.14-eks-f69f56f
ip-10-0-40-33   initial    ON_DEMAND  us-west-2c  v1.30.14-eks-bbe087e
ip-10-0-36-163  self-mng              us-west-2c  v1.30.14-eks-f69f56f
fargate-...     Fargate               us-west-2c  v1.30.14-eks-f69f56f

여기서 바로 보이는 것이 kubelet 버전이 두 종류라는 점입니다.

bbe087e는 managed 노드(initial, blue-mng)이고, f69f56f는 self-mng, Karpenter, Fargate 노드입니다.
동일한 Kubernetes 1.30.14지만 EKS 내부 패치 빌드가 다릅니다.

self-mng는 base.tf에서 ami_id = "ami-0f676a166352f02ab"로 하드코딩되어 있어서 Terraform을 건드리지 않는 한
항상 이전 빌드 AMI로 뜹니다. 이게 업그레이드 시 self-mng 처리가 managed와 다른 이유다.

 

2) Taint

fargate-ip-10-0-47-39   eks.amazonaws.com/compute-type=fargate    NoSchedule
ip-10-0-13-152          dedicated=CheckoutApp                     NoSchedule  ← Karpenter Spot
ip-10-0-7-77            dedicated=OrdersApp                       NoSchedule  ← blue-mng

Taint는 해당 노드에 특정 Toleration 없는 파드는 절대 스케줄 안 된다는 선언입니다.

blue-mng의 dedicated=OrdersApp:NoSchedule은 base.tf에서 명시적으로 선언한 것이에요.
Orders 서비스 Deployment에 이 Toleration이 있어야만 ip-10-0-7-77에 스케줄됩니다. 만약 blue-mng 업그레이드 중
이 노드가 drain되면, Orders 파드는 반드시 새로 뜨는 blue-mng 노드로 가야 합니다.
Toleration이 blue-mng Taint에만 맞춰져 있기 때문에 initial이나 self-mng로는 못 갑니다.

ip-10-0-13-152 (Karpenter Spot)에 dedicated=CheckoutApp Taint가 붙어 있다는 게 흥미로운 포인트에요.

base.tf에서 self-mng에는 이 Taint가 없습니다. Karpenter NodePool spec.template.spec.taints에 선언되어 있어서 Karpenter가 노드를 프로비저닝할 때 자동으로 붙인 것입니다.
즉, Checkout 서비스는 self-mng 노드뿐 아니라 Karpenter Spot 노드에도 스케줄될 수 있는 구조입니다.

 

3) PVC (업그레이드 순서를 결정하는 핵심)

orders/order-mysql-pvc      gp3  4Gi  RWO  → ip-10-0-7-77   (blue-mng,  us-west-2a)
checkout/checkout-redis-pvc gp3  4Gi  RWO  → ip-10-0-13-152 (Karpenter, us-west-2a)
catalog/catalog-mysql-pvc   gp3  4Gi  RWO  → ip-10-0-36-163 (self-mng,  us-west-2c)

RWO(ReadWriteOnce)는 한 번에 하나의 노드에서만 마운트 가능합니다.

gp3의 WaitForFirstConsumer는 파드가 특정 노드에 스케줄될 때 그 노드의 AZ에 EBS 볼륨을 생성합니다.

즉, EBS 볼륨은 AZ에 묶여 있다.

order-mysql-pvc가 us-west-2a의 EBS 볼륨이라는 뜻이고, blue-mng 업그레이드 시 노드가 교체되더라도 새 노드가 반드시
us-west-2a에 떠야 이 볼륨을 재마운트할 수 있습니다. base.tf에서 subnet_ids = [module.vpc.private_subnets[0]]으로
blue-mng를 us-west-2a 단일 AZ에 고정한 이유가 바로 이것입니다.
이 설계가 없었다면 업그레이드 후 Orders MySQL 파드가 다른 AZ에 뜨면서 볼륨 마운트 실패로 파드가 Pending에 걸리겠네요.

catalog-mysql-pvc는 self-mng 노드(us-west-2c)에 마운트되어 있다.

self-mng 업그레이드 시 ip-10-0-36-163을 drain하면 catalog-mysql StatefulSet 파드가 이동해야 하는데,
PVC가 us-west-2c에 묶여 있으므로 같은 AZ의 다른 노드로 가야 합니다.

self-mng가 2c에 한 대밖에 없고 initial MNG ip-10-0-40-33이 2c에 있어서 일단 그쪽으로 이동은 가능합니다.
단, Taint가 없는 initial로 가기 위해 catalog Deployment에 별도 Toleration이 없어야 해요.

 

4) StatefulSet

catalog    catalog-mysql   1/1
rabbitmq   rabbitmq        1/1
argocd     argo-cd-argocd-application-controller  1/1

StatefulSet은 Deployment와 달리 파드 이름에 순서 번호가 붙고(catalog-mysql-0),

볼륨과 파드 이름이 1:1로 고정 바인딩됩니다.

노드 drain 시 catalog-mysql-0이 종료되고 새 노드에서 다시 catalog-mysql-0으로 뜨면서 동일한 PVC를 재클레임합니다.
이 과정에서 PVC가 다른 AZ에 있으면 마운트 실패가 납니다.
Deployment는 파드가 종료되고 새 파드가 뜰 때 이름이 달라지고 볼륨 재클레임 과정도 단순하지만,
StatefulSet은 파드-볼륨 바인딩이 강결합되어 있어서 업그레이드 중 더 신경 써야 해요.

 

5) PDB (드레인이 블록될 수 있는 조건)

karpenter     maxUnavailable: 1  → ALLOWED DISRUPTIONS: 1
alb-ctrl      maxUnavailable: 1  → ALLOWED DISRUPTIONS: 1
coredns       maxUnavailable: 1  → ALLOWED DISRUPTIONS: 1
ebs-csi       maxUnavailable: 1  → ALLOWED DISRUPTIONS: 1

PDB는 동시에 종료 가능한 파드 수에 상한을 걸어두는 것입니다.

coredns의 maxUnavailable: 1은 2개의 coredns 파드 중 한 번에 1개까지만 종료 가능하다는 뜻입니다.
노드 drain 시 해당 노드에 coredns가 있으면 먼저 다른 노드에 새 coredns가 뜨고 Ready가 될 때까지 drain이 블록된다.

중요한 건 샘플 앱 서비스에는 PDB가 없다는 것입니다. catalog, carts, checkout, orders, ui

전부 PDB 없이 drain 시 즉시 eviction됩니다. 업그레이드 중 서비스 다운타임이 발생할 수 있고,

이것을 체감하기 위해 UI NLB를 미리 만들어두고 반복 호출을 걸어두는 게 의미 있을 것 같네요.

 

6) 인증모드

이 클러스터는 두 가지 인증 방식이 동시에 활성화되어 있습니다.

 

Access Entry(API 방식)로 등록된 Role 목록:

blue-mng-eks-node-group
default-selfmng-node-group
fp-profile
initial-eks-node-group
karpenter-eksworkshop-eksctl
workshop-stack-ekstfcodebuildStackDeployProjectRole

 

aws-auth ConfigMap으로 등록된 항목:

initial-eks-node-group     → system:nodes
blue-mng-eks-node-group    → system:nodes
fp-profile                 → system:node-proxier
WSParticipantRole          → system:masters
workshop-stack-IdeIdeRole  → system:masters (admin)

API_AND_CONFIG_MAP 모드에서는 API를 먼저 확인하고, 없으면 ConfigMap을 확인합니다.

현재 노드 IAM Role들이 두 방식 모두에 등록되어 있어서 중복 상태입니다.

 

이건 Access Entry 방식으로 완전히 마이그레이션하기 전 과도기적 상태를 보여주는 것이라고 생각합니다.

default-selfmng-node-group이 Access Entry에는 있는데 aws-auth에는 없다는 점이 눈에 띄네요.

self-mng 노드가 클러스터에 join할 때 Access Entry로만 인증한다는 뜻입니다.

 

7) IRSA (Pod가 AWS API를 호출할 때)

karpenter SA          → role/karpenter-...          (EC2 프로비저닝/삭제)
alb-controller SA     → role/alb-controller-...     (NLB/ALB 생성/삭제)
ebs-csi-controller SA → role/eksworkshop-ebs-csi-.. (EBS 볼륨 생성/삭제)
efs-csi-controller SA → role/aws-efs-csi-driver-..  (EFS 마운트)

IRSA는 SA에 eks.amazonaws.com/role-arn Annotation을 달아두면 해당 파드가 뜰 때 EKS가

OIDC를 통해 SA 토큰을 AWS IAM Role로 교환해주는 방식입니다.

파드 내부에서 aws sts get-caller-identity를 실행하면 EC2 인스턴스 Role이 아니라 IRSA로 매핑된 Role이 찍힌다.

업그레이드와의 관계에서 중요한 점은, OIDC Provider ARN이 클러스터 업그레이드 후에도 변경되지 않는다.

OIDC issuer는 클러스터 생성 시 고정되므로 IRSA 설정은 업그레이드 후 별도로 건드릴 필요가 없습니다.

 

8) Add-on 버전 (업그레이드 후 수동으로 올려야 하는것들)

coredns            v1.11.4-eksbuild.32   (버전 고정)
kube-proxy         v1.30.14-eksbuild.28  (버전 고정)
vpc-cni            v1.21.1-eksbuild.7    (most_recent)
aws-ebs-csi-driver v1.58.0-eksbuild.1    (버전 고정)

EKS 컨트롤 플레인을 1.31로 올려도 coredns와 kube-proxy는 addons.tf에 버전이 하드코딩되어 있어서

자동으로 올라가지 않는다. Terraform에서 버전을 바꾸고 terraform apply를 해주거나,

aws eks update-addon 명령으로 수동 업데이트해야 합니다.

 

특히 kube-proxy는 노드의 kubelet 버전과 맞지 않으면 통신 문제가 생길 수 있어서

컨트롤 플레인 업그레이드 직후 바로 올려야 합니다 !

 

vpc-cni는 most_recent = true라서 terraform apply 시점의 최신 버전이 자동으로 잡힙니다.

이미 v1.21.1이 설치되어 있고 이 버전은 1.31도 지원하므로 별도 작업 없이 유지해도 될 것 같습니다.

 

Terraform 주요 파일 분석

1) variables.tf : 버전 변수 분리 설계

variable "cluster_version"     { default = "1.30" }  # 컨트롤 플레인
variable "mng_cluster_version" { default = "1.30" }  # 관리형 노드그룹

두 변수가 분리되어 있는 게 핵심입니다. EKS 업그레이드는 컨트롤 플레인 먼저, 노드그룹 나중 순서가 강제돼요.

실습에서 cluster_version을 1.31로 올리고 terraform apply → 이후 mng_cluster_version을

1.31로 올리고 terraform apply를 두 번 나눠 수행합니다.

 

2) base.tf : 노드그룹 설계 의도

blue-mng가 가장 중요합니다.

"blue-mng" = {
  cluster_version = "1.30"                           # mng_cluster_version 기본값 무시, 명시적 고정
  subnet_ids      = [module.vpc.private_subnets[0]]  # us-west-2a 단일 AZ 고정
  taints = [{
    key    = "dedicated"
    value  = "OrdersApp"
    effect = "NO_SCHEDULE"
  }]
  labels = { type = "OrdersMNG" }
}

단일 AZ 고정은 Orders MySQL PVC 때문입니다. gp3 StorageClass가 WaitForFirstConsumer라서

파드가 배치되는 AZ에 EBS가 생성됩니다. 노드 교체 시 새 노드도 반드시 같은 AZ에 떠야 EBS 재마운트가 가능!

subnet_ids 고정이 이걸 보장해요.

 

3) default-selfmng의 AMI 하드코딩:

ami_id = "ami-0f676a166352f02ab"  # EKS 1.30용 AL2023, us-west-2 고정

셀프 관리 노드그룹은 AWS 콘솔 업그레이드 버튼이 없습니다. 업그레이드 시 이 AMI를 1.31용으로 교체하고

terraform apply → 기존 노드 drain → 새 노드 대기 순서로 직접 처리해야 합니다.

 

4) addons.tf : 애드온 버전 고정

eks_addons = {
  coredns    = { addon_version = "v1.11.4-eksbuild.32" }
  kube-proxy = { addon_version = "v1.30.14-eksbuild.28" }
  vpc-cni    = { most_recent = true }
  aws-ebs-csi-driver = { service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn }
}

컨트롤 플레인 업그레이드 후 coredns, kube-proxy는 버전이 고정되어 있어 자동으로 안 올라갑니다.

업그레이드 후 이 두 애드온을 수동으로 버전 업해줘야 한다.

 

5) gitops-setup.tf : ArgoCD ↔ CodeCommit 인증

resource "aws_iam_user" "argocd_user" { name = "argocd-user" }

resource "aws_iam_service_specific_credential" "argocd_codecommit_credential" {
  service_name = "codecommit.amazonaws.com"
  user_name    = aws_iam_user.argocd_user.name
}

resource "aws_secretsmanager_secret_version" "..." {
  secret_string = jsonencode({
    url      = codecommit_repo.clone_url_http
    username = credential.service_user_name
    password = credential.service_password
  })
}

ArgoCD가 CodeCommit에 HTTPS Git으로 접근하기 위해 IAM User 기반 서비스 자격증명을 발급하고

Secrets Manager에 저장합니다. IRSA로 처리하지 않는 이유는 ArgoCD의 Git 접근이

HTTPS credential 방식이기 때문입니다 !

 


 

02. 샘플 애플리케이션 이해하기

이 워크숍의 대부분 실습에서는 실제 구성 요소를 제공하는 공통 샘플 애플리케이션을 사용합니다.

이 샘플 애플리케이션은 고객이 카탈로그를 탐색하고, 장바구니에 상품을 추가하고, 결제 과정을 통해

주문을 완료할 수 있는 간단한 웹 쇼핑몰 애플리케이션을 모델링합니다.

 

이 애플리케이션은 여러 구성 요소와 종속성을 가지고 있습니다.

 

서비스 역할
UI 프론트엔드. 사용자 요청을 받아 각 백엔드 서비스로 라우팅
Catalog 상품 목록 및 상세 정보 API
Carts 쇼핑 카트 상태 관리 API
Checkout 결제 프로세스 API
Orders 주문 접수 및 처리 API
Assets 상품 이미지 등 정적 자원 제공

 

데이터 레이어도 서비스별로 분리되어 있습니다.

  • Catalog, Orders → MySQL (각각 별도 인스턴스)
  • Checkout → Redis (세션/임시 데이터)
  • Carts → DynamoDB

특이한 점은 Checkout → Orders 간 통신이 동기 호출이 아니라 RabbitMQ를 통한 비동기 메시지 큐 방식

으로 처리된다는 것입니다. 결제가 완료되면 Checkout이 RabbitMQ에 메시지를 발행하고, Orders 서비스가

이를 구독해서 주문을 생성하는 구조네요.

업그레이드 실습에서 이 구조가 중요한 이유는, 노드가 드레인(drain)될 때 서비스별로 영향이 다르게 나타나기 때문입니다.
상태를 가진 MySQL, Redis, RabbitMQ Pod가 어느 노드에 스케줄링되어 있느냐에 따라 PDB(Pod Disruption Budget)
설정 필요성이 달라집니다.

 

배포 방식 : ArgoCD + GitOps

모든 구성요소는 ArgoCD를 통해 EKS 클러스터에 배포됩니다. GitOps 리포지토리로는 AWS CodeCommit을 사용하며, 각 서비스의 Kubernetes 매니페스트가 리포지토리에 관리된다.

 

실습 환경 접속 흐름은 다음과 같습니다.

# 1. CodeCommit 리포지토리 클론
cd ~/environment
git clone codecommit::${REGION}://eks-gitops-repo

# 2. ArgoCD 서버 엔드포인트 확인 및 로그인
export ARGOCD_SERVER=$(kubectl get svc argo-cd-argocd-server -n argocd \
  -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname')

export ARGOCD_USER="admin"
export ARGOCD_PWD=$(kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d)

argocd login ${ARGOCD_SERVER} \
  --username ${ARGOCD_USER} \
  --password ${ARGOCD_PWD} \
  --insecure --skip-test-tls --grpc-web

# 3. 전체 Pod 상태 확인
kubectl get pods -A

ArgoCD UI에 접속하면 각 마이크로서비스에 대응하는 Application이 생성되어 있고,

대부분이 Healthy & Synced 상태인 것을 확인할 수 있습니다.

단, ui Application은 OutOfSync 상태로 시작하는데, 이후 실습에서 의도적으로

변경 사항을 적용하면서 업그레이드 검증에 활용됩니다.

 

ArgoCD 현 상태를 CLI로도 아래와 같이 확인하실 수 있습니다.

#
argocd login ${ARGOCD_SERVER} --username ${ARGOCD_USER} --password ${ARGOCD_PWD} --insecure --skip-test-tls --grpc-web
'admin:login' logged in successfully
Context 'k8s-argocd-argocdar-eb7166e616-ed2069d8c15177c9.elb.us-west-2.amazonaws.com' updated

#
argocd repo list
TYPE  NAME  REPO                                                                     INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git         https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  false     false  false  true   Successful   

#
argocd app list
NAME              CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                                     PATH            TARGET
argocd/apps       https://kubernetes.default.svc             default  Synced  Healthy  Auto        <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  app-of-apps     
argocd/assets     https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/assets     main
argocd/carts      https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/carts      main
argocd/catalog    https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/catalog    main
argocd/checkout   https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/checkout   main
argocd/karpenter  https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/karpenter  main
argocd/orders     https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/orders     main
argocd/other      https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/other      main
argocd/rabbitmq   https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/rabbitmq   main
argocd/ui         https://kubernetes.default.svc             default  Synced  Healthy  Auto-Prune  <none>      https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo  apps/ui         main

argocd app get apps
Name:               argocd/apps
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          
URL:                https://k8s-argocd-argocdar-eb7166e616-ed2069d8c15177c9.elb.us-west-2.amazonaws.com/applications/apps
Source:
- Repo:             https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo
  Target:           
  Path:             app-of-apps
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to  (acc257a)
Health Status:      Healthy

GROUP        KIND         NAMESPACE  NAME       STATUS  HEALTH  HOOK  MESSAGE
argoproj.io  Application  argocd     karpenter  Synced                application.argoproj.io/karpenter created
argoproj.io  Application  argocd     carts      Synced                application.argoproj.io/carts created
argoproj.io  Application  argocd     assets     Synced                application.argoproj.io/assets created
argoproj.io  Application  argocd     catalog    Synced                application.argoproj.io/catalog created
argoproj.io  Application  argocd     checkout   Synced                application.argoproj.io/checkout created
argoproj.io  Application  argocd     rabbitmq   Synced                application.argoproj.io/rabbitmq created
argoproj.io  Application  argocd     other      Synced                application.argoproj.io/other created
argoproj.io  Application  argocd     ui         Synced                application.argoproj.io/ui created
argoproj.io  Application  argocd     orders     Synced                application.argoproj.io/orders created

argocd app get carts
Name:               argocd/carts
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          
URL:                https://k8s-argocd-argocdar-eb7166e616-ed2069d8c15177c9.elb.us-west-2.amazonaws.com/applications/carts
Source:
- Repo:             https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo
  Target:           main
  Path:             apps/carts
SyncWindow:         Sync Allowed
Sync Policy:        Automated (Prune)
Sync Status:        Synced to main (acc257a)
Health Status:      Healthy

GROUP  KIND            NAMESPACE  NAME            STATUS  HEALTH   HOOK  MESSAGE
       Namespace                  carts           Synced                 namespace/carts created
       ServiceAccount  carts      carts           Synced                 serviceaccount/carts created
       ConfigMap       carts      carts           Synced                 configmap/carts created
       Service         carts      carts-dynamodb  Synced  Healthy        service/carts-dynamodb created
       Service         carts      carts           Synced  Healthy        service/carts created
apps   Deployment      carts      carts           Synced  Healthy        deployment.apps/carts created
apps   Deployment      carts      carts-dynamodb  Synced  Healthy        deployment.apps/carts-dynamodb created

#
argocd app get carts -o yaml
...
spec:
  destination:
    server: https://kubernetes.default.svc
  ignoreDifferences:
  - group: apps
    jsonPointers:
    - /spec/replicas
    - /metadata/annotations/deployment.kubernetes.io/revision
    kind: Deployment
  - group: autoscaling
    jsonPointers:
    - /status
    kind: HorizontalPodAutoscaler
  project: default
  source:
    path: apps/carts
    repoURL: https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo
    targetRevision: main
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - RespectIgnoreDifferences=true
...

argocd app get ui -o yaml
...
spec:
  destination:
    server: https://kubernetes.default.svc
  ignoreDifferences:
  - group: apps
    jsonPointers:
    - /spec/replicas
    - /metadata/annotations/deployment.kubernetes.io/revision
    kind: Deployment
  - group: autoscaling
    jsonPointers:
    - /status
    kind: HorizontalPodAutoscaler
  project: default
  source:
    path: apps/ui
    repoURL: https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-gitops-repo
    targetRevision: main
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - RespectIgnoreDifferences=true
...

 

ArgoCD ignoreDifferences

argocd app get carts -o yaml을 보면 모든 앱에 공통으로 아래 설정이 들어가 있습니다.

ignoreDifferences:
- group: apps
  kind: Deployment
  jsonPointers:
  - /spec/replicas
  - /metadata/annotations/deployment.kubernetes.io/revision
- group: autoscaling
  kind: HorizontalPodAutoscaler
  jsonPointers:
  - /status

이게 왜 필요한지 이해하려면 ArgoCD의 selfHeal 동작 방식을 알아야 합니다.

 

syncPolicy.automated.selfHeal: true가 켜져 있으면, ArgoCD는 클러스터 상태가 Git 리포의

상태와 다를 때 자동으로 Git 상태를 되돌립니다.

 

문제는 HPA가 부하에 따라 Deployment.spec.replicas를 동적으로 조정하는데, 이 값이 Git에 선언된 값

(replicas: 1)과 달라지는 순간 ArgoCD가 OutOfSync로 판단하고 replicas를 다시 1로 강제 복원합니다.

HPA가 스케일 아웃해도 ArgoCD가 계속 되돌리는 무한 충돌이 발생하죠.

 

결론은, ignoreDifferences는 ArgoCD가 동기화 상태를 판단할 때 특정 필드를 무시하도록 설정합니다.

/spec/replicas를 ignore 목록에 넣으면 HPA가 replicas를 바꿔도 ArgoCD는 Synced로 유지하고 건드리지 않습니다. syncOptions: [RespectIgnoreDifferences=true]도 함께 설정되어 있어서, Sync 실행 시에도 이 필드를 덮어쓰지 않습니다.

업그레이드 실습에서 이 설정이 중요한 이유는, 노드 drain 후 파드가 재배치되는 과정에서

HPA가 replicas를 조정할 수 있는데, 이때 ArgoCD가 간섭하지 않아야 복구가 정상적으로 진행되기 때문.

 


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

다음 포스팅에서는 직접 실습을 해보는 시간을 갖도록 하겠습니다.