AWS 기술 블로그

Amazon EKS 멀티 클러스터 로드밸런싱으로 고가용성 애플리케이션 구성하기

소개

Amazon Elastic Kubernetes Service(Amazon EKS)는 AWS 클라우드 환경에서 Kubernetes를 운영하시는데 사용할 수 있는 관리형 서비스입니다. Amazon EKS를 사용하면 AWS의 네트워크 및 보안 서비스와의 통합뿐만 아니라 AWS 인프라의 성능, 규모, 신뢰성 및 가용성을 활용할 수 있습니다. Amazon EKS는 클러스터를 다중 가용영역에 걸쳐 구성하는 방법으로 클러스터에 배포된 애플리케이션의 고 가용성을 지원합니다. 하지만 경우에 따라서는 클러스터 단위의 장애에 대비하기 위해 Amazon EKS 멀티 클러스터의 구성이 필요할 수 있고, 이 경우 애플리케이션으로 유입되는 트래픽을 멀티 클러스터간에 분산하는 것이 필요합니다.
본 게시글에서는 Amazon EKS의 AWS Load Balancer Controller v2.10.0 버전부터 새롭게 추가된 MultiCluster Target Group을 활용하여, Amazon EKS 멀티 클러스터 환경에서 클러스터간에 애플리케이션 트래픽의 부하를 분산하는 방법에 대해 알아봅니다.

멀티 클러스터의 필요성

Amazon EKS는 클러스터 단위로 구성되어 운영됩니다. 이때 하나의 클러스터를 여러 가용 영역(Availability Zone)에 걸쳐 구성할 수 있어서, 클러스터에 배포된 애플리케이션들이 다중 가용영역을 활용하여 고가용성 구성을 이룰 수 있도록 도와줍니다. 하지만 단일 클러스터 환경에서는 클러스터 단위의 구성 변경 작업이나, Kubernetes 사용자들의 숙명인 클러스터의 버전 업그레이드 중 발생할 수 있는 클러스터 단위의 오류 등에 의한 클러스터 단위의 장애가 발생하는 경우를 대비 하기는 쉽지 않습니다. 이러한 클러스터 단위 장애까지 대비할 수 있는 고 가용성 구성을 위해서는 하나의 애플리케이션을 2개 이상의 EKS 클러스터에 분산 배포하는 EKS 멀티 클러스터 구성이 필요할 수 있습니다. 이렇게 멀티 클러스터 환경에 하나의 애플리케이션이 분산 배포되는 경우, 애플리케이션으로 유입되는 트래픽을 EKS 클러스터간에 로드밸런싱하는 것이 필요합니다.
싱글 클러스터 vs. 멀티 클러스터
[그림: 싱글 클러스터 vs. 멀티 클러스터]

지금까지의 멀티 클러스터 로드밸런싱

여러 EKS 클러스터간에 애플리케이션 트래픽의 부하를 분산하는 방법은 다양하게 있을 수 있지만, 일반적으로 크게 두 가지 방법을 주로 사용했습니다. 그 첫 번째는 Amazon Route 53Multivalue answer routing이나 Weighted routing 등의 DNS 기반의 부하 분산을 활용하는 방법이며, 두 번째는 Application Load Balancer(ALB)에서 하나의 리스너에 2개 이상의 Target Group에 대한 Weighted Target Group을 구성하는 방법입니다.

Amazon Route 53 기반의 멀티 클러스터 로드밸런싱

Amazon Route 53을 활용한 DNS 기반의 부하 분산은 EKS 멀티클러스터 환경에서 일반적으로 사용 되는 접근방식 중 하나 입니다. Route 53 기반의 멀티 클러스터 로드밸런싱은 DNS 라우팅 정책을 활용하여 여러 클러스터 간의 트래픽을 분산시키는 방식입니다. 각 클러스터는 자체적인 ALB를 가지며, Route 53의 “Weighted routing” 또는 “Multivalue answer routing” 등을 통해 트래픽을 분산합니다. 이 구성의 장점은 EKS Ingress Controller와의 원활한 통합입니다. 각 클러스터에서 Kubernetes 네이티브 방식으로 ingress 리소스를 관리할 수 있으며, AWS Load Balancer Controller를 통해 ALB를 자동으로 프로비저닝하고 관리할 수 있습니다. 또한 Route 53의 Health check 기능을 활용할 수 있습니다. 이를 통해 클러스터의 상태를 모니터링하고, 장애 발생 시 자동으로 트래픽을 정상 클러스터로 우회시킬 수 있습니다. 하지만 DNS 기반의 부하분산의 특성상 DNS 캐싱으로 인해 즉각적인 트래픽 전환이 어려우며, 클라이언트의 DNS 캐시 정책에 따라 실제 변경사항이 적용되기까지 수분에서 수십 분이 소요될 수 있습니다. 특히 EKS 버전 업그레이드와 같은 클러스터 단위 작업 시 트래픽 조정이 즉각적이지 않으며, 점진적인 트래픽 마이그레이션이 필요한 경우 세밀한 제어가 어렵습니다.
Route 53 기반의 EKS 멀티 클러스터 로드밸런싱
[그림: Route 53 기반의 EKS 멀티 클러스터 로드밸런싱]

ALB Weighted Target group 기반의 멀티 클러스터 로드밸런싱

Route 53을 활용한 DNS 기반의 EKS 클러스터간 부하 분산에서 즉각적이고 세밀한 트래픽 제어가 어려운 측면을 해소하기 위해 EKS 멀티클러스터에서 사용하는 부하분산 방법으로 ALB의 Weighted Target Group을 활용할 수 있습니다. 이 방식은 단일 ALB에서 여러 EKS 클러스터의 워크로드를 Target Group으로 등록하고, 각 Target Group에 가중치를 부여하여 트래픽을 분산시키는 방식입니다. 각 EKS 클러스터의 워크로드는 주로 EKS의 Node port를 이용해 ALB의 Target Group에 연결됩니다. 이 구성의 가장 큰 장점은 즉각적인 트래픽 제어가 가능하다는 점입니다. DNS TTL의 영향 없이 ALB 수준에서 Target Group 간의 가중치를 조절함으로써 실시간으로 트래픽 비율을 변경할 수 있습니다.
클러스터 업그레이드나 워크로드 마이그레이션과 같은 운영 작업 시에도 점진적이고 세밀한 트래픽 제어가 가능합니다. 예를 들어, 새로운 버전의 클러스터로 1%씩 트래픽을 천천히 이동시키는 것이 가능합니다. 이 방식의 주요 단점은 Kubernetes 네이티브의 Ingress 리소스를 활용할 수 없다는 점입니다. 즉, EKS 클러스터 외부에서 ALB를 수동으로 생성하고 관리해야 하며, 이는 추가적인 관리 포인트가 됩니다. 또 다른 중요한 단점은 ALB의 Weighted Target Group 기능이 자동화된 health check 기반의 페일오버(Fail-over)를 지원하지 않는다는 것입니다. Target Group 간의 자동 페일오버가 필요한 경우, 이를 위한 별도의 모니터링과 자동화 로직을 구현해야 합니다.
ALB Weighted Target Group 기반의 EKS 멀티 클러스터 로드밸런싱
[그림: ALB Weighted Target Group 기반의 EKS 멀티 클러스터 로드밸런싱]

AWS Load Balancer Controller의 MultiCluster Target Groups 모드 기반의 멀티 클러스터 로드밸런싱

AWS Load Balancer Controller v2.10.0부터 새롭게 도입된 MultiCluster Target Groups 모드는 기존 멀티 클러스터 로드밸런싱 방식의 한계를 극복하고, Kubernetes 네이티브한 방식으로 멀티 클러스터 로드밸런싱을 구성할 수 있게 해줍니다. 이 새로운 기능은 주 클러스터와 보조 클러스터의 역할을 구분하여 효율적인 멀티 클러스터 로드밸런싱을 제공합니다.
주 클러스터에서는 Ingress 리소스를 통해 ALB를 프로비저닝하고 관리합니다. Ingress 리소스에 “MultiCluster Target Groups” 모드를 활성화하는 어노테이션을 추가하여 멀티 클러스터 로드밸런싱을 구성할 수 있습니다. 보조 클러스터에서는 “TargetGroupBinding” 리소스를 생성하여 주 클러스터에서 생성한 ALB의 동일한 Target Group에 연결됩니다. 이렇게 하나의 Target Group을 두 클러스터가 공유하는 구조로 구성됩니다.
이 기능은 Target Group 레벨에서 자동화된 헬스체크(Health check)를 지원하며, 클러스터 장애 시 자동 페일오버 기능을 제공합니다. 실시간 상태 모니터링을 통해 비정상적인 엔드포인트로의 트래픽을 자동으로 제외시킬 수 있습니다. 이는 클러스터 중 하나에 문제가 발생했을 때 자동으로 정상 클러스터로 트래픽이 전환되도록 합니다.
이 기능을 사용하기 위해서는 먼저 AWS Load Balancer Controller에서 “MultiCluster Target Groups” 기능을 활성화해야 합니다. 주 클러스터의 Ingress 리소스에는 멀티 클러스터 모드 활성화를 위한 어노테이션을 추가하고, 보조 클러스터에서는 “TargetGroupBinding” 리소스를 통해 ALB의 Target Group에 연결하는 구성이 필요합니다.
이러한 새로운 접근 방식은 여러 가지 장점을 제공합니다. Kubernetes 네이티브 방식의 구성으로 관리가 용이하며, DNS TTL의 영향 없이 신속한 페일오버가 가능합니다. 또한 자동화된 헬스체크와 페일오버를 지원하며, 명확한 클러스터 역할 구분을 통해 효율적인 리소스 관리가 가능합니다.
다만 이 기능을 사용하기 위해서는 몇 가지 제한사항이 있습니다. 일반적으로 클러스터들은 같은 Virtual Private Cloud(VPC) 내에 있어야 하며, AWS Load Balancer Controller v2.10.0 이상이 필요합니다. 또한 클러스터 간 네트워크 연결성이 보장되어야 합니다.

실습

이 실습에서는 두 개의 Amazon EKS 클러스터를 생성하고, 각 클러스터에 nginx 애플리케이션을 배포한 후, AWS Load Balancer Controller를 활용하여 “MultiCluster Target Groups” 모드 기반의 멀티클러스터 로드밸런싱을 구성해 보겠습니다. 이를 통해 멀티 클러스터 환경에서의 고 가용성 구성 방법을 실제로 경험할 수 있습니다. 실습은 Amazon Linux 2에서 진행되었으며 다른 OS 환경에서는 일부 보완이 필요할 수 있습니다.
멀티 클러스터 실습 아키텍쳐
[그림: 멀티 클러스터 실습 아키텍쳐]

실습 준비사항

본 실습을 진행하기 위해서는 AWS 계정이 필요하며, 다음 도구들을 이용해서 실습을 진행하므로, 사전 설치가 필요합니다.

  • AWS CLI – AWS 커맨드라인 관리도구, 사전에 AWS 계정 설정이 완료 되어야 합니다. – 참고: 설치 가이드
  • eksctl – EKS 커맨드라인 관리도구 – 참고: 설치 가이드
  • kubectl – Kubernetes 커맨드라인 관리도구- 참고: 설치 가이드
  • jq – 커맨드라인 JSON 파싱 도구 – 참고: 설치 가이드

실습에 사용할 다음의 환경 변수를 설정합니다.

# AWS 계정 ID와 리전 설정
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION=ap-northeast-2

VPC 생성

다음 명령어로 VPC 및 그와 관련 리소스를 생성합니다.

# VPC 생성
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --region $AWS_REGION --query 'Vpc.VpcId' --output text)
aws ec2 create-tags --resources $VPC_ID --tags Key=Name,Value=eks-multi-cluster-vpc --region $AWS_REGION

# 서브넷 생성
PUBLIC_SUBNET1_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24 --availability-zone ${AWS_REGION}a --region $AWS_REGION --query 'Subnet.SubnetId' --output text)
PUBLIC_SUBNET2_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.2.0/24 --availability-zone ${AWS_REGION}c --region $AWS_REGION --query 'Subnet.SubnetId' --output text)
PRIVATE_SUBNET1_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.3.0/24 --availability-zone ${AWS_REGION}a --region $AWS_REGION --query 'Subnet.SubnetId' --output text)
PRIVATE_SUBNET2_ID=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.4.0/24 --availability-zone ${AWS_REGION}c --region $AWS_REGION --query 'Subnet.SubnetId' --output text)

# 서브넷 태그 지정
aws ec2 create-tags --resources $PUBLIC_SUBNET1_ID --tags Key=Name,Value=eks-public-subnet-1 Key=kubernetes.io/role/elb,Value=1 --region $AWS_REGION
aws ec2 create-tags --resources $PUBLIC_SUBNET2_ID --tags Key=Name,Value=eks-public-subnet-2 Key=kubernetes.io/role/elb,Value=1 --region $AWS_REGION
aws ec2 create-tags --resources $PRIVATE_SUBNET1_ID --tags Key=Name,Value=eks-private-subnet-1 Key=kubernetes.io/role/internal-elb,Value=1 --region $AWS_REGION
aws ec2 create-tags --resources $PRIVATE_SUBNET2_ID --tags Key=Name,Value=eks-private-subnet-2 Key=kubernetes.io/role/internal-elb,Value=1 --region $AWS_REGION

# 인터넷 게이트웨이 생성 및 연결
IGW_ID=$(aws ec2 create-internet-gateway --region $AWS_REGION --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID --region $AWS_REGION

# NAT 게이트웨이 생성
EIP_ALLOC_ID=$(aws ec2 allocate-address --domain vpc --region $AWS_REGION --query 'AllocationId' --output text)
NAT_GW_ID=$(aws ec2 create-nat-gateway --subnet-id $PUBLIC_SUBNET1_ID --allocation-id $EIP_ALLOC_ID --region $AWS_REGION --query 'NatGateway.NatGatewayId' --output text)

# 라우팅 테이블 설정
PUBLIC_RT_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --region $AWS_REGION --query 'RouteTable.RouteTableId' --output text)
PRIVATE_RT_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --region $AWS_REGION --query 'RouteTable.RouteTableId' --output text)

aws ec2 create-route --route-table-id $PUBLIC_RT_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID --region $AWS_REGION
aws ec2 create-route --route-table-id $PRIVATE_RT_ID --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_GW_ID --region $AWS_REGION

aws ec2 associate-route-table --subnet-id $PUBLIC_SUBNET1_ID --route-table-id $PUBLIC_RT_ID --region $AWS_REGION
aws ec2 associate-route-table --subnet-id $PUBLIC_SUBNET2_ID --route-table-id $PUBLIC_RT_ID --region $AWS_REGION
aws ec2 associate-route-table --subnet-id $PRIVATE_SUBNET1_ID --route-table-id $PRIVATE_RT_ID --region $AWS_REGION
aws ec2 associate-route-table --subnet-id $PRIVATE_SUBNET2_ID --route-table-id $PRIVATE_RT_ID --region $AWS_REGION

#NAT 게이트웨이가 생성될 때 까지 대기
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW_ID

EKS 클러스터 생성

두 개의 EKS 클러스터를 생성합니다:
각 클러스터는 2개의 public subnet과 2개의 private subnet를 이용하며, AWS Load Balancer Controller가 ALB등 AWS 리소스를 생성할 수 있도록 하는 IAM roles for service accounts(IRSA)를 구성하기 위한 OIDC가 활성화 되어 있으며, Managed Node Group이 1개씩 구성됩니다.

# 첫 번째 EKS 클러스터 생성
eksctl create cluster \
  --name eks-cluster-1 \
  --region $AWS_REGION \
  --version 1.31 \
  --vpc-public-subnets $PUBLIC_SUBNET1_ID,$PUBLIC_SUBNET2_ID \
  --vpc-private-subnets $PRIVATE_SUBNET1_ID,$PRIVATE_SUBNET2_ID \
  --nodegroup-name ng-1 \
  --node-private-networking \
  --node-type t3.medium \
  --nodes 2 \
  --nodes-min 2 \
  --nodes-max 4 \
  --with-oidc \
  --managed

# 두 번째 EKS 클러스터 생성
eksctl create cluster \
  --name eks-cluster-2 \
  --region $AWS_REGION \
  --version 1.31 \
  --vpc-public-subnets $PUBLIC_SUBNET1_ID,$PUBLIC_SUBNET2_ID \
  --vpc-private-subnets $PRIVATE_SUBNET1_ID,$PRIVATE_SUBNET2_ID \
  --nodegroup-name ng-1 \
  --node-private-networking \
  --node-type t3.medium \
  --nodes 2 \
  --nodes-min 2 \
  --nodes-max 4 \
  --with-oidc \
  --managed

이후 kubectl의 context 변경을 위해 Context 이름을 환경변수로 저장합니다.

# Context 이름을 환경변수로 저장
CONTEXT_CLUSTER_1=$(kubectl config get-contexts -o name | grep eks-cluster-1.${AWS_REGION}.eksctl.io)
CONTEXT_CLUSTER_2=$(kubectl config get-contexts -o name | grep eks-cluster-2.${AWS_REGION}.eksctl.io)

AWS Load Balancer Controller 설치

각 클러스터에 AWS Load Balancer Controller를 설치합니다.

cert-manager 설치:

# 각 클러스터에 cert-manager 설치 (설치후 초기화 완료까지 시간이 걸립니다)
kubectl config use-context $CONTEXT_CLUSTER_1
kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.3/cert-manager.yaml
kubectl config use-context $CONTEXT_CLUSTER_2
kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.3/cert-manager.yaml

IAM 정책 및 역할 생성:

# IAM 정책 생성
curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.10.0/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam-policy.json

# eks-cluster-1에 대한 IAM 서비스 계정 생성
eksctl create iamserviceaccount \
  --cluster=eks-cluster-1 \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

# eks-cluster-2에 대한 IAM 서비스 계정 생성
eksctl create iamserviceaccount \
  --cluster=eks-cluster-2 \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

AWS Load Balancer Controller 설치:

# AWS Load Balancer Controller v2.10.0 매니페스트 다운로드
wget https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.10.0/v2_10_0_full.yaml

# eksctl에서 iamserviceaccount를 이미 생성 했으므로, ServiceAccount 매니페스트 삭제
awk '
BEGIN {print_line=1; in_service_account=0}
/^---$/ {
    if (in_service_account) {
        in_service_account=0
    } else {
        print $0
    }
    next
}
/^apiVersion: v1$/ {
    hold_line=$0
    getline
    if ($0 ~ /^(- )?kind: ServiceAccount$/) {
        in_service_account=1
    } else {
        if (print_line) print hold_line
        if (print_line) print $0
    }
    next
}
{if (!in_service_account && print_line) print $0}
' v2_10_0_full.yaml > v2_10_0_sa_removed.yaml

# 다운받은 매니페스트의 EKS 클러스터 이름과 AWS Load Balancer Controller를 위한 IAM Role의 ARN값을 eks-cluster-1의 값에 맞춰 수정해서 eks-cluster-1용 매니페스트 생성
sed -e 's|your-cluster-name|eks-cluster-1|;s|eks.amazonaws.com/role-arn: your-role-arn|eks.amazonaws.com/role-arn: '"$(eksctl get iamserviceaccount --cluster eks-cluster-1 --namespace kube-system --name aws-load-balancer-controller -o json | jq -r '.[].status.roleARN')"'|' v2_10_0_sa_removed.yaml > v2_10_0_eks-cluster-1.yaml

# 다운받은 매니페스트의 EKS 클러스터 이름과 AWS Load Balancer Controller를 위한 IAM Role의 ARN값을 eks-cluster-2의 값에 맞춰 수정해서 eks-cluster-2용 매니페스트 생성
sed -e 's|your-cluster-name|eks-cluster-2|;s|eks.amazonaws.com/role-arn: your-role-arn|eks.amazonaws.com/role-arn: '"$(eksctl get iamserviceaccount --cluster eks-cluster-2 --namespace kube-system --name aws-load-balancer-controller -o json | jq -r '.[].status.roleARN')"'|' v2_10_0_sa_removed.yaml > v2_10_0_eks-cluster-2.yaml

# eks-cluster-1에 cert-manager 및 AWS Load Balancer Controller 설치
kubectl config use-context $CONTEXT_CLUSTER_1
kubectl apply -f v2_10_0_eks-cluster-1.yaml
kubectl apply -f https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.10.0/v2_10_0_ingclass.yaml

# eks-cluster-2에 cert-manager 및 AWS Load Balancer Controller 설치
kubectl config use-context $CONTEXT_CLUSTER_2
kubectl apply -f v2_10_0_eks-cluster-2.yaml
kubectl apply -f https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases/download/v2.10.0/v2_10_0_ingclass.yaml

nginx 애플리케이션 배포

각 클러스터에 고유한 nginx 설정을 사용하여 애플리케이션을 배포합니다.

eks-cluster-1용 ConfigMap 생성:

# eks-cluster-1용 ConfigMap 생성
cat << EOF > nginx-config-1.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
      <title>Welcome to nginx on EKS Cluster 1!</title>
    </head>
    <body>
      <h1>Welcome to nginx on EKS Cluster 1!</h1>
      <p>If you see this page, the nginx web server is successfully installed and working on EKS Cluster 1.</p>
    </body>
    </html>
EOF

eks-cluster-2용 ConfigMap 생성:

# eks-cluster-2용 ConfigMap 생성
cat << EOF > nginx-config-2.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
      <title>Welcome to nginx on EKS Cluster 2!</title>
    </head>
    <body>
      <h1>Welcome to nginx on EKS Cluster 2!</h1>
      <p>If you see this page, the nginx web server is successfully installed and working on EKS Cluster 2.</p>
    </body>
    </html>
EOF

Deployment 설정 생성:

# nginx Deployment 설정 생성
cat << EOF > nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      securityContext:
        runAsNonRoot: true
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx-unprivileged:1.27.2  # ECR의 public repository에서 nginx 1.27 버전 사용
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: nginx-config
          mountPath: /usr/share/nginx/html/index.html
          subPath: index.html
        securityContext:
          allowPrivilegeEscalation: false
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-config
EOF

Service 설정 생성:

# nginx Service 설정 생성
cat << EOF > nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
EOF

각 클러스터에 ConfigMap, Deployment, Service를 적용:

# eks-cluster-1에 적용
kubectl config use-context $CONTEXT_CLUSTER_1
kubectl apply -f nginx-config-1.yaml
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml

# eks-cluster-2에 적용
kubectl config use-context $CONTEXT_CLUSTER_2
kubectl apply -f nginx-config-2.yaml
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml

멀티클러스터 로드밸런싱 구성

주 클러스터(eks-cluster-1)에 Ingress 리소스를 생성:

# Ingress 리소스 생성
cat << EOF > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/multi-cluster-target-group: "true" # 멀티클러스터 타겟 그룹 활성화
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-service
                port:
                  number: 80
EOF

kubectl config use-context $CONTEXT_CLUSTER_1
kubectl apply -f ingress.yaml

우선 주 클러스터(eks-cluster-1)의 Ingress 구성이 완료 된 후, 해당 Ingress에서 쓰이는 Target Group의 ARN값을 얻어 옵니다.

# ALB_TARGET_GROUP_ARN 및 Security Group ID 환경 변수 설정
export ALB_TARGET_GROUP_ARN=$(kubectl get targetgroupbindings.elbv2.k8s.aws -l ingress.k8s.aws/stack-name=nginx-ingress -o jsonpath='{.items[0].spec.targetGroupARN}')
export BACKEND_SECURITY_GROUP_ID=$(kubectl get targetgroupbindings.elbv2.k8s.aws -l ingress.k8s.aws/stack-name=nginx-ingress -o jsonpath='{.items[0].spec.networking.ingress[0].from[0].securityGroup.groupID}')

이 후 가져온 Target Group ARN을 이용해서 보조 클러스터(eks-cluster-2)에 TargetGroupBinding을 생성합니다:

# TargetGroupBinding 리소스 생성
cat << EOF > targetgroupbinding.yaml
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: nginx-tgb
spec:
  serviceRef:
    name: nginx-service
    port: 80
  multiClusterTargetGroup: true # 멀티클러스터 타겟 그룹 활성화
  networking:
    ingress:
    - from:
      - securityGroup:
          groupID: ${BACKEND_SECURITY_GROUP_ID} # ALB의 Shared Backend SecurityGroup for LoadBalancer
      ports:
      - port: 8080
        protocol: TCP
  targetGroupARN: ${ALB_TARGET_GROUP_ARN} # 공유해서 사용할 Target Group의 ARN
EOF

kubectl config use-context $CONTEXT_CLUSTER_2
kubectl apply -f targetgroupbinding.yaml

테스트

ALB의 DNS 이름으로 접속하여 nginx 웹페이지가 표시되는지 확인합니다. 여러 번 새로고침하여 요청이 두 클러스터로 분산되는지 확인할 수 있습니다.

우선, Target Group에 각 클러스터의 2개 pod씩 총 4개의 pod의 IP 주소가 등록돼 있는지 확인합니다.

# Target Group에서 IP 주소 가져오기
aws elbv2 describe-target-health --target-group-arn $ALB_TARGET_GROUP_ARN --query 'TargetHealthDescriptions[*].Target.Id' --output text

이때 실습이 정상적으로 이루어졌다면 아래와 같이 4개의 IP 주소가 조회 됩니다.
Target Group에 등록된 IP 조회
[그림: Target Group에 등록된 IP 조회]

이제 nginx에 실제 http 요청을 보내 봅니다.
먼저 현재 서비스되고 있는 ALB의 DNS Name을 가져와 환경변수로 저장합니다.

# ALB_DNS_NAME 환경 변수 설정
kubectl config use-context $CONTEXT_CLUSTER_1
export ALB_DNS_NAME=$(kubectl get ingress nginx-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

curl을 이용해서 ALB으로 요청을 보냅니다. ALB 및 DNS Name이 완전히 활성화 되어 요청을 처리할 수 있기까지 시간이 수분 걸릴 수 있습니다.

# ALB로 요청 보내기
curl http://${ALB_DNS_NAME}

위 명령을 여러 번 실행하면 아래와 같이 “Welcome to nginx on EKS Cluster 1!”과 “Welcome to nginx on EKS Cluster 2!” 메시지가 번갈아 표시되는 것을 확인할 수 있습니다.
curl 테스트 실행 결과
[그림: curl 테스트 실행 결과]

이로써 EKS 멀티 클러스터 환경에서 로드밸런싱 구성이 완료되었습니다. 이 구성을 통해 클러스터 간 고 가용성을 확보하고 효과적인 부하 분산을 실현할 수 있습니다. 다만, 실습 내용을 실제 운영 환경에 적용하기 위해서는 보안 그룹, VPC 설정 등 추가적인 구성이 필요할 수 있으며, 적절한 모니터링 및 로깅 설정이 필요합니다.

리소스 정리

실습이 완료되면 다음 단계를 따라 생성된 리소스를 정리해야 불필요한 비용이 발생하는것을 막을 수 있습니다. 아래 단계들을 모두 수행하면 실습 중 생성한 모든 AWS 리소스가 정리됩니다. 하지만 리소스 삭제 전에 반드시 필요한 정보를 백업하고, 삭제하려는 리소스가 다른 기존의 시스템에 영향을 주지 않는지 확인하시기 바랍니다. 또한, 일부 리소스는 삭제에 시간이 걸릴 수 있으며, 종속성으로 인해 즉시 삭제되지 않을 수 있습니다.

ALB 및 관련 리소스 삭제:

# eks-cluster-2에서 TargetGroupBinding 삭제
kubectl config use-context $CONTEXT_CLUSTER_2
kubectl delete targetgroupbindings.elbv2.k8s.aws nginx-tgb

# eks-cluster-1에서 Ingress 삭제
kubectl config use-context $CONTEXT_CLUSTER_1
kubectl delete ingress nginx-ingress

# ALB가 완전히 삭제될 때까지 기다립니다 (AWS CLI 또는 AWS 콘솔에서 확인 가능)

EKS 클러스터 삭제:

# eks-cluster-1 삭제
eksctl delete cluster --name eks-cluster-1 --region $AWS_REGION

# eks-cluster-2 삭제
eksctl delete cluster --name eks-cluster-2 --region $AWS_REGION

VPC 및 관련 리소스 삭제:

# NAT 게이트웨이 삭제
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_GW_ID --region $AWS_REGION

# 이후 과정은 NAT 게이트웨이가 삭제된 후 진행 (NAT 게이트웨이 삭제까지 수분 걸릴 수 있습니다)
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT_GW_ID

# 서브넷 삭제
aws ec2 delete-subnet --subnet-id $PUBLIC_SUBNET1_ID --region $AWS_REGION
aws ec2 delete-subnet --subnet-id $PUBLIC_SUBNET2_ID --region $AWS_REGION
aws ec2 delete-subnet --subnet-id $PRIVATE_SUBNET1_ID --region $AWS_REGION
aws ec2 delete-subnet --subnet-id $PRIVATE_SUBNET2_ID --region $AWS_REGION

# 라우팅 테이블 삭제
aws ec2 delete-route-table --route-table-id $PUBLIC_RT_ID --region $AWS_REGION
aws ec2 delete-route-table --route-table-id $PRIVATE_RT_ID --region $AWS_REGION

# 인터넷 게이트웨이 분리 및 삭제
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID --region $AWS_REGION
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID --region $AWS_REGION

# VPC 삭제
aws ec2 delete-vpc --vpc-id $VPC_ID --region $AWS_REGION

# NAT 게이트웨이에 할당 됐던 EIP 릴리스
aws ec2 release-address --allocation-id $EIP_ALLOC_ID --region $AWS_REGION

IAM 정책 삭제:

# AWS Load Balancer Controller IAM 정책 삭제
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy --region $AWS_REGION

로컬 파일 정리:

# 생성한 YAML 파일들 삭제
rm nginx-config-1.yaml nginx-config-2.yaml nginx-deployment.yaml nginx-service.yaml ingress.yaml targetgroupbinding.yaml iam-policy.json v2_10_0_full.yaml v2_10_0_sa_removed.yaml v2_10_0_eks-cluster-1.yaml v2_10_0_eks-cluster-2.yaml

환경 변수 정리:

# 설정한 환경 변수들 제거
unset AWS_ACCOUNT_ID AWS_REGION VPC_ID PUBLIC_SUBNET1_ID PUBLIC_SUBNET2_ID PRIVATE_SUBNET1_ID PRIVATE_SUBNET2_ID IGW_ID NAT_GW_ID EIP_ALLOC_ID PUBLIC_RT_ID PRIVATE_RT_ID ALB_TARGET_GROUP_ARN CONTEXT_CLUSTER_1 CONTEXT_CLUSTER_2 BACKEND_SECURITY_GROUP_ID ALB_DNS_NAME

이제 실습을 위해 생성 했던 리소스들이 모두 삭제 됐습니다. 마지막으로 AWS CLI 또는 콘솔을 통해 리소스들이 완전히 삭제 되었는지 확인하는 것이 좋습니다.

결론

본 게시글에서는 EKS 멀티 클러스터 환경에서 기존에 사용하던 부하 분산 방법들을 살펴보고 AWS Load Balancer Controller v2.10.0부터 새롭게 도입된 MultiCluster Target Groups에 대해 알아봤습니다. 그리고 단계별 실습 과정을 통해 해당 기능을 구성하는 방법도 알아봤습니다.
이러한 새로운 접근 방식은 기존의 Route 53 기반 또는 ALB Weighted Target Group 기반의 방식이 가진 한계를 극복하면서, 보다 강력하고 관리하기 쉬운 멀티 클러스터 로드밸런싱 솔루션을 제공합니다. 특히 단일 Target Group을 공유하는 구조를 통해 간단하면서도 효과적인 고 가용성 구성을 실현할 수 있습니다.
실습을 통해 확인한 바와 같이, MultiCluster Target Groups 모드는 Kubernetes 네이티브한 방식으로 멀티 클러스터 로드밸런싱을 구성할 수 있게 해주며, 자동화된 헬스 체크와 페일오버 기능을 제공합니다. 이는 운영 관리의 효율성을 크게 향상시키고, 애플리케이션의 안정성을 높이는 데 도움이 됩니다.
AWS Load Balancer Controller에 대해 더 자세한 내용은 AWS Load Balancer Controller 공식 홈페이지를 참고하시기 바랍니다. 본 게시글에서 다룬 실습 내용을 기반으로 여러분의 환경에 맞게 적용해 보시면, EKS 멀티 클러스터 환경에서의 로드밸런싱 구성에 대한 이해를 더욱 깊이 할 수 있을 것입니다.

Junhwan An

Junhwan An

안준환 솔루션즈 아키텍트는 다양한 분야의 애플리케이션 개발과 클라우드 인프라 구축 및 운영 경험을 바탕으로 고객이 AWS의 여러 서비스들을 효율적으로 이용할 수 있도록 도와드리고 있습니다.

Dongsoo Koo

Dongsoo Koo

구동수 솔루션즈 아키텍트는 디지털 기업 고객을 대상으로 고객의 비즈니스 성과 달성을 위해 고객과 함께 최적의 아키텍처를 구성하는 역할을 수행하고 있습니다.