AWS VPC CNI
AWS VPC CNI(Container Network Interface)는 Amazon EKS 클러스터의 파드 네트워킹을 담당하는 네트워크 플러그인이다.
AWS CNI는 다른 CNI들과는 주요 특징은 다음과 같다.
- 노드와 파드의 네트워크 대역이 같다.
- ENI와 L-IPAM을 함께 활용하여 다른 CNI와 차별화된 네트워크 방식을 제공한다.
- 워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다.

노드와 파드의 네트워크 대역이 같다
노드와 파드의 네트워크 대역이 같다. 이는 VPC의 서브넷 IP를 파드에 직접 할당함으로써, 파드가 마치 VPC 내의 일반적인 EC2 인스턴스처럼 동작할 수 있게 한다. 일반적인 K8S CNI는 오버레이(VXLAN, IP-IP)등으로 통신하기 때문에 오버헤드가 발생해 느릴 수밖에 없다. 하지만 AWS VPC CNI는 동일 대역으로 직접 통신하기 때문에 효율적이다.

L-IPAM
ENI(Elastic Network Interface)와 L-IPAM을 함께 활용하여 다른 CNI와 차별화된 네트워크 방식을 제공한다. ENI는 VPC 내에서 가상 네트워크 랜 카드 역할을 하며, L-IPAM(Local IP Address Management)은 각 노드에서 파드의 IP 할당을 관리한다.

warm pool을 통해 효율적이고 신속하게 파드의 IP를 할당한다. warm pool은 미리 일정 수의 IP 주소를 준비해 두는 메커니즘으로, 새로운 파드가 생성될 때 IP 할당 지연을 최소화한다. 이는 파드의 빠른 시작과 확장을 가능하게 하여 애플리케이션의 응답성을 향상한다.

워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다.
워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다. 이는 각 EC2 인스턴스 타입이 지원하는 ENI의 수와 각 ENI당 할당 가능한 IP 주소 수에 따라 결정된다. 예를 들어, t3.small 인스턴스는 4개의 ENI를 지원하고 각 ENI당 6개의 IP를 할당할 수 있어, 최대 할당 가능한 파드 수가 제한된다.
Prefix Delegation 기능을 사용하면 각 ENI에 단일 IP가 아닌 IP 프리픽스(/28: 16개 IP)를 할당하여, 단일 ENI로 더 많은 파드를 지원할 수 있게 한다

기본 통신 흐름 확인하기
CASE 1: 노드 간 파드 통신
tcp dump를 통해 노드 별 파드의 통신 흐름을 확인해 보면, 노드의 IP를 거치지 않고 파드 IP로 직접 통신하는 것을 볼 수 있다. 예를 들어, 192.168.1.100 파드에서 다른 노드에 있는 192.168.2.100 파드로 통신할 때, 기존 오버레이 네트워크 방식은 노드 IP를 거쳐 통신해야 했지만, AWS VPC CNI에서는 파드가 VPC 서브넷의 IP를 직접 할당받아 사용하기 때문에 노드를 거치지 않고 파드 간 직접 통신이 가능하다

CASE 2: 파드에서 외부 통신
파드의 외부 통신 흐름은 iptables의 SNAT(Source Network Address Translation) 규칙을 통해 이루어진다. 파드가 외부와 통신할 때, 원본 파드의 IP 주소는 노드의 기본 네트워크 인터페이스인 eth0(ens5)의 IP 주소로 변환됩니다.

다음과 같이 iptables 룰을 확인해보면 SNAT 설정이 되어있음을 확인할 수 있다. 또한 VPC CNI의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다
[ec2-user@ip-192-168-1-230 ~]$ sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
-A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.1.230 --random-fully
쿠버네티스 클러스터와 노드 포트에서 통신 동작원리 - 쿠버네티스의 기본적인 서비스에 통신 흐름(iptables)에 관련 내용은 전 포스팅을 참고 바랍니다
Kubernetes Service(Cluster IP & NodePort) 동작 원리 딥다이브 - KANS 4주차
1. Cluster IP개요 클러스터 내부 접근 전용: ClusterIP는 클러스터 내부에서만 접근 가능하며, 도메인(DNS) 기반으로도 접근할 수 있습니다. DNAT 기반 통신: 클라이언트가 ClusterIP로 접근 시 해당 노드의
hackjsp.tistory.com
AWS LoadBalancer Controller
AWS LoadBalancer Controller는 쿠버네티스 클러스터에서 AWS Elastic Load Balancer를 관리하는 컨트롤러이다. 이전에는 AWS ALB Ingress Controller로 알려졌으나, NLB 지원이 추가되면서 현재의 이름으로 변경되었다.
이 컨트롤러는 쿠버네티스의 서비스와 인그레스 리소스를 AWS의 로드밸런서 리소스로 자동 변환하는 역할을 한다. 예를 들어, 개발자가 쿠버네티스에서 서비스나 인그레스를 생성하면, 컨트롤러는 자동으로 해당하는 AWS ALB 또는 NLB를 생성하고 구성한다.
Network Load Balancer (NLB) - Service
NLB는 AWS의 4 계층(TCP/UDP) 로드 밸런서이고, NLB는 아래 두 가지 모드가 존재한다.
- 인스턴스 유형
- IP 유형
인스턴스 유형
Instance 타입은 노드의 NodePort로 트래픽이 전달되고 kube-proxy가 파드로 트래픽을 분배하는 기본적인 기능을 제공합니다.

IP 타입은 VPC CNI 환경에서 파드 IP로 직접 트래픽이 전달되어 오버레이 네트워크 통신보다 더 나은 네트워크 성능을 제공하며, AWS Load Balancer Controller(이전의 ALB Controller)가 필수적으로 설치되어야 한다. 왜냐하면 노드의 Nodeport에 의존하지 않고 파드 IP로 직접 트래픽을 라우팅 하기 위해서는 AWS Load Balancer Controller가 Target Group을 동적으로 관리해야 하기 때문이다.

결론적으로, IP 모드를 사용하면 파드로 IP로 직접 트래픽이 전달되기(bypass) 때문에 성능이 더 좋다.
실습 : NLB - IP 모드
인스턴스 유형은 기존과 동일하기 때문에, IP모드에 대한 실습만 진행한다. 먼저, IP모드 사용을 위해, AWS Load Balancer Controller 배포한다.
# helm으로 배포
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME
# 관련 crd 확인
kubectl get crd | grep elbv2
ingressclassparams.elbv2.k8s.aws 2025-02-15T13:43:28Z
targetgroupbindings.elbv2.k8s.aws 2025-02-15T13:43:28Z
# alb 파드 확인
kubectl get pod -n kube-system | grep aws-load
aws-load-balancer-controller-554fbd9d-7mmxg 1/1 Running
aws-load-balancer-controller-554fbd9d-jdrwk 1/1 Running
echoserver 이미지를 사용하여, 간단하게 응답을 호출하는 디플로이먼트와 서비스를 생성하고, 해당 서비스를 NLB IP 모드로 배포한다.
# 디플로이먼트 & 서비스 생성
cat << EOF > echo-service-nlb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip # IP 모드 지정 (파드 IP로 직접 라우팅)
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing # 외부 접근 가능한 인터넷 연결 NLB 생성
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080" # 헬스체크 포트 지정
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" # 가용영역 간 로드밸런싱 활성화
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb # AWS NLB 사용 지정
selector:
app: deploy-websrv
EOF
kubectl apply -f echo-service-nlb.yaml
기존에는 LoadBalancer 타입의 서비스 생성 시 별도의 어노테이션 없이 AWS Cloud Controller Manager에 의해 Classic Load Balancer(CLB)가 자동으로 프로비저닝 되었다. 하지만 AWS Load Balancer Controller를 사용하고 적절한 어노테이션을 추가하면 NLB나 ALB를 생성할 수 있으며, 이를 통해 더 다양한 로드밸런싱 기능을 활용할 수 있다.
배포 확인
kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 82m
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 98m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 3h46m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 98m
쿠버네티스 서비스 생성을 통해 AWS Load Balancer Controller가 AWS 리소스를 프로비저닝 한 것을 확인할 수 있다. 서비스에 정의된 어노테이션과 설정에 따라 AWS NLB와 대상 그룹이 자동으로 생성되었으며, IP 모드 설정으로 인해 대상 그룹에는 노드가 아닌 파드의 IP가 직접 등록된 것을 확인할 수 있다.

부하분산 통신 테스트
NLB의 엔드포인트로 반복 요청(100번) 시에 등록한 2개의 대상그룹에 거의 동일한 비율로 부하분산이 잘 동작하는 것을 확인할 수 있다.
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
51 Hostname: deploy-echo-bf9bdb8bc-8xt84
49 Hostname: deploy-echo-bf9bdb8bc-jqkgt
디플로이먼트의 레플리카 수를 변경해 보면, 자동으로 대상그룹이 생성됨을 확인할 수 있다. 부하분산 역시 잘 동작한다.
# 레플리카수 2->3
kubectl scale deployment deploy-echo --replicas=3
deployment.apps/deploy-echo scaled
kubectl get deploy,pod,svc
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-echo 3/3 3 3 104m
NAME READY STATUS RESTARTS AGE IP
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 88m 192.168.3.70
pod/deploy-echo-bf9bdb8bc-gfkbv 1/1 Running 0 6s 192.168.3.193
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 104m 192.168.3.124
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 3h53m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 104m
# 타겟 그룹 확인
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo;
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2a 192.168.1.193 8080
TARGETHEALTH healthy
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2b 192.168.2.124 8080
TARGETHEALTH healthy
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2c 192.168.3.70 8080
TARGETHEALTH healthy
# 부하분산 확인 100번 통신시도
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
39 Hostname: deploy-echo-bf9bdb8bc-gfkbv
37 Hostname: deploy-echo-bf9bdb8bc-jqkgt
24 Hostname: deploy-echo-bf9bdb8bc-8xt84
Application Load Balancer (ALB) - ingress
ALB는 AWS의 7 계층(HTTP/HTTPS) 로드 밸런서이다. 7 계층에서 동작한다는 건 URL 기반 라우팅과 같은 HTTP/HTTPS 트래픽의 내용을 이해하고 처리할 수 있다 것이 NLB와의 차이점이다.

실습 : 게임 애플리케이션을 ALB로 배포
이번에는 간단한 게임 디플로이먼트와 서비스를 배포하고, 이 기존에 봤던 LoadBalancer 타입의 서비스 대신, 이번에는 Ingress 리소스를 활용해서 ALB를 구성한다.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
EOF
배포 리소스 확인
kubectl get ingress,svc,pod -n game-2048
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/ingress-2048 alb * k8s-game2048-ingress2-70d50ce3fd-385503327.ap-northeast-2.elb.amazonaws.com 80 34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/service-2048 NodePort 10.100.231.76 <none> 80:30326/TCP 34s
NAME READY STATUS RESTARTS AGE
pod/deployment-2048-7df5f9886b-d59wl 1/1 Running 0 34s
pod/deployment-2048-7df5f9886b-t97wt 1/1 Running 0 34s
로드밸런서와 대상 그룹이 잘 생성됨을 확인할 수 있다.

이번에는 파드의 개수를 1개 감소하여 타깃그룹들이 잘 반영이 되는지 확인해 보자.
# 터미널2 : 파드 1개로 감소
kubectl scale deployment -n game-2048 deployment-2048 --replicas 1
k get pod,svc
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 104m
pod/deploy-echo-bf9bdb8bc-gfkbv 1/1 Running 0 16m
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 120m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4h9m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 120m
대상 그룹이 잘 반영되는 것을 확인할 수 있다.

이제 alb의 외부주소를 통해 게임에 접속해 보자.
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "Game URL = http://"$1 }'
Game URL = http://k8s-game2048-ingress2-70d50ce3fd-385503327.ap-northeast-2.elb.amazonaws.com

'AWS > EKS' 카테고리의 다른 글
EKS Karpenter 기본 사용(Node AutoScaling) (1) | 2025.03.05 |
---|---|
EKS 기본 스토리지(EBS + EFS CSI Driver, Instance Store) (0) | 2025.02.23 |
EKS 설치 및 클러스터 엔드포인트(EKS Cluster Endpoint) (2) | 2025.02.09 |
EKS IaC(Terraform) - AEWS 8주차 (1) | 2024.04.28 |
EKS CI/CD - AEWS 7주차 (1) | 2024.04.20 |
AWS VPC CNI
AWS VPC CNI(Container Network Interface)는 Amazon EKS 클러스터의 파드 네트워킹을 담당하는 네트워크 플러그인이다.
AWS CNI는 다른 CNI들과는 주요 특징은 다음과 같다.
- 노드와 파드의 네트워크 대역이 같다.
- ENI와 L-IPAM을 함께 활용하여 다른 CNI와 차별화된 네트워크 방식을 제공한다.
- 워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다.

노드와 파드의 네트워크 대역이 같다
노드와 파드의 네트워크 대역이 같다. 이는 VPC의 서브넷 IP를 파드에 직접 할당함으로써, 파드가 마치 VPC 내의 일반적인 EC2 인스턴스처럼 동작할 수 있게 한다. 일반적인 K8S CNI는 오버레이(VXLAN, IP-IP)등으로 통신하기 때문에 오버헤드가 발생해 느릴 수밖에 없다. 하지만 AWS VPC CNI는 동일 대역으로 직접 통신하기 때문에 효율적이다.

L-IPAM
ENI(Elastic Network Interface)와 L-IPAM을 함께 활용하여 다른 CNI와 차별화된 네트워크 방식을 제공한다. ENI는 VPC 내에서 가상 네트워크 랜 카드 역할을 하며, L-IPAM(Local IP Address Management)은 각 노드에서 파드의 IP 할당을 관리한다.

warm pool을 통해 효율적이고 신속하게 파드의 IP를 할당한다. warm pool은 미리 일정 수의 IP 주소를 준비해 두는 메커니즘으로, 새로운 파드가 생성될 때 IP 할당 지연을 최소화한다. 이는 파드의 빠른 시작과 확장을 가능하게 하여 애플리케이션의 응답성을 향상한다.

워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다.
워커 노드의 인스턴스 타입별 파드 생성 개수 제한이 있다. 이는 각 EC2 인스턴스 타입이 지원하는 ENI의 수와 각 ENI당 할당 가능한 IP 주소 수에 따라 결정된다. 예를 들어, t3.small 인스턴스는 4개의 ENI를 지원하고 각 ENI당 6개의 IP를 할당할 수 있어, 최대 할당 가능한 파드 수가 제한된다.
Prefix Delegation 기능을 사용하면 각 ENI에 단일 IP가 아닌 IP 프리픽스(/28: 16개 IP)를 할당하여, 단일 ENI로 더 많은 파드를 지원할 수 있게 한다

기본 통신 흐름 확인하기
CASE 1: 노드 간 파드 통신
tcp dump를 통해 노드 별 파드의 통신 흐름을 확인해 보면, 노드의 IP를 거치지 않고 파드 IP로 직접 통신하는 것을 볼 수 있다. 예를 들어, 192.168.1.100 파드에서 다른 노드에 있는 192.168.2.100 파드로 통신할 때, 기존 오버레이 네트워크 방식은 노드 IP를 거쳐 통신해야 했지만, AWS VPC CNI에서는 파드가 VPC 서브넷의 IP를 직접 할당받아 사용하기 때문에 노드를 거치지 않고 파드 간 직접 통신이 가능하다

CASE 2: 파드에서 외부 통신
파드의 외부 통신 흐름은 iptables의 SNAT(Source Network Address Translation) 규칙을 통해 이루어진다. 파드가 외부와 통신할 때, 원본 파드의 IP 주소는 노드의 기본 네트워크 인터페이스인 eth0(ens5)의 IP 주소로 변환됩니다.

다음과 같이 iptables 룰을 확인해보면 SNAT 설정이 되어있음을 확인할 수 있다. 또한 VPC CNI의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다
[ec2-user@ip-192-168-1-230 ~]$ sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
-A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.1.230 --random-fully
쿠버네티스 클러스터와 노드 포트에서 통신 동작원리 - 쿠버네티스의 기본적인 서비스에 통신 흐름(iptables)에 관련 내용은 전 포스팅을 참고 바랍니다
Kubernetes Service(Cluster IP & NodePort) 동작 원리 딥다이브 - KANS 4주차
1. Cluster IP개요 클러스터 내부 접근 전용: ClusterIP는 클러스터 내부에서만 접근 가능하며, 도메인(DNS) 기반으로도 접근할 수 있습니다. DNAT 기반 통신: 클라이언트가 ClusterIP로 접근 시 해당 노드의
hackjsp.tistory.com
AWS LoadBalancer Controller
AWS LoadBalancer Controller는 쿠버네티스 클러스터에서 AWS Elastic Load Balancer를 관리하는 컨트롤러이다. 이전에는 AWS ALB Ingress Controller로 알려졌으나, NLB 지원이 추가되면서 현재의 이름으로 변경되었다.
이 컨트롤러는 쿠버네티스의 서비스와 인그레스 리소스를 AWS의 로드밸런서 리소스로 자동 변환하는 역할을 한다. 예를 들어, 개발자가 쿠버네티스에서 서비스나 인그레스를 생성하면, 컨트롤러는 자동으로 해당하는 AWS ALB 또는 NLB를 생성하고 구성한다.
Network Load Balancer (NLB) - Service
NLB는 AWS의 4 계층(TCP/UDP) 로드 밸런서이고, NLB는 아래 두 가지 모드가 존재한다.
- 인스턴스 유형
- IP 유형
인스턴스 유형
Instance 타입은 노드의 NodePort로 트래픽이 전달되고 kube-proxy가 파드로 트래픽을 분배하는 기본적인 기능을 제공합니다.

IP 타입은 VPC CNI 환경에서 파드 IP로 직접 트래픽이 전달되어 오버레이 네트워크 통신보다 더 나은 네트워크 성능을 제공하며, AWS Load Balancer Controller(이전의 ALB Controller)가 필수적으로 설치되어야 한다. 왜냐하면 노드의 Nodeport에 의존하지 않고 파드 IP로 직접 트래픽을 라우팅 하기 위해서는 AWS Load Balancer Controller가 Target Group을 동적으로 관리해야 하기 때문이다.

결론적으로, IP 모드를 사용하면 파드로 IP로 직접 트래픽이 전달되기(bypass) 때문에 성능이 더 좋다.
실습 : NLB - IP 모드
인스턴스 유형은 기존과 동일하기 때문에, IP모드에 대한 실습만 진행한다. 먼저, IP모드 사용을 위해, AWS Load Balancer Controller 배포한다.
# helm으로 배포
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME
# 관련 crd 확인
kubectl get crd | grep elbv2
ingressclassparams.elbv2.k8s.aws 2025-02-15T13:43:28Z
targetgroupbindings.elbv2.k8s.aws 2025-02-15T13:43:28Z
# alb 파드 확인
kubectl get pod -n kube-system | grep aws-load
aws-load-balancer-controller-554fbd9d-7mmxg 1/1 Running
aws-load-balancer-controller-554fbd9d-jdrwk 1/1 Running
echoserver 이미지를 사용하여, 간단하게 응답을 호출하는 디플로이먼트와 서비스를 생성하고, 해당 서비스를 NLB IP 모드로 배포한다.
# 디플로이먼트 & 서비스 생성
cat << EOF > echo-service-nlb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip # IP 모드 지정 (파드 IP로 직접 라우팅)
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing # 외부 접근 가능한 인터넷 연결 NLB 생성
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080" # 헬스체크 포트 지정
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" # 가용영역 간 로드밸런싱 활성화
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb # AWS NLB 사용 지정
selector:
app: deploy-websrv
EOF
kubectl apply -f echo-service-nlb.yaml
기존에는 LoadBalancer 타입의 서비스 생성 시 별도의 어노테이션 없이 AWS Cloud Controller Manager에 의해 Classic Load Balancer(CLB)가 자동으로 프로비저닝 되었다. 하지만 AWS Load Balancer Controller를 사용하고 적절한 어노테이션을 추가하면 NLB나 ALB를 생성할 수 있으며, 이를 통해 더 다양한 로드밸런싱 기능을 활용할 수 있다.
배포 확인
kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 82m
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 98m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 3h46m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 98m
쿠버네티스 서비스 생성을 통해 AWS Load Balancer Controller가 AWS 리소스를 프로비저닝 한 것을 확인할 수 있다. 서비스에 정의된 어노테이션과 설정에 따라 AWS NLB와 대상 그룹이 자동으로 생성되었으며, IP 모드 설정으로 인해 대상 그룹에는 노드가 아닌 파드의 IP가 직접 등록된 것을 확인할 수 있다.

부하분산 통신 테스트
NLB의 엔드포인트로 반복 요청(100번) 시에 등록한 2개의 대상그룹에 거의 동일한 비율로 부하분산이 잘 동작하는 것을 확인할 수 있다.
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
51 Hostname: deploy-echo-bf9bdb8bc-8xt84
49 Hostname: deploy-echo-bf9bdb8bc-jqkgt
디플로이먼트의 레플리카 수를 변경해 보면, 자동으로 대상그룹이 생성됨을 확인할 수 있다. 부하분산 역시 잘 동작한다.
# 레플리카수 2->3
kubectl scale deployment deploy-echo --replicas=3
deployment.apps/deploy-echo scaled
kubectl get deploy,pod,svc
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-echo 3/3 3 3 104m
NAME READY STATUS RESTARTS AGE IP
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 88m 192.168.3.70
pod/deploy-echo-bf9bdb8bc-gfkbv 1/1 Running 0 6s 192.168.3.193
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 104m 192.168.3.124
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 3h53m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 104m
# 타겟 그룹 확인
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo;
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2a 192.168.1.193 8080
TARGETHEALTH healthy
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2b 192.168.2.124 8080
TARGETHEALTH healthy
TARGETHEALTHDESCRIPTIONS 8080
TARGET ap-northeast-2c 192.168.3.70 8080
TARGETHEALTH healthy
# 부하분산 확인 100번 통신시도
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
39 Hostname: deploy-echo-bf9bdb8bc-gfkbv
37 Hostname: deploy-echo-bf9bdb8bc-jqkgt
24 Hostname: deploy-echo-bf9bdb8bc-8xt84
Application Load Balancer (ALB) - ingress
ALB는 AWS의 7 계층(HTTP/HTTPS) 로드 밸런서이다. 7 계층에서 동작한다는 건 URL 기반 라우팅과 같은 HTTP/HTTPS 트래픽의 내용을 이해하고 처리할 수 있다 것이 NLB와의 차이점이다.

실습 : 게임 애플리케이션을 ALB로 배포
이번에는 간단한 게임 디플로이먼트와 서비스를 배포하고, 이 기존에 봤던 LoadBalancer 타입의 서비스 대신, 이번에는 Ingress 리소스를 활용해서 ALB를 구성한다.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
EOF
배포 리소스 확인
kubectl get ingress,svc,pod -n game-2048
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/ingress-2048 alb * k8s-game2048-ingress2-70d50ce3fd-385503327.ap-northeast-2.elb.amazonaws.com 80 34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/service-2048 NodePort 10.100.231.76 <none> 80:30326/TCP 34s
NAME READY STATUS RESTARTS AGE
pod/deployment-2048-7df5f9886b-d59wl 1/1 Running 0 34s
pod/deployment-2048-7df5f9886b-t97wt 1/1 Running 0 34s
로드밸런서와 대상 그룹이 잘 생성됨을 확인할 수 있다.

이번에는 파드의 개수를 1개 감소하여 타깃그룹들이 잘 반영이 되는지 확인해 보자.
# 터미널2 : 파드 1개로 감소
kubectl scale deployment -n game-2048 deployment-2048 --replicas 1
k get pod,svc
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-bf9bdb8bc-8xt84 1/1 Running 0 104m
pod/deploy-echo-bf9bdb8bc-gfkbv 1/1 Running 0 16m
pod/deploy-echo-bf9bdb8bc-jqkgt 1/1 Running 0 120m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4h9m
service/svc-nlb-ip-type LoadBalancer 10.100.77.70 k8s-default-svcnlbip-54f780389b-fee0ec94ec3352e6.elb.ap-northeast-2.amazonaws.com 80:30603/TCP 120m
대상 그룹이 잘 반영되는 것을 확인할 수 있다.

이제 alb의 외부주소를 통해 게임에 접속해 보자.
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "Game URL = http://"$1 }'
Game URL = http://k8s-game2048-ingress2-70d50ce3fd-385503327.ap-northeast-2.elb.amazonaws.com

'AWS > EKS' 카테고리의 다른 글
EKS Karpenter 기본 사용(Node AutoScaling) (1) | 2025.03.05 |
---|---|
EKS 기본 스토리지(EBS + EFS CSI Driver, Instance Store) (0) | 2025.02.23 |
EKS 설치 및 클러스터 엔드포인트(EKS Cluster Endpoint) (2) | 2025.02.09 |
EKS IaC(Terraform) - AEWS 8주차 (1) | 2024.04.28 |
EKS CI/CD - AEWS 7주차 (1) | 2024.04.20 |