9W - EKS 업그레이드

개요

이번 주차는 EKS를 업그레이드하는 전략과 방법에 대해서 알아본다.
스터디원 분의 도움으로 EKS 워크샵을 통해 실습할 수 있는 기회를 얻었으며, 이번 주차는 해당 내용을 정리하는 시간을 가질 것이다.
그렇기에 이번 글은 기존에 사용하던 사전 지식-실습 진행이라는 목차를 두지 않고 철저하게 워크샵 내용을 따라 글을 작성한다.

쿠버네티스 업그레이드 전략

쿠버네티스를 업그레이드한다는 것은 코어 컴포넌트, 관련 애드온, 사용되는 노드의 커널 버전 등 다양한 것을 포함할 수 있다.
image.png
그러나 어떻게 하더라도 결국 업그레이드하는 데에 있어 순서는 이렇게 정리될 수 있다.

말이 길어 복잡해보이나, 결국 노드들을 하나씩, 컨트롤 플레인부터 업그레이드해나간다는 것이다.
요약하면 간단하지만 실제로 업그레이드를 할 때는 어떠한 사이드 이펙트가 발생할지 미리 예측하는 것이 쉽지 않기 때문에 각 단계에서 이상이 발생하지는 않았는지, 이상이 발생했다면 어떻게 원복할 것인지에 대한 추가 전략과 고민이 필요하다.
이러한 방식은 상태 원복이 상대적으로 어렵기 때문에, 아예 새로운 버전의 클러스터를 다른 공간에 새로 띄우는 블루 그린 방식의 업그레이드도 많이 활용되는 추세이다.
블루 그린을 할 경우에는 상태 유지 어플리케이션 간의 경합이 발생하지는 않는지, 데이터 무결성은 제대로 지켜지는지, 자원을 얼마나 소모하게 되는지 등에 대한 고민점이 또 발생하게 되니, 어느 전략을 선택할지는 운영 조직의 몫이 될 것이다.

CKA 시험을 치뤄봤다면 간단하게라도 kubeadm으로 업그레이드하는 방법에 대해서 공부해본 적이 있을 것이다.
EKS에서 업그레이드를 하는 것은 훨씬 더 간단하다(상대적으로 간단하다는 말이다).
AWS에서 업그레이드의 절차들을 상당 부분 책임지고 관리해주기 때문이다.

인플레이스 업그레이드

현재 존재하는 클러스터를 유지하면서 노드를 하나씩 업그레이드하는 방식을 말한다.
이 방식의 장점은 기존 클러스터의 각종 고유한 정보들을 그대로 유지하면서 업그레이드할 수 있다는 것이다.
특히 EKS의 경우 컨트롤 플레인을 사용자가 관리할 수 없고, api 서버 앞단에 위치한 로드밸런서의 도메인 주소 역시 마음대로 설정할 수 없다.
자체적으로 앞단에 프록시를 하나 더 두는 방식으로 관리하는 방안을 생각할 수 있긴 하겠지만, 이 경우 api서버가 설정해주는 OIDC url이 문제가 된다.
인플레이스 업그레이드는 api 서버의 도메인 주소를 유지시키는 채로 업그레이드를 하기 때문에 별도의 추가적인 리소스를 들이지 않으며 업그레이드를 진행할 수 있다.
다만 한번 업그레이드를 했을 때 롤백이 어렵다는 단점이 있다.
특히 EKS의 경우, 컨트롤 플레인의 업그레이드를 롤백시킬 수 없기 때문에 섣불리 클러스터를 빠르게 업그레이드했다가 호환되지 않는 툴이 생기기라도 한다면 낭패가 생긴다.

블루그린 업그레이드

블루그인 업그레이드는 흔한 배포 전략과 방식이 똑같다.
현재 클러스터를 블루 버전으로 두고, 새로운 버전의 클러스터를 그린 버전으로서 똑같이 만드는 방식을 말한다.
이 경우 업그레이드간 문제가 발생하더라도 기존 클러스터는 유지되고 있으므로 롤백이 훨씬 편리하다.
롤백이랄 것도 없는 게, 그냥 그린 버전을 없애기만 하면 끝이다.
특히 인플레이스 업그레이드는 설정에 따라서는 기존 버전의 클러스터에 다운타임을 유발할 가능성도 있으나 블루 그린은 이런 문제는 발생하지 않는다.
다만 새로운 클러스터 자원을 들여야 하기 때문에 업그레이드간 그만큼의 비용이 더 발생한다는 점이 대표적인 단점이다.
또한 상태 유지가 중요한 어플리케이션이나 고유성이 확보돼야 하는 어플리케이션이 클러스터에 존재하는 경우, 이들의 데이터가 적절하게 동기화되고 문제가 없도록 동작하도록 고민을 해야한다는 추가적인 어려움이 발생할 수 있다.

인플레이스 업그레이드가 아닌 블루그린 업그레이드를 사용하는 것이 유효한 상황들을 체크해보자.

이제부터는 워크샵의 내용을 철저하게 따라가면서 진행한다.

클러스터 환경 파악

image.png
먼저 콘솔로 간단하게 확인해보면 버전은 1.25 버전을 쓰고 있는 것을 볼 수 있다.
빨리 업그레이드하지 않으면 AWS가 이놈하며 돈을 더 요구한다!
image.png
버전 업그레이드 세팅은 Extended로 되어 있어 자동으로 업그레이드하지 않고 사용자가 수동으로 트리거해야 한다.
image.png
현재 배치된 노드는 파게이트 하나, 관리형 노드 그룹으로 배치된 노드 2개, 자체 관리 노드 2개로 이뤄져있다.
추가적으로 블루 그린을 간단하게 테스트하기 위한 노드도 배치된 상태이다.
image.png
자체 관리 노드 중 하나는 카펜터를 통해 프로비저닝된 노드란 것도 확인할 수 있다.
image.png
애드온도 버전에 맞춰 업그레이드를 해야하므로 현 버전에 대해 간단하게 체크해둔다.
image.png
아주 고맙게도, 콘솔에서 대시보드를 들어가보면 현재 버전에서 업그레이드를 진행하는데 있어 체크할 사항들을 보여준다.
image.png
당장 1.26으로 버전을 업그레이드하는데 발생하는 문제가 하나 보이는데, hpa 리소스에서 버전이 v2beta2로 되어있는 것이 화근이다.
온프레미스 환경에서도 업그레이드를 할 때 각 리소스의 api 버전을 체크하는 것은 매우 중요하다!
image.png
클러스터 내부적으로는 argocd가 있는 것이 보인다.
image.png
아르고 서버가 로드밸런서로 열려 있으므로, 웹으로 접속해서 확인해보자.

k -n argocd get secrets argocd-initial-admin-secret -ojsonpath='{.data.password}' | base64 -d

초기 비밀번호를 알아내서 들어가보면..
image.png
여러 어플리케이션이 들어가 있는 것을 볼 수 있다.
image.png
각 앱은 apps라는 어플리케이션의 하위 앱으로서 app of apps 패턴으로 구성됐다.
image.png
깃 레포를 받아서 뜯어보면, 이렇게 app of apps 디렉에서 하위 앱들의 스펙을 명시한다.
그리고 각 앱은 apps 디렉토리에 상태가 구현돼있다.
image.png
싱크가 안 맞는 앱이 하나 있는데, hpa 관련 설정에 문제가 있다는 것도 확인해두자.

image.png
추가적으로, 현재 클러스터는 테라폼 코드로 관리되고 있다.
image.png
파게이트 하나, 관리형 노드 그룹 2개가 세팅돼있다.
추가적으로 셀프형 노드 그룹도 있는 상태이다.
실습 상에서 우리는 테라폼을 통해 업그레이드를 진행할 것이다.

EKS 업그레이드 준비

EKS 업그레이드 자체도 기본적으로 흐름은 쿠버네티스 업그레이드 흐름과 같다.
대신 EKS의 경우 컨트롤 플레인 업그레이드를 결정했을 때 해당 업그레이드는 전적으로 책임을 져준다.

EKS에서 업그레이드를 진행할 때의 기본적인 준비사항은 이렇게 된다.

클러스터 리소스의 변경점이나 기타 고려사항은 EKS 문서에서도 잘 정리해주고 있으므로, 이 내용을 기반으로 현 클러스터에서 추적관리할 사항을 고려하면 된다.[1]
가령 AL2는 1.33부터는 지원되지 않으니 업그레이드 간 ami를 AL2023 혹은 보틀로켓으로 변경하는 것이 좋을 것이다.

aws ec2 describe-subnets --subnet-ids \
  $(aws eks describe-cluster --name ${CLUSTER_NAME} \
  --query 'cluster.resourcesVpcConfig.subnetIds' \
  --output text) \
  --query 'Subnets[*].[SubnetId,AvailabilityZone,AvailableIpAddressCount]' \
  --output table

image.png
다행히? 가용 IP는 차고 넘친다.
부족하다 싶으면 서브넷을 추가로 늘리거나, 아니면 VPC 커스텀 네트워크를 고려해보자.

고가용성을 고려하기 위해 PDB를 세팅하는 예시는 다음과 같다.

cat << EOF > ~/environment/eks-gitops-repo/apps/orders/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: orders-pdb
  namespace: orders
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: service
      app.kubernetes.io/instance: orders
      app.kubernetes.io/name: orders
EOF

echo "  - pdb.yaml" >> ~/environment/eks-gitops-repo/apps/orders/kustomization.yaml
cd ~/environment/eks-gitops-repo/
git add apps/orders/kustomization.yaml
git add apps/orders/pdb.yaml
git commit -m "Add PDB to orders"
git push

argocd app sync orders

아르고cd를 쓰고 있으므로 싱크를 맞춰주는 작업까지 곁들였다.
image.png
정상적으로 싱크가 된 것까지 확인됐다.

aws s3 ls | awk '{print $3}^C
aws s3 ls s3://$(!!)

image.png
현 워크샵 실습에서는 s3를 테라폼 백엔드로 사용할 수 있도록 제공해주고 있다.

terraform { 
  backend "s3" {
    bucket = "workshop-stack-tfstatebackendbucketf0fc9a9d-5wvsn1twmiae"
    region = "us-west-2"
    key    = "terraform.tfstate"
  }
}

다만 이걸 직접 연결하도록 주석 처리가 돼있으므로 직접 백엔드 설정을 해주자.

terraform init
terraform output

해서 kubectl config 업데이트 명령어가 나온다면 성공이다.

helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm repo update
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --namespace kube-system

#
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-type: external
  labels:
    app.kubernetes.io/instance: kube-ops-view
    app.kubernetes.io/name: kube-ops-view
  name: kube-ops-view-nlb
  namespace: kube-system
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/instance: kube-ops-view
    app.kubernetes.io/name: kube-ops-view
EOF

상황을 빠르게 실시간으로 모니터링하기 위해 kube ops view까지 설치해주었다.

EKS 인플레이스 업그레이드 실습

컨트롤 플레인

콘솔, eksctl, aws cli 등 방법은 다양하나 이미 테라폼으로 리소스를 관리하고 있으니 테라폼을 적극 이용한다.
image.png
간단하게 입력 값을 수정한다.
image.png
이 입력값은 eks 모듈의 버전에 들어가므로, 이 변경사항이 적용되면 클러스터 업그레이드가 시작된다.
정확하게는 컨트롤 플레인의 업그레이드이고, 연결된 노드 그룹에 버전 명시가 따로 돼있지 않다면 이 업그레이드 적용으로 인해 같이 업그레이드가 진행된다.
그러나 현재 코드 상에서는 버전 명시가 분리돼있어 데이터 플레인은 따로 진행해야 한다.
image.png
플랜을 해보면, 교체되는 것과 새로 생기는 것들이 존재한다.
액세스 엔트리의 경우 완전히 새로 교체되는데, 실제로 리소스 상에서 교체가 일어나진 않는 경우도 있어서 무조건 믿을 수는 없다.
image.png
업그레이드 진행 간 eks 애드온 관련 모듈도 버전의 영향을 받도록 코드가 세팅돼있기에, 각 애드온들에 대해서도 버전 정보가 새로 읽힌다.
그러나 이건 쿠버네티스 버전에 따라서 버전이 읽히는 것이지 실제로 애드온 자체의 버전이 변경되는 것은 아니다.
most_recent가 참이라면 버전이 변경되기는 할 것이다.
image.png
apply를 하자 콘솔에서도 버전 업데이트 이력이 생긴다.
image.png
성공적으로 업데이트가 이뤄진다면 이렇게 버전 정보가 변하게 된다.
image.png
cli로 확인했을 때도 버전이 업데이트된 것이 확인된다.

애드온

image.png
다음으로 업데이트 할 것은 애드온인데, vpc cni의 경우 이미 most-recent로 설정돼있다.

ADDON=vpc-cni
VERSION=1.26
aws eks describe-addon-versions --addon-name $ADDON --kubernetes-version $VERSION --output table \
    --query "addons[].addonVersions[:10].{Version:addonVersion,DefaultVersion:compatibilities[0].defaultVersion}"

현 버전에 적절한 애드온 버전을 확인해보면..
image.png
쿠버 1.26에서 가장 최신 버전이 위에 있는 것을 볼 수 있다.
image.png
vpc cni는 이미 같이 해당 버전으로 업데이트가 진행됐다.
웬만해서는 문제 될 일이 많지는 않겠으나, 컨트롤할 수 없는 변경은 지양하는 게 좋다고 생각한다.
아무튼 위의 명령어를 통해 각 애드온의 최신 버전, 혹은 기본 버전으로 코드를 수정하고 업데이트 해준다.
image.png
요로코롬 바꿔주었다.
이들의 업데이트는 디플로이먼트의 전략에 따라 진행될 텐데, 후에 워커 노드 업데이트 간 발생할 다운타임을 고려한다면 지금 미리 토폴로지 분산 제약을 설정해두는 것도 좋을 것이다.

데이터 플레인

데이터 플레인으로 사용할 수 있는 노드의 방식이 다양한 만큼, 각각의 업그레이드 방식에도 조금씩 차이가 있다.

관리형 노드 그룹

일단 관리형 노드의 경우 오토스케일링그룹으로 관리되기에 오토스케일링의 전략을 그대로 따라간다.
구체적으로는 다음의 단계를 따른다고 보면 된다.

이때 주의할 것은 ami이다.
그냥 쿠버네티스 버전을 명시하여 만든 노드 그룹이라면 상관 없는 이야기이다.
그러나 노드 그룹의 ami를 특정하여 설정한 경우라면 ami의 버전을 바꿔줘야만 한다.
그러나 쿠버네티스 버전 따라 각 ami도 버전도 달라지기 때문이다.

aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.26/amazon-linux-2/recommended/image_id \
  --region $AWS_REGION --query "Parameter.Value" --output text

이런 식으로 사용하고자 하는 버전에 추천되는 ami id를 기입하는 방식으로 이용하자.
image.png
테라폼 코드 상에서는 입력 변수 값으로 노드 그룹 버전을 지정하는데, 아래 블루 노드그룹의 경우 하드 코딩이 돼있다.
이건 직접적으로 업그레이드하는 상황을 실습할 것이다.
image.png
아무튼 업그레이드를 진행하면 위에서 말한대로 새로운 버전의 노드가 일단 먼저 프로비저닝되기 시작한다.
image.png
업그레이드 진행 간 최대 노드 개수가 자동으로 조금 더 확장되는데, 이것은 최대 노드 개수만큼 프로비저닝된 상태에서도 새 버전의 노드가 프로비저닝될 수 있도록 하기 위함이다.
기본적으로 노드그룹이 허용된 가용영역 개수의 2배까지는 최대 개수가 확장되어 각 가용영역에 노드가 배치되는 것이 이상 없도록 한다.
image.png
허허.. 15분이 걸렸다..

관리형 노드 그룹 블루그린

image.png
위에서 잠시 봤듯이 블루라는 노드 그룹은 버전이 업데이트되지 않았다.
이 그룹에 대해서는 블루그린 방식으로 업데이트를 진행할 것이다.
image.png
이런 식으로 하는 이유는, 이 노드 그룹은 한 가용 영역에만 배치되어 ebs 스토리지를 사용하도록 되어있기 때문이다.

kubectl describe node -l type=OrdersMNG

image.png
현재 이 노드그룹은 상태유지가 필요한 워크로드만을 배치하기 위해 테인트까지 걸려있는 상태이다.
이런 워크로드를 배치할 때는 보통 하나의 가용영역에만 배치하고(ebs를 쓸 때) 업데이트도 블루 그린으로 안전하게 하는 것이 보통이다.
물론 인플레이스로 하는 것이 불가능한 것은 아니지만, 어차피 노드 그룹 업데이트로만 본다면 여기에서 블루그린 방식의 업그레이드는 결국 인플레이스 업그레이드 방식에서 중간에 수동으로 조절할 수 있는 한 스텝을 추가한 것과 다름없다.
image.png
그래서 이전 노드그룹과 같은 조건을 가진 노드 그룹을 하나 더 만든다.

위에서 준비를 하면서 pdb를 설정했는데, 이것 때문에 그냥 그린 버전으로 이전하려고 할 때 문제가 발생한다.
현재 orders 기본 워크로드의 pdb는 최소 활성화된 파드가 1개 있어야한다는 제한을 걸고 있는데, 애초에 mysql 파드가 하나밖에 없는 관계로, 이 상태로는 업그레이드가 진행되지 않는다.

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

해결법은 레플리카 개수를 늘려서 깃에 올리고, 아르고가 이를 싱크하게 하는 것이다.
image.png
그러나 막상 푸시가 되어도 아르고는 싱크를 하지 않는데, 이유는 위와 같이 레플리카에 대해서 ignore가 걸려있기 때문이다..
image.png
깃 상에서 원인을 추적하자면, app of apps 패턴의 상위 어플리케이션에서 일괄적으로 ignoreDifferences 필드를 적용하고 있는 상태이다.
이 문제를 가장 간단하게 해결하는 방법이 뭘까 조금 고민했는데, hpa를 사용하기로 마음 먹었다.
레플리카는 어차피 수동으로 조절하는 것이 그렇게 좋은 방식이 아니라고 판단했다.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: orders
  namespace: orders
spec:
  minReplicas: 2
  maxReplicas: 4
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: orders
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 80

그냥 최소 레플리카 개수를 2개로 둔다면, 알아서 hpa가 디플로이먼트를 스케일 해주므로 더 이상 코드로서 레플리카를 관여할 필요도 없어지면서 pdb를 충족시킬 수 있게 된다.
api 버전 주의하자!
위에서 잠시 봤듯이 1.26에서는 v2beta2의 hpa 리소스는 더 이상 지원되지 않으며, 이제 v2에서는 metrics라는 필드 아래 조건을 걸 메트릭을 정의해야 한다.
image.png
마지막으로, 각 앱들은 kustomize로 관리되고 있는 상태이므로 해당 리소스가 커스토마이즈로 관리되도록 명시를 해주는 과정을 넣어주어야 한다.

git add apps/orders
git commit -m "Orders: Create HPA and remove replicas field in deployments"
git push

image.png
제대로 싱크가 된 것을 확인하고, 블루 노드 그룹을 없애보자.
단순하게 테라폼에서 블루 노드 그룹 블록을 지우고 apply를 하면 된다.
image.png
image.png
문제 없이 파드가 그린에 해당하는 노드로 옮겨간 것을 확인할 수 있다.

문제 정리

https://catalog.us-east-1.prod.workshops.aws/event/dashboard/en-US/workshop/module-3/20-mangednodegroupupgrade/new-mng
이 페이지에서 깃 반영을 하더라도 실제로는 레플리카가 ignore 되어 있다.
이것에 대한 설명을 추가하거나, 아니면 커스텀된 설정을 넣어주는 것이 혼란을 줄일 수 있다.

카펜터

다음으로 진행해볼 것은 카펜터로 프로비저닝되는 노드이다.
근데 사실 이건 간단한 게, 카펜터의 노드클래스로 ami를 명시하는 부분만 바꿔주면 된다.
image.png
image.png
현재는 1.25 버전을 지원하는 ami를 쓰고 있으므로, 이걸 1.26 버전에 해당하는 ami로 바꾸기만 하면 된다.
amiSelectorTerms 필드를 수정해주기만 하면 되는데, 사실 이 필드를 그냥 없애면 amiFamily 조건에 따라 AL2를 고르되 컨트롤 플레인의 버전에 맞는 최신 버전의 AMI를 알아서 골라준다.
즉, 이후에 클러스터 버전을 더 업그레이드한다고 해도 카펜터가 알아서 워커 노드의 버전이 컨트롤플레인의 버전에 맞도록 반영해준다는 말이다.
다만 이 방식은 해당 버전에서 지원되는 최신 ami가 또 나온다면 그것대로 또 알아서 노드를 교체해주게 되니, 그런 상황을 의도한 게 아니라면 일찌감치 amiSelectorTerms로 특정한 ami id를 지정하거나 필터링을 하는 것이 좋겠다.

 aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.26/amazon-linux-2/recommended/image_id \
    --region ${AWS_REGION} --query "Parameter.Value" --output text

이렇게 어떤 클러스터 버전에 어떤 os에서 가장 추천되는 ami가 무엇인지 확인할 수 있다.
하지만 나는 그냥 amiFamily만을 기반으로 알아서 카펜터가 새 버전의 노드를 프로비저닝하는 것을 보기로 결정했다.
image.png
방식이야 아까와 다를 것 없이, 카펜터 앱의 코드를 변경하여 푸시하면 아르고 CD가 알아서 반영한다.
image.png
단순하게 describe를 해서 노드클래스 리소스를 보면, 조건에 매칭된 ami가 뭐가 있는지 표시된다.
image.png
기다리다보면 이렇게 새 노드가 갑자기 생기게 된다.
image.png
그리고 이전 버전의 노드는 사라졌다.
image.png
새로 들어온 노드는 정확하게 1.26 버전이 것을 확인할 수 있다.
image.png
image.png
이제 보니 실질적으로 추천하는 ami가 그냥 최신 버전의 ami인 건지 결국에는 별도의 설정을 하지 않아도 추천된 ami로 노드가 세팅됐다.

image.png
셀프형 노드 그룹은 테라폼에서 ami를 바꿔서 하는 수밖에 없다.
카펜터에 세팅된 것과 같은 ami를 넣어주면 된다.

파게이트

마지막으로 파게이트 노드는.. 그냥 파드를 재기동하면 된다.
파게이트는 기본적으로 컨트롤 플레인의 버전에 맞춰 자원이 프로비저닝된다.
그렇기 때문에 컨트롤 플레인만 업그레이드됐다면, 새로 파게이트 자원을 프로비저닝하기만 하면 업그레이드가 완료되는 셈이다.

EKS 블루그린 업그레이드 실습

인플레이스 업그레이드는 겨우 한 버전씩만 올리는 것에 그치지만, 블루그린의 경우 원하는 만큼 점프를 하기 쉽다!
실습에서는 같은 vpc안에 클러스터를 구축하는데, 장점은 다음과 같다.

등등..

그린 클러스터 구축

이미 제공돼있는 그린 버전의 테라폼 코드를 실행한다.
당연히 기존 버전과 실질적인 양식은 거의 동일하다.

export EFS_ID=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
echo $EFS_ID
terraform init
terraform apply -var efs_id=$EFS_ID

efs의 id를 같이 넣으면서 실행해주면 된다.
image.png
새로운 버전의 클러스터가 1.30 버전으로 올라왔다.

aws eks --region ${AWS_REGION} update-kubeconfig --name ${EKS_CLUSTER_NAME} --alias blue && \
  kubectl config use-context blue

aws eks --region ${AWS_REGION} update-kubeconfig --name ${EKS_CLUSTER_NAME}-gr --alias green && \
  kubectl config use-context green

간단하게 클러스터 컨텍스트를 변경할 수 있도록 세팅한다.
image.png
해당 클러스터는 카펜터와 아르고 cd까지만 기본으로 설치돼있다.
샘플 어플리케이션을 본격적으로 배포하기 위해 아르고 cd에 레포를 연결하고 어플리케이션을 만드는 작업을 본격적으로 진행한다.

cd ~/environment/eks-gitops-repo
git switch -c green
git branch -a

현재 main 브랜치에서 그린 브랜치를 파고, 이 브랜치를 통해 그린 클러스터를 구축한다.

export AL2023_130_AMI=$(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)
 cat << EOF > ~/environment/eks-gitops-repo/apps/karpenter/default-ec2nc.yaml
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  amiSelectorTerms:
  - id: "${AL2023_130_AMI}" # Latest EKS 1.30 AMI
  role: karpenter-eksworkshop-eksctl-gr
  securityGroupSelectorTerms:
  - tags:
      karpenter.sh/discovery: eksworkshop-eksctl-gr
  subnetSelectorTerms:
  - tags:
      karpenter.sh/discovery: eksworkshop-eksctl
  tags:
    intent: apps
    managed-by: karpenter
    team: checkout
EOF

일단 카펜터의 경우 고르는 ami를 바꿔줘야 한다.
특이사항으로는 업그레이드를 진행하면서 ami 패밀리 자체를 AL2023으로 바꿨다는 것 정도?

pluto detect-files -d ~/environment/eks-gitops-repo/

이밖에도 버전 호환성과 업데이트를 위한 툴 중 하나인 pluto를 이용해서 각 양식의 버전 상태를 확인해볼 수 있다.
image.png
위에서도 봤듯이, hpa의 api 버전이 deprecated됐다.

cd ~/environment/eks-gitops-repo/
kubectl convert -f apps/ui/hpa.yaml --output-version autoscaling/v2 -o yaml > apps/ui/tmp.yaml && mv apps/ui/tmp.yaml apps/ui/hpa.yaml
cat apps/ui/hpa.yaml

버전을 바꾸는 작업은 convert라는 플러그인을 사용한다.

# 최신 버전으로 바꾸어 출력
kubectl convert -f pod.yaml --local -o json
# 해당 디렉토리의 모든 양식을 최신 버전으로 적용
kubectl convert -f . | kubectl create -f -
# 출력 버전 지정
kubectl convert -f hpa.yaml --output-version autoscaling/v2

이 플러그인은 직접적으로 버전을 명시해서 사용하는 것도 가능하지만, 현재 클러스터에 기반하여 특정 kind의 최신 버전으로 바꾸어 출력하는 기능도 제공하기에 잘 쓰면 꽤나 유용하다.

sed -i 's/targetRevision: main/targetRevision: green/' app-of-apps/values.yaml
git add .  && git commit -m "1.30 changes"
git push -u origin green

이제 새로운 상위 app을 배포한다.

export ARGOCD_SERVER_GR=$(kubectl get svc argo-cd-argocd-server -n argocd -o json --context green | jq --raw-output '.status.loadBalancer.ingress[0].hostname')
echo "ArgoCD URL: http://${ARGOCD_SERVER_GR}"
export ARGOCD_USER_GR="admin"
export ARGOCD_PWD_GR=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" --context green | base64 -d)
echo "Username: ${ARGOCD_USER_GR}"
echo "Password: ${ARGOCD_PWD_GR}"
argocd login --name green ${ARGOCD_SERVER_GR} --username ${ARGOCD_USER_GR} --password ${ARGOCD_PWD_GR} --insecure --skip-test-tls --grpc-web

이전에는 웹 ui를 통해 세팅을 확인하고 진행했으나, 이번에는 세팅은 cli를 통해 해보자.
(근데 여태 받은 느낌으로는 아르고 cd는 다른 아르고 시리즈보다도 ui가 훨씬 잘돼있어서 ui를 주로 사용법을 익히는 것도 괜찮을 것 같기는 하다.)

argo_creds=$(aws secretsmanager get-secret-value --secret-id argocd-user-creds --query SecretString --output text)

argocd repo add $(echo $argo_creds | jq -r .url) --username $(echo $argo_creds | jq -r .username) --password $(echo $argo_creds | jq -r .password) --server ${ARGOCD_SERVER_GR}

image.png
같은 레포지토리를 쓰기 때문에 여기까지는 블루 클러스터와 다를 게 없다.

argocd app create apps --repo $(echo $argo_creds | jq -r .url) --path app-of-apps \
  --dest-server https://kubernetes.default.svc --sync-policy automated --revision green --server ${ARGOCD_SERVER_GR}

image.png
다만 이번에는 revision을 green으로 하여, 위에서 수정한 브랜치를 기준으로 앱을 배포한다.
app of apps 패턴으로 인해 세팅된 모든 앱들이 배포되기 시작한다.

이제 두 개의 클러스터가 있는 상태이므로, 그린 클러스터를 테스트하고 문제가 없다면 블루 클러스터를 제거하는 식으로 업그레이드를 마치면 된다.
현 실습에서는 따로 도메인을 제공해주지 않으나, 실제 서비스를 운영하고 있다면 각종 서비스를 위해 도메인을 두고 있을 것이다.
이때 도메인 기반으로 라우팅을 진행하며 트래픽을 점진적으로 그린 클러스터로 바꾸는 식으로 마이그레이션을 하는 방식을 생각해볼 수 있다.

external-dns.alpha.kubernetes.io/set-identifier: {{ .Values.spec.clusterName }}
external-dns.alpha.kubernetes.io/aws-weight: '{{ .Values.spec.ingress.route53_weight }}'

External DNS를 이용할 경우 위의 어노테이션을 이용해 각 클러스터 별로 라우팅되는 트래픽을 가중치로 나누어 전달하는 전략이 유효할 수 있다.

추가 고려사항

블루그린에서 고려해야 할 가장 어려운 사항 중 하나가 바로 상태 유지 어플리케이션이다.
실습 상에서는 EFS를 이용하고, 스테이트풀셋으로 프로비저닝된 자원을 그린 버전에서 그대로 받아보는 식으로 실습을 진행한다.
그래도 실습 상에서는 다중 가용영역에서 멀티 어태치가 가능한 EFS를 사용하기에 그나마 용이하다고 할 수 있겠다.
그러나 실제 운영 환경에서 블루 그린 업그레이드를 진행한다면, 이보다 훨씬 엄밀한 접근이 필요하다.
일단 특정 파일이나 프로세스를 블루 클러스터의 어플리케이션이 독점하는 경우도 있을 수 있다.
대표적으로 대부분의 RDBMS는 DB 프로세스가 스토리지를 완전히 lock을 걸어버린다.
또 EBS의 경우 한 스토리지는 한 가용 영역에서만 사용할 수 있으며 인스턴스와 1대1 연결이 기본이기에(multi attaching의 경우 Nitro System을 이용한다던가 하는 추가 제약이 있다[2]), 볼륨 스냅샷을 뜨거나 그냥 스토리지를 복사하여 그린 클러스터에 배치해주는 방식의 전략을 생각해볼 수 있다.
이렇게 사용한다고 해도 복제된 스토리지 간의 동기화를 고려해야 하며, 이를 위해 데이터를 클러스터와 더 격리된 환경에서 관리하는 리소스를 추가적으로 투입해야 할 수 있다.

결론

내 로컬 환경에서 클러스터 버전을 업그레이드했던 경험이 있었다.
그러나 이것은 사실 단순하게 CKA 시험을 준비하기 위해 kubeadm으로 딸깍 해보는 정도에 그쳤을 뿐이다.
만약 다양한 어플리케이션이 구동되고 있는 상황에서 업그레이드를 진행했더라면 더욱 많은 고려사항을 두고 점진적으로 업그레이드를 진행했을 것 같다.
EKS에서의 업그레이드 역시 고려사항을 면밀히 검토할 필요가 있다.
그러나 최소한 컨트롤 플레인에 대한 업그레이드를 사용자가 책임 질 필요는 없다는 점은 확실한 장점이라고 이야기할 수 있을 것 같다.

생각

이것도 장점이라 볼 수 있을지는 모르겠는데, 사실 EKS를 쓰게 된다면 클러스터 버전 업그레이드는 사실상 안 하면 돈을 내야 하기에 반강제나 다름 없다.
버전을 업그레이드를 하는 것은 어렵고 사이드 이펙트를 많이 고려해야 하지만, 그래도 그럼으로서 얻는 이득이 또 있기도 하다.
그런 면에서 배수진(?!)을 치게 만드는 것도.. EKS의 장점..?이라 할 수 있지 않을까?
운영팀의 입장에서는 언젠가 해야 할 일이 분명한 사항이면서도 조직에 해당 업무의 당위성을 충분히 어필할 수 있으니..
경험해보지 않았으니 말은 조심해야겠지만, 그래도 업그레이드를 주기적으로 하는 것은 궁극적으로는 서비스를 안정적으로 운영하도록 하는데 도움이 될 것이라 생각한다.

이전 글, 다음 글

다른 글 보기

이름 index noteType created
1W - EKS 설치 및 액세스 엔드포인트 변경 실습 1 published 2025-02-03
2W - 테라폼으로 환경 구성 및 VPC 연결 2 published 2025-02-11
2W - EKS VPC CNI 분석 3 published 2025-02-11
2W - ALB Controller, External DNS 4 published 2025-02-15
3W - kubestr과 EBS CSI 드라이버 5 published 2025-02-21
3W - EFS 드라이버, 인스턴스 스토어 활용 6 published 2025-02-22
4W - 번외 AL2023 노드 초기화 커스텀 7 published 2025-02-25
4W - EKS 모니터링과 관측 가능성 8 published 2025-02-28
4W - 프로메테우스 스택을 통한 EKS 모니터링 9 published 2025-02-28
5W - HPA, KEDA를 활용한 파드 오토스케일링 10 published 2025-03-07
5W - Karpenter를 활용한 클러스터 오토스케일링 11 published 2025-03-07
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 12 published 2025-03-15
6W - api 구조와 보안 1 - 인증 13 published 2025-03-15
6W - api 보안 2 - 인가, 어드미션 제어 14 published 2025-03-16
6W - EKS 파드에서 AWS 리소스 접근 제어 15 published 2025-03-16
6W - EKS api 서버 접근 보안 16 published 2025-03-16
7W - 쿠버네티스의 스케줄링, 커스텀 스케줄러 설정 17 published 2025-03-22
7W - EKS Fargate 18 published 2025-03-22
7W - EKS Automode 19 published 2025-03-22
8W - 아르고 워크플로우 20 published 2025-03-30
8W - 아르고 롤아웃 21 published 2025-03-30
8W - 아르고 CD 22 published 2025-03-30
8W - CICD 23 published 2025-03-30
9W - EKS 업그레이드 24 published 2025-04-02
10W - Vault를 활용한 CICD 보안 25 published 2025-04-16
11W - EKS에서 FSx, Inferentia 활용하기 26 published 2025-04-18
11주차 - EKS에서 FSx, Inferentia 활용하기 26 published 2025-05-11
12W - VPC Lattice 기반 gateway api 27 published 2025-04-27

관련 문서

이름 noteType created
컨테이너 프로브 knowledge 2024-08-22
CICD knowledge 2024-06-13
KEDA knowledge 2024-12-29
Karpenter knowledge 2025-03-04
Argo CD knowledge 2025-03-24
GitOps knowledge 2025-03-24
Argo Workflows knowledge 2025-03-24
Argo Rollouts knowledge 2025-03-24
아르고 롤아웃과 이스티오 연계 knowledge 2025-04-22
uv knowledge 2025-05-02
메시 배포 모델 knowledge 2025-05-21
Istio WorkloadGroup knowledge 2025-05-26
Istio WorkloadEntry knowledge 2025-05-26
이스티오 스케일링 topic 2025-05-18
엔보이에 와즘 플러그인 적용해보기 topic 2025-06-09
k8s air-gap install topic 2025-06-09
폐쇄망 k8s 설치 개요 topic 2025-06-09
에어갭 kubespray 단일 노드 설치 with Vagrant topic 2025-06-09
kubespray 삽질 topic 2025-06-11
kubesphere topic 2025-06-12

참고


  1. https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html ↩︎

  2. https://docs.aws.amazon.com/ko_kr/ebs/latest/userguide/ebs-volumes-multi.html ↩︎