실습 환경
이번 실습은 Terraform으로 배포한 EKS 클러스터에서 진행했다. 퍼블릭 서브넷 3개에 t3.medium 워커 노드 3대를 배치하고, VPC CNI의 다양한 IP 할당 모드와 로드밸런서 동작을 확인한다.
| 클러스터 | myeks (EKS, Terraform 배포) |
| 리전 | ap-northeast-2 |
| VPC CIDR | 192.168.0.0/16 |
| 서브넷 | 퍼블릭 3개 + 프라이빗 3개 |
| 노드 그룹 | t3.medium × 3대 (ENI 3개, ENI당 IP 6개) |
| CNI | AWS VPC CNI (기본 설치) |
노드 배포 상태는 아래 명령으로 확인할 수 있다:
# 노드 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" \
--filters Name=instance-state-name,Values=running --output table
전체 클러스터 구성은 다음과 같다:

1. AWS VPC CNI
AWS VPC CNI는 EKS 클러스터의 기본 네트워킹 Add-on이다. 클러스터를 프로비저닝하면 자동으로 설치된다.
가장 큰 특징은 파드의 IP가 노드와 동일한 VPC 대역을 사용한다는 것이다. 보통 CNI는 Overlay 네트워크를 별도로 구성하지만, VPC CNI는 파드에 실제 VPC IP를 할당한다. NAT 없이 직접 통신이 가능하고 VPC Flow Logs, Security Group, 라우팅 정책 같은 AWS 네이티브 기능도 파드 수준에서 그대로 쓸 수 있다.
Overlay CNI (Calico, Flannel 등) AWS VPC CNI
| 파드 IP 대역 | 별도 가상 네트워크 (예: 10.244.0.0/16) | VPC 실제 대역 (예: 192.168.x.x) |
| 통신 방식 | VXLAN/IPIP 캡슐화 | VPC 라우팅 직접 사용 |
| NAT | 파드 → 외부 통신 시 SNAT 필요 | NAT 없이 직접 통신 |
| 네트워크 성능 | 캡슐화 오버헤드 존재 | 네이티브 VPC 수준 |
| 관찰성 | 별도 모니터링 필요 | VPC Flow Logs로 파드 트래픽 확인 |
| 보안그룹 | 노드 단위 적용 | 파드 단위 적용 가능 |
VPC CNI에서 파드는 노드와 같은 VPC 대역의 IP를 할당받는다

1.1 구성요소
CNI 바이너리
- 파드에 VPC IP를 실제로 할당하는 역할
- 워커 노드의 /opt/cni/bin/aws-cni에 위치
- 설정 파일은 /etc/cni/net.d/10-aws.conflist에서 확인 가능
IPAMD (IP Address Management Daemon)
- 각 노드에서 ENI를 관리하고, IP 주소의 Warm Pool을 유지
- 기본값 WARM_ENI_TARGET=1로, 여분의 ENI 1개를 항상 대기시켜 파드 생성 시 즉시 IP를 할당
- 파드가 생성될 때 빠르게 IP를 할당할 수 있도록 ENI와 IP 주소를 미리 사전 할당(pre-allocation)해 두는 방식
1.2 IP 할당 모드
VPC CNI는 파드에 IP를 할당하는 모드가 3가지 있고, 모드에 따라 노드당 파드 집적도와 IP 관리 방식이 달라진다.
1.2.1 Secondary IP (기본 설정)
EKS 기본 설정에서 파드는 노드(EC2 인스턴스)에 부착된 ENI(Elastic Network Interface)의 Secondary IP를 하나씩 할당받아 통신한다. 중요한 건 EC2 인스턴스 타입마다 부착 가능한 최대 ENI 수와 ENI당 할당 가능한 Secondary IP 수가 하드웨어 스펙으로 고정되어 있다는 것이다. 인스턴스 타입을 선택하는 순간 그 노드에서 실행 가능한 파드 수의 상한이 결정된다.

파드 최대 개수 공식:
파드 개수 = (최대 ENI 수) × (ENI당 Secondary IP 수 - 1) + 2
- -1: 각 ENI의 Primary IP는 노드 자체의 통신에 사용되므로 파드에 할당할 수 없다
- +2: aws-node와 kube-proxy 같은 호스트 네트워크를 사용하는 시스템 파드 여유분
t3.medium 기준 계산:
항목 값
| 최대 ENI 수 | 3 |
| ENI당 IP 수 | 6 |
| 최대 파드 수 | 3 × (6 - 1) + 2 = 17 |
아래는 t3.medium 노드에서 3개 ENI에 Secondary IP가 할당되는 구조다. 각 ENI의 Primary IP 1개는 노드가 사용하고, 나머지 5개가 파드에 할당된다:
ENI 1 (Primary IP: 노드 사용)
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
└── Secondary IP → Pod
ENI 2
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
└── Secondary IP → Pod
ENI 3
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
├── Secondary IP → Pod
└── Secondary IP → Pod
Secondary IP의 최대 개수는 전적으로 EC2 인스턴스의 스펙(타입)에 달려있다. 인스턴스가 클수록 더 많은 ENI를 붙일 수 있고, ENI당 더 많은 Secondary IP를 가질 수 있다. 인스턴스 타입별 네트워크 스펙은 아래 명령으로 확인할 수 있다:
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
+----------+----------+--------------+
| IPv4addr | MaxENI | Type |
+----------+----------+--------------+
| 15 | 4 | t3.2xlarge |
| 6 | 3 | t3.medium |
| 12 | 3 | t3.large |
| 15 | 4 | t3.xlarge |
| 2 | 2 | t3.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
이 한계를 직접 확인해보자. nginx deployment의 replicas를 50개로 늘리면, 17개를 초과하는 파드는 IP를 할당받지 못하고 Pending 상태에 빠진다:
kubectl scale deployment nginx-deployment --replicas=50


결국 Secondary IP 방식에서는 인스턴스 스펙이 파드 수의 물리적 한계가 된다. 이 제약을 풀려면 CNI 설정 튜닝이나 Prefix Delegation을 적용해야 한다.
1.2.2 VPC CNI 설정 튜닝
기본 설정인 WARM_ENI_TARGET=1은 IP가 1개라도 필요하면 ENI 전체를 새로 생성한다. 이는 IP를 많이 사용하지 않는 노드에서 불필요한 ENI가 할당되는 원인이 된다. IP 단위로 더 세밀하게 제어하려면 다음 환경변수를 사용한다:
환경변수 설명
| WARM_IP_TARGET | 항상 확보해둘 여유 IP 수 |
| MINIMUM_IP_TARGET | 노드에서 최소 유지할 IP 수 |
예를 들어 MINIMUM_IP_TARGET=10으로 설정하면, 최초에 10개의 IP를 미리 할당하므로 ENI도 2개가 생성된다. 파드 생성이 빈번한 워크로드에서 IP 할당 지연을 줄일 수 있다.
설정은 aws-node DaemonSet의 환경변수로 적용한다:
kubectl set env daemonset aws-node -n kube-system WARM_IP_TARGET=5 MINIMUM_IP_TARGET=10
1.2.3 Prefix Delegation (접두사 위임)
Secondary IP에서는 ENI 슬롯 1개에 IP 1개만 들어간다. Prefix Delegation은 이 슬롯 하나에 /28 크기의 IP 블록(16개)을 통째로 넣는 방식이다. 슬롯당 16배의 IP를 확보하니 노드당 파드 집적도가 크게 올라간다.
사용 조건:
- AWS Nitro 기반 인스턴스 (m5 이상) — Prefix Delegation은 Nitro 시스템의 ENI에서만 지원하는 기능
- ENABLE_PREFIX_DELEGATION=true 설정
- kubelet --max-pods 값 조정 필요 (인스턴스 타입별로 할당 가능한 접두사 수가 다름)
Prefix Delegation을 활성화하면 인스턴스 타입의 ENI/IP 스펙에 구애받지 않고 권장 한도 110개까지 파드를 배치할 수 있다.
Prefix 할당 상태는 다음 명령으로 확인한다:
aws ec2 describe-instances \
--filters "Name=tag-key,Values=eks:cluster-name" "Name=tag-value,Values=myeks" \
--query 'Reservations[*].Instances[].{InstanceId: InstanceId, Prefixes: NetworkInterfaces[].Ipv4Prefixes[]}' | jq
[
{ "InstanceId": "i-06bb745636f1a2cd2", "Prefixes": [{ "Ipv4Prefix": "192.168.5.160/28" }] },
{ "InstanceId": "i-0cabf55bffcdc3004", "Prefixes": [{ "Ipv4Prefix": "192.168.3.176/28" }] },
{ "InstanceId": "i-09ee8a731b20eff8f", "Prefixes": [{ "Ipv4Prefix": "192.168.9.32/28" }] }
]
각 노드에 /28 블록이 할당된 것을 확인할 수 있다. /28은 16개의 IP를 포함하므로, 기존 Secondary IP 방식에서 슬롯당 1개였던 것이 16개로 늘어난 것이다.
참고: Node가 아닌 Pod가 IMDS로 메타데이터(VPC ID 등)를 가져올 수 있도록 IMDS hop limit을 2로 설정해야 한다. EKS 모듈로 배포 시 관리형 노드 그룹의 시작 템플릿에 http_put_response_hop_limit = 2를 적용한다.

1.2.4 커스텀 네트워킹
Prefix Delegation이 노드당 파드 집적도를 높이는 기능이라면, 커스텀 네트워킹은 파드가 IP를 할당받는 서브넷 자체를 노드와 분리하는 기능이다.
문제 상황: K8s 환경에서 파드가 수시로 생성/삭제되면서 대량의 IP를 소모한다. 노드와 파드가 같은 VPC 대역을 사용하면, 새로운 노드나 RDS 같은 AWS 리소스를 띄울 IP가 부족해지는 VPC IP 고갈이 발생할 수 있다.
해결: VPC에 파드 전용 Secondary CIDR을 추가한다. 주로 내부 통신 전용인 100.64.0.0/10(CGNAT) 대역을 사용하여 기존 VPC 대역과의 충돌을 회피한다.
┌─── VPC ──────────────────────────────────────────────┐
│ │
│ ┌─ Primary CIDR: 192.168.0.0/16 ─────────────────┐ │
│ │ │ │
│ │ [Node] [RDS] [ALB] │ │
│ │ │ │ │
│ │ ├── ENI (Primary) → 192.168.x.x │ │
│ │ │ │ │
│ └─────┼────────────────────────────────────────────┘ │
│ │ │
│ └── ENI (Secondary) │
│ │ │
│ ┌─ Secondary CIDR: 100.64.0.0/16 (파드 전용) ─────┐ │
│ │ ↓ │ │
│ │ [Pod] [Pod] [Pod] ... → 100.64.x.x │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
이렇게 분리하면 파드가 아무리 많은 IP를 소모해도 노드, RDS, ALB 등 다른 AWS 리소스의 IP 대역에는 영향을 주지 않는다.
2. Service & 로드밸런싱
VPC CNI로 파드에 IP가 할당되었다면, 다음 단계는 이 파드들을 외부에 노출하는 것이다. 쿠버네티스는 Service 리소스를 통해 파드 그룹에 안정적인 네트워크 엔드포인트를 제공한다.
2.1 쿠버네티스 서비스 타입
- ClusterIP: 클러스터 내부 통신 전용
- NodePort: 노드의 정적 포트로 외부 노출
- LoadBalancer: 클라우드 프로바이더의 로드밸런서와 연동
2.2 AWS Load Balancer Controller
EKS에서 외부 트래픽을 파드로 연결하는 컨트롤러다. Service나 Ingress 리소스에 어노테이션을 달아두면 컨트롤러가 이를 감지해서 목적에 맞는 AWS 로드밸런서를 자동으로 만들고 파드와 연결해준다.
리소스 생성되는 LB 용도
| Service (type: LoadBalancer) | NLB | TCP/UDP L4 트래픽 |
| Ingress | ALB | HTTP/HTTPS L7 트래픽 |
기존의 Cloud Controller Manager(CCM)도 CLB/NLB를 프로비저닝할 수 있지만, 현재는 거의 사용되지 않는다.
2.2.1 설치 및 구성
AWS LB Controller가 AWS 리소스를 생성하려면 적절한 IAM 권한이 필요하다. 이번 실습에서는 IRSA(IAM Roles for Service Accounts)를 사용했다. IRSA는 ServiceAccount에 IAM 역할을 바인딩하여 해당 파드에만 최소 권한을 부여하는 방식으로, Pod Identity나 Instance Profile(취약) 대비 가장 보편적으로 사용된다.
설치 과정의 핵심 포인트:
- OIDC Provider 확인 — EKS 클러스터에 OIDC가 연결되어 있어야 IRSA가 동작
- 서브넷 태그 확인 — LB Controller는 kubernetes.io/role/elb: 1 태그가 붙은 서브넷을 자동으로 탐색하여 LB를 배치한다. 이 태그가 없으면 컨트롤러가 대상 서브넷을 찾지 못해 LB 생성에 실패한다
- IRSA 생성 — LB Controller용 ServiceAccount에 IAM 역할 바인딩
- Helm으로 설치 — eks/aws-load-balancer-controller 차트 배포
# 서브넷 태그 확인 예시
+-------------------------------------------------------+------------------+
| Key | Value |
+-------------------------------------------------------+------------------+
| kubernetes.io/role/elb | 1 |
+-------------------------------------------------------+------------------+
# Helm으로 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=$CLUSTER_NAME \
--set serviceAccount.name=aws-load-balancer-controller \
--set serviceAccount.create=false
2.3 NLB (Network Load Balancer)
NLB 생성 시 타겟 유형을 지정해야 하며, 두 가지 모드가 있다:
인스턴스 모드:
Client → NLB → Node(EC2) → kube-proxy → [다른 노드로 포워딩] → Pod
↑ ↑
1차 홉 2차 홉 (파드가 다른 노드에 있을 때)
트래픽이 노드를 경유하면서 불필요한 네트워크 홉이 발생하고, 목적지 파드가 다른 노드에 있으면 추가 점프가 필요하다.
IP 모드:
Client → NLB → Pod (직접 연결)
VPC CNI 덕에 파드가 실제 VPC IP를 갖고 있으니 NLB가 파드 IP로 바로 트래픽을 전달할 수 있다. 경유지가 없어지면서 지연 시간도 줄어든다.
실무에서는 대부분 IP 모드를 기본으로 사용한다. 단, IP 모드는 반드시 AWS Load Balancer Controller와 IAM 정책 설정이 필요하다. LB Controller 없이 CCM(Cloud Controller Manager)만으로 운영하는 경우에는 인스턴스 모드를 사용한다.


2.4 ALB와 Ingress (L7)
Ingress는 클러스터 내부 서비스를 외부로 노출하는 L7 리소스다. AWS LB Controller가 Ingress를 감지하면 ALB를 생성한다.
Ingress가 제공하는 기능:
- 호스트 기반 라우팅: 도메인별로 다른 서비스로 라우팅 (api.example.com → api-service)
- 경로 기반 라우팅: URL 경로별로 다른 서비스로 라우팅 (/api → api-service, /web → web-service)
- HTTPS 처리: 어노테이션으로 AWS ACM 인증서를 등록하고, ALB에서 SSL Termination 수행. 외부는 HTTPS, 내부 파드 통신은 HTTP로 처리하여 부하를 줄인다.
2.4.1 IngressGroup으로 비용 절감
기본적으로 Ingress 리소스 1개당 ALB 1개가 생성된다. 서비스가 10개면 ALB도 10개 — 비용이 급증한다.
alb.ingress.kubernetes.io/group.name 어노테이션으로 여러 Ingress를 하나의 ALB로 병합할 수 있다:
# Ingress 1: API 서비스
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
alb.ingress.kubernetes.io/group.name: my-team
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
---
# Ingress 2: Web 서비스 (같은 group.name)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
alb.ingress.kubernetes.io/group.name: my-team
spec:
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
두 Ingress가 같은 group.name: my-team을 사용하므로, ALB는 1개만 생성된다. 같은 포트(80)의 리스너 안에 도메인별 리스너 규칙이 추가되어 api.example.com은 api-service로, web.example.com은 web-service로 트래픽을 분배한다.
3. ExternalDNS
ExternalDNS는 Service, Ingress, Gateway API 리소스에 도메인을 설정하면 해당 DNS 레코드를 클라우드 DNS 서비스(AWS Route53, Azure DNS, GCP Cloud DNS 등)에 자동으로 생성/삭제해주는 컨트롤러다.
NLB를 만들고 tetris.hackjap.link 도메인을 연결하고 싶다면 Route53 콘솔에서 A 레코드를 직접 만들 필요 없이 어노테이션 한 줄이면 된다.
3.1 설치 및 구성
ExternalDNS가 Route53 레코드를 관리하려면 IAM 권한이 필요하다. LB Controller와 마찬가지로 IRSA로 설정한다.
- IAM 정책 생성 — route53:ChangeResourceRecordSets, route53:ListHostedZones 등 Route53 관련 권한
- IRSA 생성 — external-dns ServiceAccount에 IAM 역할 바인딩
- Helm으로 설치 — 주요 설정값:
provider: aws
serviceAccount:
create: false
name: external-dns
# 관리 대상 도메인 필터 (보안상 권장)
domainFilters:
- hackjap.link
# 레코드 동기화 정책
# sync: K8s에서 삭제 시 Route53에서도 자동 삭제
# upsert-only: 생성/수정만 하고 삭제는 수동 (안전)
policy: sync
# 감시 대상 리소스
sources:
- service
- ingress
# 여러 클러스터가 동일 도메인 관리 시 충돌 방지용 식별자
txtOwnerId: "myeks-cluster"
# Helm으로 설치
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm install external-dns external-dns/external-dns \
-n kube-system \
-f external-dns-values.yaml
3.2 NLB + ExternalDNS 연동 실습
테트리스 게임을 배포하고, NLB에 도메인을 연결하는 실습이다.
# 테트리스 Deployment + NLB Service 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
spec:
replicas: 1
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
name: tetris
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-cross-zone-load-balancing-enabled: "true"
spec:
selector:
app: tetris
ports:
- port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
NLB가 생성된 후, 어노테이션으로 도메인을 연결한다:
# ExternalDNS로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
# Route53에 A 레코드가 자동 생성되었는지 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" \
--query "ResourceRecordSets[?Type == 'A']" | jq
# DNS 전파 확인
dig +short tetris.$MyDomain @8.8.8.8
ExternalDNS 로그에서 레코드 생성 과정을 실시간으로 확인할 수 있다:
kubectl logs deploy/external-dns -n kube-system -f
리소스를 삭제하면(kubectl delete deploy,svc tetris), policy: sync 설정에 의해 Route53의 A 레코드도 자동으로 삭제된다.
4. Gateway API
Gateway API는 쿠버네티스의 차세대 트래픽 라우팅 표준이다. Ingress의 한계(어노테이션 의존, 프로바이더마다 다른 비표준 설정)를 해결하려고 만들어졌고, nginx ingress controller 지원 종료와 맞물려 전환이 빨라지고 있다.
Ingress가 하나의 리소스에 모든 설정을 몰아넣었다면 Gateway API는 역할별로 리소스를 분리해서 관심사를 나눈다.
4.1 리소스 구조
Gateway API는 여러 CRD로 구성되며, 각 리소스가 계층적으로 연결된다:
LoadBalancerConfiguration ← LB 세부 설정 (scheme: internet-facing 등)
↑
GatewayClass ← 구현체 정의 (ALB 사용)
↑
Gateway ← 진입점 정의 (Listener: 포트, 프로토콜)
↑
HTTPRoute ← 라우팅 규칙 (호스트, 경로 → 서비스 매핑)
TargetGroupConfiguration ← 타겟 그룹 설정 (targetType: ip)
리소스 역할 주요 설정
| LoadBalancerConfiguration | ALB/NLB 프로비저닝 설정 | scheme: internet-facing (기본값은 internal) |
| GatewayClass | 어떤 구현체(provider)를 사용할지 정의 | LoadBalancerConfiguration 참조 |
| Gateway | 실제 진입점 (LB 생성) | GatewayClass 참조, Listener 정의 |
| HTTPRoute | L7 라우팅 규칙 | 호스트/경로 기반 → 백엔드 서비스 매핑 |
| TargetGroupConfiguration | AWS 타겟 그룹 세부 설정 | targetType: ip |
4.2 설치 및 구성
Gateway API 사용을 위해서는 CRD 설치, LB Controller의 feature flag 활성화, ExternalDNS의 source 추가 3단계가 필요하다.
1. CRD 설치
표준 Gateway API CRD와 AWS LB Controller 전용 CRD를 각각 설치한다:
# Gateway API 표준 CRD (필수)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
# Gateway API Experimental CRD (L4 Route 사용 시 필요)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/experimental-install.yaml
# AWS LB Controller 전용 CRD (LoadBalancerConfiguration, TargetGroupConfiguration 등)
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/refs/heads/main/config/crd/gateway/gateway-crds.yaml
# 설치 확인
kubectl get crd | grep gateway.networking # 표준 CRD
kubectl get crd | grep gateway.k8s.aws # AWS 전용 CRD
2. LB Controller에 Gateway API feature flag 활성화
기본적으로 LB Controller는 Gateway API CRD를 감시하지 않는다. Deployment에 feature flag를 추가해야 한다:
# LBC v2.13.0 이상 필요
kubectl describe pod -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller | grep Image: | uniq
# feature flag 활성화 (args에 추가)
KUBE_EDITOR="nano" kubectl edit deploy -n kube-system aws-load-balancer-controller
# deployment args에 아래 줄 추가
- --feature-gates=NLBGatewayAPI=true,ALBGatewayAPI=true
3. ExternalDNS에 Gateway API source 추가
ExternalDNS가 Gateway API 리소스의 도메인도 감지하도록 source를 추가한다:
# external-dns-values.yaml에 sources 추가
sources:
- service
- ingress
- gateway-httproute
- gateway-grpcroute
- gateway-tlsroute
- gateway-tcproute
- gateway-udproute
helm upgrade -i external-dns external-dns/external-dns -n kube-system -f external-dns-values.yaml
4.3 실습: 2048 게임 배포
Gateway API의 전체 리소스 생성 흐름을 2048 게임 배포로 확인한다.
LoadBalancerConfiguration → GatewayClass → Gateway 생성:
# 1. LoadBalancerConfiguration: 외부 접근 가능하도록 internet-facing 설정
apiVersion: gateway.k8s.aws/v1beta1
kind: LoadBalancerConfiguration
metadata:
name: lbc-config
spec:
scheme: internet-facing
---
# 2. GatewayClass: ALB 구현체 지정 + LoadBalancerConfiguration 참조
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: aws-alb
spec:
controllerName: gateway.k8s.aws/alb
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: lbc-config
namespace: default
---
# 3. Gateway: HTTP 80 포트 리스너 생성 (여기서 ALB가 실제 생성됨)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: alb-http
spec:
gatewayClassName: aws-alb
listeners:
- name: http
protocol: HTTP
port: 80
# Gateway 생성 확인 — ALB DNS가 ADDRESS에 표시된다
kubectl get gateways
# NAME CLASS ADDRESS PROGRAMMED AGE
# alb-http aws-alb k8s-default-albhttp-xxx.ap-northeast-2.elb... Unknown 24s
애플리케이션 + TargetGroupConfiguration + HTTPRoute 생성:
# 4. 2048 게임 Deployment + ClusterIP Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-2048
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: app-2048
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: service-2048
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: app-2048
ports:
- port: 80
targetPort: 80
---
# 5. TargetGroupConfiguration: IP 모드로 타겟 그룹 설정
apiVersion: gateway.k8s.aws/v1beta1
kind: TargetGroupConfiguration
metadata:
name: backend-tg-config
spec:
targetReference:
name: service-2048
defaultConfiguration:
targetType: ip
protocol: HTTP
---
# 6. HTTPRoute: 도메인 → 서비스 라우팅
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: alb-http-route
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: alb-http
sectionName: http
hostnames:
- gwapi.hackjap.link
rules:
- backendRefs:
- name: service-2048
port: 80
# 접속 확인
dig +short gwapi.hackjap.link @8.8.8.8
echo "http://gwapi.hackjap.link"
# 삭제
kubectl delete httproute,targetgroupconfigurations,Gateway,GatewayClass --all
Ingress에서 어노테이션으로 흩어져 있던 설정이 Gateway API에서는 리소스 단위로 분리된다. 처음엔 장황해 보이지만 역할이 나뉘어 있어 팀 간 책임 경계가 분명해지고, 프로바이더를 바꿔도 구조를 재사용하기 쉽다.
5. CoreDNS
CoreDNS는 클러스터 내부 DNS 서버다. 파드가 서비스 이름(my-service.default.svc.cluster.local)으로 다른 파드에 접근할 수 있게 해주며, EKS에서는 관리형 Add-on으로 클러스터 프로비저닝 시 자동 설치된다.
5.1 Corefile 구조
CoreDNS의 동작은 ConfigMap에 정의된 Corefile로 제어된다:
kubectl get cm -n kube-system coredns -o yaml
.:53 {
errors # 에러 로깅
health {
lameduck 5s # 종료 시 5초간 지연 (DNS 실패 최소화)
}
ready # readiness 엔드포인트 (/ready:8181)
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure # 파드 DNS 레코드 생성
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153 # 메트릭 엔드포인트
forward . /etc/resolv.conf # 클러스터 외부 도메인은 업스트림 DNS로 포워딩
cache 30 # DNS 응답 30초 캐시
loop # 무한 루프 감지
reload # Corefile 변경 시 자동 리로드
loadbalance # 응답의 A 레코드 순서 랜덤화
}
5.2 주요 설정
lameduck
CoreDNS 파드가 재시작되거나 노드가 종료될 때 트래픽 수신을 바로 끊으면 DNS 확인이 실패할 수 있다. lameduck 5s는 종료 신호를 받아도 5초간 정상 응답을 유지해서 이 문제를 줄여준다.
cache
기본 설정은 cache 30으로, 모든 DNS 응답을 30초간 캐시한다. 대규모 클러스터에서는 캐시를 세밀하게 튜닝할 수 있다:
cache 30 {
success 10000 30 # positive 캐시: 최대 10,000건, 30초 TTL
denial 2000 10 # negative 캐시: 최대 2,000건, 10초 TTL
prefetch 5 60s # 동일 질의 5회 이상이면 만료 60초 전에 미리 갱신
}
forward
cluster.local에 매칭되지 않는 외부 도메인 질의는 /etc/resolv.conf에 설정된 업스트림 DNS(VPC DNS)로 포워딩한다. 대규모 환경에서는 동시 요청 수를 늘릴 수 있다:
forward . /etc/resolv.conf {
max_concurrent 2000
prefer_udp
}
5.3 EKS 관리형 Add-on 특성
EKS의 CoreDNS는 관리형 Add-on이라 일반 쿠버네티스와 좀 다른 부분이 있다:
PDB (Pod Disruption Budget) — CoreDNS에 기본으로 PDB가 설정되어 있어, 노드 종료 시에도 CoreDNS 파드가 동시에 모두 내려가는 것을 방지한다:
kubectl get pdb -n kube-system coredns
# NAME MAX UNAVAILABLE ALLOWED DISRUPTIONS
# coredns 1 1
topologySpreadConstraints — EKS Add-on 구성 스키마로 CoreDNS 파드를 AZ별로 분산 배치할 수 있다. 특정 AZ에 CoreDNS가 몰리면 해당 AZ 장애 시 DNS가 마비되므로, 프로덕션에서는 설정을 권장한다:
# add-on 설정으로 topologySpreadConstraints 적용
aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name coredns \
--configuration-values '{"topologySpreadConstraints":[{"maxSkew":1,"topologyKey":"topology.kubernetes.io/zone","whenUnsatisfiable":"ScheduleAnyway","labelSelector":{"matchLabels":{"k8s-app":"kube-dns"}}}]}'
readinessProbe — /health 대신 /ready 엔드포인트를 사용하여, Corefile의 ready 플러그인과 연동된 정확한 준비 상태를 확인한다.
5.4 DNS 쿼리 흐름
파드에서 외부 도메인을 질의할 때의 흐름:
Pod → CoreDNS (cluster.local?)
├── Yes → 클러스터 내부 서비스 IP 반환
└── No → forward → VPC DNS (AmazonProvidedDNS) → 외부 DNS
파드의 /etc/resolv.conf는 CoreDNS Service의 ClusterIP를 nameserver로 가리키고 있으며, ndots:5 설정에 의해 짧은 이름은 cluster.local 등의 search domain이 자동으로 붙는다.
마무리
2주차에 다룬 내용을 정리하면:
VPC CNI → Service → Gateway API + AWS LB Controller → ExternalDNS → CoreDNS
VPC CNI가 파드에 실제 VPC IP를 부여하고, 그 위에 Service로 엔드포인트를 잡고, Gateway API와 LB Controller가 함께 ALB/NLB를 만들어 라우팅을 처리하고, ExternalDNS가 도메인을 붙이는 흐름이다. CoreDNS는 이 모든 과정에서 클러스터 내부 DNS 해석을 담당한다.
파드 IP부터 로드밸런서, DNS까지 — 쿠버네티스 리소스를 선언하면 대응하는 AWS 리소스가 바로 프로비저닝된다. EKS 네트워킹이 쿠버네티스 오브젝트와 AWS 인프라를 이 정도로 긴밀하게 연동해 놓았다는 점에서, 두 레이어 사이의 경계가 거의 느껴지지 않을 만큼 잘 설계되었다고 느꼈다.
'AWS > EKS' 카테고리의 다른 글
| [AEWS4] EKS가 만들어주는 AWS 리소스 들여다보기 (0) | 2026.03.19 |
|---|---|
| Amazon VPC Lattice로 간소화하는 Amazon EKS 클러스터 통신 (0) | 2025.04.27 |
| ML Infra with EKS : AI 워크로드에 대한 컨테이너 사용 (1) | 2025.04.20 |
| AWS EKS 업그레이드, In-place부터 Blue/Green 마이그레이션까지 (1) | 2025.04.02 |
| EKS 서버리스 서비스 간략히 알아보기(Fargate & EKS AutoMode) (0) | 2025.03.23 |