[CloudNeta] EKS 워크샵 스터디 (2) - EKS Network Part 3 - Ingress, ExternalDNS, Gateway API
이번 게시글에서는 EKS 워크샵 스터디 제 2주차 내용을 작성합니다.
이 글은 3부로 나누어집니다.
- Ingress (L7: HTTP) 를 통한 외부 노출
- ExternalDNS를 활용한 Route53 자동 관리
- Gateway API로 차세대 트래픽 라우팅 구성
이전 글에 이어서, L7 레이어에서의 트래픽 라우팅과 DNS 자동화, 그리고 Gateway API를 실습합니다.
Ingress (L7: HTTP)
쿠버네티스의 Ingress는 클러스터 내부의 서비스(ClusterIP, NodePort, LoadBalancer)를 외부로 노출하기 위한 웹 프록시 역할입니다. AWS에서는 이를 Load Balancer Controller + Ingress (ALB) IP 모드로 동작하게 하며, AWS VPC CNI와 함께 작업합니다.
AWS Load Balancer Controller 설치
Ingress 리소스를 ALB로 프로비저닝하려면 AWS LBC가 클러스터에 설치되어 있어야 합니다. IRSA의 개념은 1주차에서 다뤘으니, 여기서는 설치 과정만 진행합니다.
IAM Policy 및 IRSA 생성
먼저 LBC가 AWS API를 호출할 수 있도록 IAM Policy를 생성하고, IRSA로 Service Account에 연결합니다.
# IAM Policy json 파일 다운로드
$ curl -o aws_lb_controller_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/refs/heads/main/docs/install/iam_policy.json
# IAM Policy 생성
$ aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://aws_lb_controller_policy.json
{
"Policy": {
"PolicyName": "AWSLoadBalancerControllerIAMPolicy",
"Arn": "arn:aws:iam::<ACCOUNT_ID>:policy/AWSLoadBalancerControllerIAMPolicy",
...
}
}
# 생성 확인
$ ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
$ aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy | jq '.Policy.PolicyName'
"AWSLoadBalancerControllerIAMPolicy"
# IRSA 생성 — eksctl이 CloudFormation으로 IAM Role을 만들고 Service Account에 연결
$ CLUSTER_NAME=myeks
$ eksctl create iamserviceaccount \
--cluster=$CLUSTER_NAME \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--approve
2026-03-26 05:37:32 [ℹ] 1 iamserviceaccount (kube-system/aws-load-balancer-controller) was included (based on the include/exclude rules)
2026-03-26 05:38:04 [ℹ] created serviceaccount "kube-system/aws-load-balancer-controller"
# Service Account에 IAM Role ARN이 붙었는지 확인
$ kubectl get sa -n kube-system aws-load-balancer-controller -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-...
name: aws-load-balancer-controller
namespace: kube-system
Helm Chart 설치
# Helm Chart Repository 추가
$ helm repo add eks https://aws.github.io/eks-charts
$ helm repo update
# 현재 설치된 차트 확인 — 아직 없음
$ helm list -n kube-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
# Helm Chart - AWS Load Balancer Controller 설치
# https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller
# https://github.com/aws/eks-charts/blob/master/stable/aws-load-balancer-controller/values.yaml
$ helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --version 3.1.0 \
--set clusterName=$CLUSTER_NAME \
--set serviceAccount.name=aws-load-balancer-controller \
--set serviceAccount.create=false
NAME: aws-load-balancer-controller
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
NOTES:
AWS Load Balancer controller installed!
# 설치 확인
$ helm list -n kube-system
NAME NAMESPACE REVISION STATUS CHART APP VERSION
aws-load-balancer-controller kube-system 1 deployed aws-load-balancer-controller-3.1.0 v3.1.0
설치 후 파드 상태와 로그를 확인합니다:
$ kubectl get pod -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
NAME READY STATUS RESTARTS AGE
aws-load-balancer-controller-7875649799-ftgts 0/1 Running 0 11s
aws-load-balancer-controller-7875649799-sbz7b 0/1 Running 0 11s
$ kubectl logs -n kube-system deployment/aws-load-balancer-controller
{"level":"info","ts":"2026-03-25T20:40:48Z","msg":"version","GitVersion":"v3.1.0","GitCommit":"250024d...","BuildDate":"2026-02-24T18:21:40+0000"}
{"level":"error","ts":"2026-03-25T20:40:53Z","logger":"setup","msg":"unable to initialize AWS cloud",
"error":"failed to get VPC ID: failed to fetch VPC ID from instance metadata: ...context deadline exceeded"}
unable to initialize AWS cloud 에러LBC 파드가 EC2 인스턴스 메타데이터(IMDS)에서 VPC ID를 가져오지 못하고 있습니다. LBC는 기동 시 Region과 VPC ID를 알아야 하는데, 이를 IMDS에서 자동 감지하려다 실패한 것입니다. 해결 방법은 두 가지입니다.
에러 해결 — Region/VPC ID 제공
AWS LBC는 기동 시 Region과 VPC ID 정보가 필요합니다. 기본적으로 EC2 인스턴스 메타데이터(IMDS)에서 자동으로 가져오지만, IMDS 접근이 제한되면 위와 같은 에러가 발생합니다.
방안 1: Helm 설치 시 명시적으로 지정
가장 간단한 방법입니다. region과 vpcId를 Helm 파라미터로 직접 넘깁니다.
# VPC ID 확인 (Terraform 사용 시)
$ terraform show -json | jq -r '.values.root_module.child_modules[] |
select(.address == "module.vpc") | .resources[] |
select(.address == "module.vpc.aws_vpc.this[0]") | .values.id'
vpc-0db6bf1bbadee777d
# 기존 설치 삭제 후 재설치
$ helm uninstall aws-load-balancer-controller -n kube-system
$ helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --version 3.1.0 \
--set clusterName=$CLUSTER_NAME \
--set serviceAccount.name=aws-load-balancer-controller \
--set serviceAccount.create=false \
--set region=ap-northeast-2 \
--set vpcId=vpc-0db6bf1bbadee777d
방안 2: IMDS Hop Limit 변경
LBC 파드가 IMDS에 접근하지 못하는 근본 원인은 IMDS의 hop limit입니다. EC2 인스턴스 메타데이터 v2(IMDSv2)는 기본 hop limit이 1인데, 파드에서 IMDS까지는 컨테이너 네트워크를 거쳐 2홉이 필요합니다. hop limit을 2로 올리면 파드에서도 IMDS에 접근할 수 있습니다.
# 워커 노드의 IMDS hop limit 확인
$ aws ec2 describe-instances \
--filters "Name=tag:eks:cluster-name,Values=$CLUSTER_NAME" \
--query "Reservations[].Instances[].{Id:InstanceId, HopLimit:MetadataOptions.HttpPutResponseHopLimit}" \
--output table
# hop limit을 1 → 2로 변경
$ for instance_id in $(aws ec2 describe-instances \
--filters "Name=tag:eks:cluster-name,Values=$CLUSTER_NAME" \
--query "Reservations[].Instances[].InstanceId" --output text); do
aws ec2 modify-instance-metadata-options \
--instance-id $instance_id \
--http-put-response-hop-limit 2
done
방안 1이 간단하고 확실합니다. 방안 2는 IMDS hop limit을 영구적으로 변경해야 하고, 노드가 교체될 때마다 다시 설정해야 할 수 있습니다(Launch Template에 반영하지 않은 경우). 다만 LBC 외에도 IMDS를 사용하는 파드가 있다면 방안 2가 근본적인 해결입니다.
이번 실습에서는 방안 1을 적용했습니다. helm upgrade --install로 region과 vpcId를 추가한 뒤 파드가 정상 기동되는지 확인합니다:
$ helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --version 3.1.0 \
--set clusterName=myeks \
--set serviceAccount.name=aws-load-balancer-controller \
--set serviceAccount.create=false \
--set region=ap-northeast-2 \
--set vpcId=vpc-00ac8ce92e70442fe
Release "aws-load-balancer-controller" has been upgraded. Happy Helming!
NAME: aws-load-balancer-controller
STATUS: deployed
REVISION: 2
# 새 파드 로그 확인 — 에러 없이 controller가 시작되면 정상
$ kubectl logs -n kube-system deployment/aws-load-balancer-controller --since=1m
{"level":"info","ts":"...","msg":"version","GitVersion":"v3.1.0",...}
{"level":"info","ts":"...","logger":"setup","msg":"adding health check for controller"}
{"level":"info","ts":"...","logger":"setup","msg":"adding readiness check for webhook"}
...
{"level":"info","ts":"...","msg":"successfully acquired lease kube-system/aws-load-balancer-controller-leader"}
{"level":"info","ts":"...","msg":"Starting Controller","controller":"ingress"}
{"level":"info","ts":"...","msg":"Starting workers","controller":"ingress","worker count":3}
{"level":"info","ts":"...","msg":"Starting Controller","controller":"service"}
{"level":"info","ts":"...","msg":"Starting workers","controller":"service","worker count":3}
{"level":"info","ts":"...","msg":"Starting Controller","controller":"targetGroupBinding",...}
{"level":"info","ts":"...","msg":"Starting workers","controller":"targetGroupBinding",...,"worker count":3}
Starting workers가 ingress, service, targetGroupBinding 세 컨트롤러 모두에서 출력되면 LBC가 정상 동작하는 것입니다.
서비스/파드 배포 테스트
게임 2048 앱을 Deployment + Service + Ingress로 한번에 배포하여 ALB가 정상적으로 프로비저닝되는지 확인합니다.
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
Ingress의 핵심 설정을 짚어보면:
| 설정 | 값 | 의미 |
|---|---|---|
ingressClassName |
alb |
AWS LBC가 이 Ingress를 처리하도록 지정 |
alb.ingress.kubernetes.io/scheme |
internet-facing |
퍼블릭 서브넷에 외부 접근 가능한 ALB 생성 |
alb.ingress.kubernetes.io/target-type |
ip |
Target Group에 파드 IP를 직접 등록 (Part 2에서 다룬 IP mode) |
target-type: ip이므로 ALB는 파드 IP로 직접 트래픽을 전달합니다. Service의 type: NodePort는 LBC가 Ingress 백엔드로 인식하기 위한 요구사항일 뿐, 실제 NodePort를 통한 트래픽 경유는 발생하지 않습니다.
배포 후 리소스 상태를 확인합니다:
$ kubectl get pod,ingress,svc,ep,endpointslice -n game-2048
NAME READY STATUS RESTARTS AGE
pod/deployment-2048-7bf64bccb7-dqtdb 1/1 Running 0 3m35s
pod/deployment-2048-7bf64bccb7-xwzbd 1/1 Running 0 3m35s
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/ingress-2048 alb * k8s-game2048-ingress2-70d50ce3fd-748718985.ap-northeast-2.elb.amazonaws.com 80 3m35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/service-2048 NodePort 10.100.44.189 <none> 80:31243/TCP 3m35s
NAME ENDPOINTS AGE
endpoints/service-2048 192.168.10.128:80,192.168.7.160:80 3m35s
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
endpointslice.discovery.k8s.io/service-2048-824cg IPv4 80 192.168.7.160,192.168.10.128 3m35s
Ingress의 ADDRESS에 ALB DNS가 나타났습니다. 이 주소로 브라우저에서 접속하면 2048 게임이 정상적으로 표시됩니다.
여기서 눈여겨볼 점은 EndpointSlice의 ENDPOINTS입니다. 192.168.7.160, 192.168.10.128 — 이것이 파드의 실제 VPC IP이며, ALB의 Target Group에 이 IP가 직접 등록되어 있습니다. target-type: ip 덕분에 NodePort(31243)를 경유하지 않고 ALB → 파드로 바로 트래픽이 전달됩니다.
참고사항 - TargetGroupBinding으로 EKS 블루/그린 업그레이드
클러스터와 워커노드를 순차 업데이트할 때는 서비스 중단이 발생할 수 있습니다. 이를 ALB + TargetGroupBinding을 활용한 블루/그린 방식으로 무중단 업그레이드가 가능합니다.

핵심 아이디어는 Ingress를 제거하고 ALB를 Kubernetes 외부에서 독립적으로 관리하는 것입니다:
- Terraform 등으로 ALB를 별도 구성한 후, 파드를
TargetGroupBindingCRD로 직접 연결합니다 - ALB가 Kubernetes와 독립적이므로 Route 53, CloudFront, WAF 등의 설정을 클러스터 업그레이드 시 건드릴 필요가 없습니다

두 클러스터(v1, v2)가 하나의 ALB를 공유하되, Target Group을 클러스터별로 분리하고 Listener의 Weight를 조정하여 트래픽을 제어합니다:
| 단계 | v1 TG | v2 TG | 상태 |
|---|---|---|---|
| 평상시 운영 | 50% | 50% | 양쪽 클러스터에 균등 분배 |
| v1 업그레이드 전 | 0% | 100% | v1에서 트래픽 제거 후 업그레이드 진행 |
| v2 업그레이드 전 | 100% | 0% | v2에서 트래픽 제거 후 업그레이드 진행 |
| 업그레이드 완료 | 50% | 50% | 양쪽 복귀 |
ExternalDNS
위에서 Ingress로 ALB를 프로비저닝하면 AWS가 자동 생성한 DNS 이름(k8s-game2048-xxxxxxxx.ap-northeast-2.elb.amazonaws.com)을 받게 됩니다. 하지만 실제 서비스에서는 app.example.com 같은 자체 도메인을 사용해야 하죠. 이때 ALB DNS를 Route 53에 A 레코드로 매번 수동 등록하는 것은 비현실적입니다.
ExternalDNS는 이 과정을 자동화합니다. 쿠버네티스 Service, Ingress, Gateway API 리소스에 도메인을 지정하면, ExternalDNS가 이를 감지하여 AWS Route 53(또는 Azure DNS, GCP Cloud DNS)에 A/TXT 레코드를 자동으로 생성하고, 리소스가 삭제되면 레코드도 함께 정리합니다.
ExternalDNS를 사용하려면 퍼블릭 도메인을 소유하고 있어야 하며, 해당 도메인이 Route 53 Hosted Zone에 등록되어 있어야 합니다.
ExternalDNS 설치 및 활용 테스트
먼저 아래 명령으로 정책파일을 생성합니다.
cat << EOF > externaldns_controller_policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"route53:ListTagsForResources"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones"
],
"Resource": [
"*"
]
}
]
}
EOF
해당 파일로 IAM 정책 생성 후, IRSA를 생성합니다.
# IAM 정책 생성
aws iam create-policy \
--policy-name ExternalDNSControllerPolicy \
--policy-document file://externaldns_controller_policy.json
# 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/ExternalDNSControllerPolicy | jq
# IRSA 생성 : CloudFormation을 통해 IAM Role 생성
CLUSTER_NAME=myeks
eksctl create iamserviceaccount \
--cluster=$CLUSTER_NAME \
--namespace=kube-system \
--name=external-dns \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/ExternalDNSControllerPolicy \
--override-existing-serviceaccounts \
--approve
이후 ExternalDNS를 배포하고 Helm repository 등록 및 차트를 설치하여 서비스와 연동을 실시합니다.
Gateway API
Gateway API는 Ingress의 후속 표준입니다. 기존 Ingress는 단일 리소스에 인프라 설정과 라우팅 규칙이 혼재하고, 어노테이션에 의존하는 구조적 한계가 있었습니다. 또한 F5 NGINX가 kubernetes/ingress-nginx 프로젝트의 메인테이너에서 철수하면서, 커뮤니티 차원에서도 Gateway API로의 전환이 가속화되고 있습니다.
배포 테스트 - 사전설정
아래 과정을 통해 LBC 버전 및 Gateway API 사용 준비가 되어있는지 확인합니다.
# LBC > v2.13.0 버전 이상
kubectl describe pod -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller | grep Image: | uniq
Image: public.ecr.aws/eks/aws-load-balancer-controller:v3.1.0
# Installation of Gateway API CRDs # --server-side=true
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml # [REQUIRED] # Standard Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/experimental-install.yaml # [OPTIONAL: Used for L4 Routes] # Experimental Gateway API CRDs
kubectl get crd | grep gateway.networking
backendtlspolicies.gateway.networking.k8s.io 2026-03-19T08:37:54Z
gatewayclasses.gateway.networking.k8s.io 2026-03-19T08:37:03Z
gateways.gateway.networking.k8s.io 2026-03-19T08:37:04Z
grpcroutes.gateway.networking.k8s.io 2026-03-19T08:37:04Z
httproutes.gateway.networking.k8s.io 2026-03-19T08:37:04Z
referencegrants.gateway.networking.k8s.io 2026-03-19T08:37:05Z
tcproutes.gateway.networking.k8s.io 2026-03-19T08:37:55Z
tlsroutes.gateway.networking.k8s.io 2026-03-19T08:37:56Z
udproutes.gateway.networking.k8s.io 2026-03-19T08:37:56Z
xbackendtrafficpolicies.gateway.networking.x-k8s.io 2026-03-19T08:37:56Z
xlistenersets.gateway.networking.x-k8s.io 2026-03-19T08:37:56Z
kubectl api-resources | grep gateway.networking
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
grpcroutes gateway.networking.k8s.io/v1 true GRPCRoute
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
referencegrants refgrant gateway.networking.k8s.io/v1beta1 true ReferenceGrant
tcproutes gateway.networking.k8s.io/v1alpha2 true TCPRoute
tlsroutes gateway.networking.k8s.io/v1alpha2 true TLSRoute
udproutes gateway.networking.k8s.io/v1alpha2 true UDPRoute
backendtlspolicies btlspolicy gateway.networking.k8s.io/v1alpha3 true BackendTLSPolicy
xbackendtrafficpolicies xbtrafficpolicy gateway.networking.x-k8s.io/v1alpha1 true XBackendTrafficPolicy
xlistenersets lset gateway.networking.x-k8s.io/v1alpha1 true XListenerSet
kubectl explain gatewayclasses.gateway.networking.k8s.io.spec
kubectl explain gateways.gateway.networking.k8s.io.spec
kubectl explain httproutes.gateway.networking.k8s.io.spec
# Installation of LBC Gateway API specific CRDs
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.k8s.aws
listenerruleconfigurations.gateway.k8s.aws 2026-03-19T06:05:41Z
loadbalancerconfigurations.gateway.k8s.aws 2026-03-19T06:05:42Z
targetgroupconfigurations.gateway.k8s.aws 2026-03-19T06:05:41Z
kubectl api-resources | grep gateway.k8s.aws
listenerruleconfigurations gateway.k8s.aws/v1beta1 true ListenerRuleConfiguration
loadbalancerconfigurations gateway.k8s.aws/v1beta1 true LoadBalancerConfiguration
targetgroupconfigurations gateway.k8s.aws/v1beta1 true TargetGroupConfiguration
kubectl explain loadbalancerconfigurations.gateway.k8s.aws.spec
kubectl explain listenerruleconfigurations.gateway.k8s.aws.spec
kubectl explain targetgroupconfigurations.gateway.k8s.aws.spec
LBC에 Gateway API 활성화
# 설치 정보 확인
helm list -n kube-system
helm get values -n kube-system aws-load-balancer-controller # helm values 에 Args 및 활성화 값이 현재는 없음
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep Args: -A2
Args:
--cluster-name=myeks
--ingress-class=alb
# 모니터링
kubectl get pod -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller --watch
# deployment 에 feature flag를 활성화 : By default, the LBC will not listen to Gateway API CRDs.
KUBE_EDITOR="nano" kubectl edit deploy -n kube-system aws-load-balancer-controller
...
- args:
- --cluster-name=myeks
- --ingress-class=alb
- --feature-gates=NLBGatewayAPI=true,ALBGatewayAPI=true
...
# 확인
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep Args: -A3
Args:
--cluster-name=myeks
--ingress-class=alb
--feature-gates=NLBGatewayAPI=true,ALBGatewayAPI=true
ExternalDNS 에 gateway api 지원 설정
# deployment 에 gateway api 지원 설정 : external-dns-values.yaml 파일 편집 -> 아래 추가
--------------------------
# 리소스 감지 대상
sources:
- service
- ingress
- gateway-httproute
- gateway-grpcroute
- gateway-tlsroute
- gateway-tcproute
- gateway-udproute
--------------------------
# ExternalDNS 에 gateway api 지원 설정
helm upgrade -i external-dns external-dns/external-dns -n kube-system -f external-dns-values.yaml
# 확인
kubectl describe deploy -n kube-system external-dns | grep Args: -A15
Args:
--log-level=info
--log-format=text
--interval=1m
--source=service
--source=ingress
--source=gateway-httproute
--source=gateway-grpcroute
--source=gateway-tlsroute
--source=gateway-tcproute
--source=gateway-udproute
--policy=sync
--registry=txt
--txt-owner-id=stduy-myeks-cluster
--domain-filter=gasida.link
--provider=aws
CoreDNS와 쿠버네티스 DNS
Part 2에서 Service의 세 구현체 중 하나로 CoreDNS를 언급했습니다. kube-proxy가 iptables로 ClusterIP → 파드 IP 변환을 담당한다면, CoreDNS는 서비스 이름 → ClusterIP 변환(DNS 해석)을 담당합니다. 파드가 curl my-service를 호출할 때, 그 이름을 IP로 바꿔주는 것이 CoreDNS입니다.
이 섹션은 Kubernetes CoreDNS (popappend) 블로그를 참고하여 작성했습니다.
CoreDNS의 동작 원리
CoreDNS는 Corefile이라는 설정 파일을 기반으로 동작하며, 플러그인 체인 구조로 DNS 쿼리를 처리합니다. EKS에서는 kube-system 네임스페이스에 ConfigMap으로 관리됩니다.
핵심 플러그인들:
| 플러그인 | 역할 |
|---|---|
| kubernetes | cluster.local 도메인에 대한 DNS 레코드를 Kubernetes API에서 가져와 응답 |
| forward | cluster.local 외의 외부 도메인 쿼리를 업스트림 DNS 서버(VPC DNS 등)로 전달 |
| cache | 응답을 캐싱하여 반복 쿼리 성능 개선 |
| hosts | /etc/coredns/NodeHosts 파일을 참조하여 노드 정보 제공 |
| prometheus | :9153에서 메트릭 수집 |
쿼리 흐름을 정리하면:
파드에서 "my-service.default.svc.cluster.local" 쿼리
│
▼
CoreDNS (10.100.0.10:53)
│
├─ cluster.local 도메인? → kubernetes 플러그인이 API Server에서 조회 → ClusterIP 응답
│
└─ 외부 도메인? → forward 플러그인이 업스트림(VPC DNS: 192.168.0.2)으로 전달
파드의 DNS 설정
파드 안에서 /etc/resolv.conf를 보면:
nameserver 10.100.0.10 ← CoreDNS Service의 ClusterIP
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
ndots와 search domain
ndots:5는 "도메인에 점(.)이 5개 미만이면 FQDN이 아니라고 판단"한다는 의미입니다. FQDN이 아니면 search 도메인을 순서대로 붙여서 시도합니다.
예를 들어 파드에서 curl google.com을 하면 (점이 1개 < 5):
| 순서 | 쿼리 | 결과 |
|---|---|---|
| 1 | google.com.default.svc.cluster.local |
NXDOMAIN |
| 2 | google.com.svc.cluster.local |
NXDOMAIN |
| 3 | google.com.cluster.local |
NXDOMAIN |
| 4 | google.com.ap-northeast-2.compute.internal |
NXDOMAIN |
| 5 | google.com |
응답 |
외부 도메인 하나 조회하는데 불필요한 DNS 쿼리가 4번 더 발생합니다. 이를 피하려면 google.com.처럼 끝에 점을 붙여 FQDN으로 명시하면 search domain을 건너뛰고 바로 조회합니다.
curl my-service만 써도 search domain 덕분에 my-service.default.svc.cluster.local로 자동 해석됩니다. ndots:5는 내부 서비스 호출을 짧게 쓸 수 있게 해주는 설정입니다.
dnsPolicy
파드의 dnsPolicy 설정에 따라 /etc/resolv.conf가 달라집니다:
| dnsPolicy | nameserver | 용도 |
|---|---|---|
| ClusterFirst (기본값) | CoreDNS ClusterIP | 클러스터 도메인은 CoreDNS, 외부는 업스트림으로 전달 |
| Default | 노드의 /etc/resolv.conf |
CoreDNS를 거치지 않고 노드의 DNS 설정을 그대로 사용 |
| None | dnsConfig 필드에서 직접 지정 |
완전한 커스텀 DNS 설정 |
hostNetwork: true인 파드(예: aws-node, kube-proxy)는 ClusterFirst여도 노드의 DNS를 사용합니다.
참고자료 - 쿠버네티스의 DNS 쿼리 흐름
쿠버네티스 클러스터 내에서 DNS 쿼리가 어떤 경로를 거치는지는 아래 다이어그램을 참고하세요:
