[네떡스터디🔥kans] AWS - EKS와 AWS LoadBalancer Controller

CloudNet@ 가시다님이 진행하는 쿠버네티스 네트워크 스터디 KANS 3기 내용을 정리한 글입니다.

 

 

 

aws에서는 eks의 로드밸런서를 구성하기 위해 aws loadbalancer controller를 제공합니다. 설치를 하지 않은 경우에는 NLB로 구성이 되지만 CLB(Classic Loadbalancer) 혹은 ALB(Application LoadBalancer)를 사용하고 싶은 경우에는 별도의 설치가 필요합니다.

 

설치하기

AWS Loadbalancer Controller는 EKS에만 설치할 수 있는 컨트롤러는 아니지만 EKS에 설치하는 방법에 대해서만 소개하도록 하겠습니다.

 

IAM 정책 다운로드 및 생성

# 정책 다운로드
curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.10.0/docs/install/iam_policy.json

# IAM 정책 생성
aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://iam-policy.json
    
aws iam get-policy --policy-arn <porlicy-arn>

 

생성 결과도 확인해볼 수 있습니다.

IRSA로 구성하기

OIDC 설정하기

Amazon EKS에서 IAM Roles for Service Accounts(IRSA)를 사용하려면 먼저 클러스터에 OIDC 공급자를 등록해야 합니다. 이 과정은 EKS 클러스터가 IRSA를 통해 Kubernetes 서비스 계정에 IAM 역할을 안전하게 제공할 수 있도록 합니다.

 

OIDC란?
OIDC(OpenID Connect)는 인증 서버에서 수행한 인증을 기반으로 애플리케이션이 사용자의 신원을 검증할 수 있도록 해주는 인증 프로토콜입니다. EKS에서는 OIDC를 통해 Kubernetes와 AWS 간 안전한 통신이 가능해지며, 이를 통해 Kubernetes 서비스 계정이 IAM 역할을 가질 수 있습니다.

 

다음 eksctl 명령어를 사용해서 oidc를 등록합니다.

eksctl utils associate-iam-oidc-provider \
    --region <region-code> \
    --cluster <your-cluster-name> \
    --approve

 

서비스 어카운트 설정하기

ekctl 명령어를 사용해서 kube-system 네임스페이스에 aws-load-balancer-controller 서비스 어카운트에 정책을 붙입니다.

--override-existing-serviceaccounts 옵션으로 서비스어카운트가 없는 경우 서비스 어카운트를 생성합니다. 

eksctl create iamserviceaccount \
--cluster=<cluster-name> \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::<AWS_ACCOUNT_ID>:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--region <region-code> \
--approve

 

명령어가 수행되고 생성된 것을 확인할 수 있습니다.

 

이게 아닌 kubectl로 접근 가능한 경우 서비스 어카운트에 직접 정책을 설정할 수도 있습니다. 만약 이미 파드가 생성되어있는데 해당 설정을 한다면 이후에 파드를 재시작해줘야합니다. 

apiVersion: v1
kind: ServiceAccount
metadata:
  name: aws-load-balancer-controller
  namespace: kube-system
  annotations:
    eks.amazonaws.com/role-arn: ${CARTS_IAM_ROLE}

 

IAM 정책을 노드에 직접 부착

IRSA를 사용하지 않는 경우에는 노드의 IAM 정책을 직접 부착해야 합니다. 위에서 다운로드한 정책을 노드 그룹의 IAM 역할에 부착하거나 EC2 인스턴스 프로파일에 추가하면 됩니다.

aws iam put-role-policy --role-name <노드-역할-이름> \
  --policy-name LoadBalancerPolicy --policy-document file://iam-policy.json

 

Helm으로 설치하기

이제 Helm을 사용해 AWS Load Balancer Controller를 설치해보겠습니다. 우선 Helm 레포지터리를 추가합니다.

helm repo add eks https://aws.github.io/eks-charts

 

IRSA로 설치하는 경우

위에서 정책을 생성할 때 서비스 어카운트가 이미 생성되었기 때문에 AWS Load Balancer Controller를 설치할 때 서비스 어카운트를 새로 생성하지 않고, 기존 이름을 사용하도록 설정합니다.

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system --set clusterName=<cluster-name> \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

 

버전은 가장 최신 버전인 v2.10.0을 설치했습니다.

 

IRSA로 설치하지 않는 경우

IRSA를 사용하지 않고 노드의 IAM 권한을 이용할 때는 차트의 수정은 필요하지 않습니다.

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=<cluster-name>

 

 

Service

테스트용 Cluster IP 생성하기

기존의 nginx 파드에 서비스를 연결하여 테스트용 Cluster IP를 생성했습니다. 아래의 명령어를 통해 nginx 디플로이먼트를 서비스에 노출시킵니다.

kubectl expose deployment nginx --port 80

 

 

출발지 노드에서 서비스 IP와의 통신을 확인해보면, 서비스와의 연결이 이루어지는 것을 확인할 수 있습니다. 이제 반대로 도착지 노드에서도 같은 서비스 IP를 통해 통신이 이루어지는지 확인해보겠습니다.

 

 

 

서비스 IP로의 통신을 확인하기 위해 아래 명령어를 사용해 보았으나, 들어오는 요청이 없는 것을 확인할 수 있었습니다.

sudo tcpdump -i any host 10.100.13.87

 

 

출발지 주소를 출발지 파드의 IP로 변경하여 패킷 덤프를 확인해 보겠습니다:

sudo tcpdump -i any host 192.168.1.176

 

 

패킷 덤프의 흐름을 분석한 결과, 출발지 노드에서 iptables에 의해 목적지 파드가 결정되고, 목적지 파드의 ENI(Elastic Network Interface)를 통해 통신이 이루어지는 것을 확인할 수 있습니다.

 

 

 

또한, 출발지 노드에서도 목적지 파드의 IP를 대상으로 덤프를 떠보면 해당 노드로의 통신이 정상적으로 이루어지는 것을 확인할 수 있습니다.

 

CLB (Classic LoadBalancer)

aws loadbalancer controller를 사용하지 않고 LoadBalancer 타입으로 서비스를 변경하면 아래처럼 Classic LoadBalancer가 생성됩니다. 

 

NLB (Network LoadBalancer)

nlb부터는 aws loadbalancer controller가 있어야 생성을 할 수 있습니다. 인스턴스 모드와 IP 모드 두가지가 존재하며 각각의 모드에 대해 아래에서 설명하도록 하겠습니다. 

 

 

인스턴스 모드

NLB가 대상 그룹 내의 EC2 인스턴스와 직접 연결하여 트래픽을 분산하는 방식입니다.

 

위에서 생성한 nginx clb를 nlb로 변경해보겠습니다. 기본 loadbalancer에 annotation으로 instance 모드로 nlb를 구성하는 것을 설정합니다. 기본 clb구성과 동일하게 하기 위해서는 internet-facing으로 설정도 해야합니다. 별도로 설정을 하지 않는 경우에는 ineternal 모드로 설정되게 됩니다. 

apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing 
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: LoadBalancer

 

기존 것이 변경되는것이 아니라 신규로 구성되기 시작했습니다. 

 

변경이 완료된 이후지만 기존의 clb가 삭제가 되지 않고 있는 것으로 보입니다. 별도로 삭제하는 옵션이 있는지 확인이 필요할 것 같습니다. 

 

서비스를 삭제한 이후에도 clb는 남아있는 것으로 확인이 됩니다.

 

IP 모드

NLB가 대상 그룹에 등록된 IP 주소로 트래픽을 라우팅하는 방식입니다. 여기서 대상은 EC2 인스턴스뿐만 아니라 온프레미스 서버나 컨테이너 등 다양한 IP 기반 서비스가 될 수 있습니다. 

 

이번엔 신규로 서비스객체를 만들어 nlb로 변경해보겠습니다.

kubectl expose deployment nginx --port 80 --name nginx-ip

 

이제 아이피 모드로 변경을 해보도록 하겠습니다. 

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: nginx
  name: nginx-ip
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: LoadBalancer

 

구성이 완료가 되면 대상그룹에는 IP가 타겟으로 설정되게 됩니다. 

 

만약 파드가 바로 변경이 된다고 하였을 때에도 바로 업데이트가 되는지 확인을 해보려고 합니다.

#!/bin/bash

# The URL to be called
URL="k8s-default-nginxip-92b5a1987a-86393baeb7f5d359.elb.ap-northeast-2.amazonaws.com"

# Loop indefinitely
while true; do
    # Capture the current timestamp
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

    # Call the URL with a 1-second timeout and capture the response
    RESPONSE=$(curl --max-time 1 -s -o /dev/null -w "%{http_code}" "$URL")

    # Save the timestamp and response status to a log file
    echo "$TIMESTAMP - Status: $RESPONSE" >> status_log.txt

    # Wait for 0.5 seconds before the next call
    sleep 0.5

done

 

이 스크립트를 호출한 뒤 테스트해본 결과 바로 업데이트가 되지 않아 에러가 바로 발생하는 것을 확인할 수 있습니다. 

 

ALB (Application LoadBalancer) <작성 필요>

TBD;

 

Ingress <작성 필요>

만약 기본 설정으로 설치하는 경우 alb라는 이름의 기본 ingress class가 생성이 됩니다. 이 설정에는 정말 아무런 특별한 설정이 들어있지 않습니다. 파라미터를 수정하여 더 추가적인 설정을 할 수 있습니다. 

 

 

 

(✰) 멀티 클러스터 지원 - multiClusterTargetGroup <테스트 필요>

멀티 클러스터를 사용하는 사용자들이 많아지면서 지원되게 된 기능인 것 같습니다. 하나의 로드밸런서로부터 다수의 클러스터를 타겟 그룹으로 사용할 수 있습니다.

aws load balancer controller v2.10.0 이상의 버전부터 지원이 가능하고 쿠버네티스는 1.22이상의 버전이 필요합니다. eks를 사용하는 경우에는 일반적으론 업그레이드를 해야하기 때문에 대다수의 사용자들이 사용할 수 있는 기능으로 보입니다.

 

설정 옵션

ALB를 사용하는 경우

alb.ingress.kubernetes.io/multi-cluster-target-group: "true"

 

NLB를 사용하는 경우

service.beta.kubernetes.io/aws-load-balancer-multi-cluster-target-group: "true"

 

설정 방법

두개의 EKS 클러스터가 있다고 가정해보겠습니다.

A 클러스터

A 클러스터에 Ingress를 생성합니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echoserver
  namespace: echoserver
  annotations:
    alb.ingress.kubernetes.io/multi-cluster-target-group: "true"    
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/tags: Environment=dev,Team=test
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Exact
            backend:
              service:
                name: echoserver
                port:
                  number: 80

 

생성한 Ingress로 나온 Target Group Binding 값을 확인해봅니다.

kubectl -n echoserver get targetgroupbinding k8s-echoserv-echoserv-cc0122e143 -o yaml

 

Spec에 있는 targetGroupARN 값을 B클러스터에 등록해야합니다.

apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  annotations:
    elbv2.k8s.aws/checkpoint: cKay81gadoTtBSg6uVVginqtmCVG-1ApTvYN4YLD37U/_4kBy3Yg64qrXzjvIb2LlC3O__ex1qjozynsqHXmPgo
    elbv2.k8s.aws/checkpoint-timestamp: "1729021572"
  creationTimestamp: "2024-10-15T19:46:06Z"
  finalizers:
  - elbv2.k8s.aws/resources
  generation: 1
  labels:
    ingress.k8s.aws/stack-name: echoserver
    ingress.k8s.aws/stack-namespace: echoserver
  name: k8s-echoserv-echoserv-cc0122e143
  namespace: echoserver
  resourceVersion: "79121011"
  uid: 9ceaa2ea-14bb-44a5-abb0-69c7d2aac52c
spec:
  ipAddressType: ipv4
  multiClusterTargetGroup: true <<< 여기에 멀티클러스터 설정
  networking:
    ingress:
    - from:
      - securityGroup:
          groupID: sg-06a2bd7d790ac1d2e
      ports:
      - port: 32197
        protocol: TCP
  serviceRef:
    name: echoserver
    port: 80
  targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:565768096483:targetgroup/k8s-echoserv-echoserv-cc0122e143/6816b87346280ee7
  targetType: instance
  vpcID: vpc-0a7ef5bd8943067a8

 

B 클러스터

여기서 이제 Ingress를 생성합니다. 그리고 TargetGroup Binding의 위의 Target Group ARN을 등록합니다.

apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: MyTargetGroupBinding
  namespace: echoserver
spec:
  serviceRef:
    name: echoserver
    port: 80
  multiClusterTargetGroup: true
  targetType: instance
  ipAddressType: ipv4
  networking:
    ingress:
    - from:
      - securityGroup:
          groupID: $SG_FROM_ABOVE
      ports:
      - port: 32197
        protocol: TCP
  targetGroupARN: $TG_FROM_ABOVE