CloudNet@팀의 EKS 스터디 AEWS 2기에 작성된 자료를 토대로 작성합니다.
이번 포스팅에서는 CoreDNS 사용시 이슈사항을 소개해보겠습니다.
운영환경의 경험을 기반으로 DNS에 대한 이야기를 풀어보는 시간을 가지도록 하겠습니다.
✅ 환경 준비
☑️ 편의를 위해 ExternalDNS 설정까지 같이 진행하겠습니다.
(BINARY-N/A:N/A) [root@operator-host ~]# MyDomain=cubicle.com
(BINARY-N/A:N/A) [root@operator-host ~]# aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." | jq
{
"HostedZones": [
{
"ResourceRecordSetCount": 7,
"CallerReference": "RISWorkflow-RD:c9f2e4c5-4a7b-4ba4-a1f1-acba36eb2a65",
"Config": {
"Comment": "HostedZone created by Route53 Registrar",
"PrivateZone": false
},
"Id": "/hostedzone/Z05493571IQ5M12FEKOC1",
"Name": "cubicle.zone."
}
],
"DNSName": "cubicle.com.",
"IsTruncated": false,
"MaxItems": "100"
}
(BINARY-N/A:N/A) [root@operator-host ~]#
(BINARY-N/A:N/A) [root@operator-host ~]# MyDnzHostedZoneId=`aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text`
(BINARY-N/A:N/A) [root@operator-host ~]# echo $MyDnzHostedZoneId
/hostedzone/Z05493571IQ5M12FEKOC1
# (옵션) NS 레코드 타입 첫번째 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
# (옵션) A 레코드 타입 모두 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']"
# A 레코드 타입 조회
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" --output text
# A 레코드 값 반복 조회
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
☑️ ExternalDNS 설치
# EKS 배포 시 Node IAM Role 설정되어 있음
# eksctl create cluster ... --external-dns-access ...
#
MyDomain=<자신의 도메인>
MyDomain=cubicle.com
# 자신의 Route 53 도메인 ID 조회 및 변수 지정
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
# 변수 확인
echo $MyDomain, $MyDnzHostedZoneId
# ExternalDNS 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
cat externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
# 확인 및 로그 모니터링
kubectl get pod -l app.kubernetes.io/name=external-dns -n kube-system
kubectl logs deploy/external-dns -n kube-system -f
☑️ NLB + SVC 배포
# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
kubectl logs deploy/external-dns -n kube-system -f
혹은
kubectl stern -l app.kubernetes.io/name=external-dns -n kube-system
# 테트리스 디플로이먼트 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
labels:
app: 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"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
#service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
selector:
app: tetris
ports:
- port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
EOF
# 배포 확인
kubectl get deploy,svc,ep tetris
# NLB에 ExternanDNS 로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
# Route53에 A레코드 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
# 확인
dig +short tetris.$MyDomain @8.8.8.8
dig +short tetris.$MyDomain
# 도메인 체크
echo -e "My Domain Checker Site1 = https://www.whatsmydns.net/#A/tetris.$MyDomain"
echo -e "My Domain Checker Site2 = https://dnschecker.org/#A/tetris.$MyDomain"
# 웹 접속 주소 확인 및 접속
echo -e "Tetris Game URL = http://tetris.$MyDomain"
✅ DNS 이해하기
ndots / search domain / coredns에 대한 개념은 아래 포스팅 참고
https://themapisto.tistory.com/240
Core DNS 성능개선 [ nodelocaldns / ndots & FQDN ]
먼저 CoreDNS 성능 개선을 위해 여러가지 테스트를 하기전에 CoreDNS와 NodeLocalDNS가 무엇인지, 그리고 그 동작과정에 대해 먼저 알아보겠습니다.이를 이해하기 위해서는 ndots과 search 도메인 , 그리고
themapisto.tistory.com
Known 이슈사항 정리
DNS lookup timeouts due to races in conntrack # 3287
https://github.com/weaveworks/weave/issues/3287
DNS lookup timeouts due to races in conntrack · Issue #3287 · weaveworks/weave
What happened? We are experiencing random 5 second DNS timeouts in our kubernetes cluster. How to reproduce it? It is reproducible by requesting just about any in-cluster service, and observing tha...
github.com
conntrack 테이블에 트래픽이 과도하게 발생 시 UDP Race condition을 일으킴
- Race condition이란?
같은 소켓을 통해 동시에 두 개의 레이싱 UDP 패킷이 전송될 때 발생하는 UDP 패킷이 드랍되는 현상 예를 들어, DNS 조회의 경우 UDP 통신 입니다. glibc와 musl libc는 모두 A와 AAAA DNS 조회를 병렬로 수행합니다. 그 결과, UDP 패킷 중 하나가 커널에 의해 삭제되고, 그러면 클라이언트가 기본적으로 보통 5초인 시간 초과 후에 다시 시도하게 됩니다.
conntrack -S 명령어 입력시 insert_failed 항목을 아래 유심히 봅시다.
어플리케이션 로그에서 DNS 타임아웃이 많이 보이나요?
UDP Race condition을 의심해볼수 있습니다.
다행히도 경쟁 조건 중 두 가지는 2018년 커널 코드 내에서 완화되었습니다.
하지만 netfilter 커널에 대한 이러한 패치는 DNS 서버의 단일 인스턴스를 실행할 때만 문제를 해결했고(다른 조건의 영향만 줄였음) 문제를 완전히 해결하지는 못했습니다.
[root@ip-192-168-1-61 ~]# conntrack -S
cpu=0 found=58 invalid=2121 insert=0 insert_failed=19 drop=19 early_drop=0 error=38 search_restart=0
cpu=1 found=67 invalid=2120 insert=0 insert_failed=16 drop=16 early_drop=0 error=29 search_restart=0
- insert_failed란?
- 아래 그림을 보고 이해해 봅시다.

✅ 임시 조치
# nf_table conntrack max 설정
✅ 해결 방안
# nodelocal dns 설치
# 어플리케이션 API 게이트웨이 FQDN 적용
✅ DNS 이해하기
- 모든 파드에는 다음과 같은 설정값이 있다. 각각의 값은 dns 서버의 IP와 searchdomain 그리고 ndots option이 있다. 아래와 같이 dns config를 직접 적어주지 않는 일반적인 경우에는 CoreDNS의 Corefile의 설정을 따른다.
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
(Ted:N/A) [root@operator-host koo]# k exec dns-example -- cat /etc/resolv.conf
search ns1.svc.cluster-domain.example my.dns.search.suffix
nameserver 1.2.3.4
options ndots:2 edns0
(Ted:N/A) [root@operator-host koo]#
- Corefile 확인
(Ted:N/A) [root@operator-host koo]# k describe cm -n kube-system coredns
Name: coredns
Namespace: kube-system
Labels: eks.amazonaws.com/component=coredns
k8s-app=kube-dns
Annotations: <none>
Data
====
Corefile:
----
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
BinaryData
====
Events: <none>
- 일반적인 파드의 /etc/resolv.conf 설정
(Ted:N/A) [root@operator-host koo]# k exec tetris-6786cddbd5-h9wr2 -- cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
nameserver 10.100.0.10
options ndots:5
✅ 클러스터 내부 DNS
☑️ tcpdump about internal DNS
# 워커노드에 각각 1개씩 netshoot 파드를 배포해본다.
# 테스트용 netshoot-pod 디플로이먼트 생성
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot-pod
spec:
replicas: 3
selector:
matchLabels:
app: netshoot-pod
template:
metadata:
labels:
app: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[0].metadata.name}')
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[1].metadata.name}')
PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[2].metadata.name}')
# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
# 1번. tetris.svc.cluster.local로 질의시 5번만에 조회 ( 같은 네임스페이스 )
(Ted:N/A) [root@operator-host koo]#
kubectl exec -it $PODNAME1 -- curl -v tetris.svc.cluster.local
# 2번. tetris로 질의시 1번만에 조회 ( 서치도메인 default.svc.cluster.local 제일 앞에 적용)
(Ted:N/A) [root@operator-host koo]#
kubectl exec -it $PODNAME1 -- curl -v tetris
# 2번. tetris.default 로 질의시 2번만에 조회 ( 서치도메인 svc.cluster.local 2번째에 적용)
(Ted:N/A) [root@operator-host koo]#
kubectl exec -it $PODNAME1 -- curl -v tetris.default
# 4번. tetris.default.svc.cluster.local. ( 풀FQDN으로 질의시 1번만에 조회)
(Ted:N/A) [root@operator-host koo]#
kubectl exec -it $PODNAME1 -- curl -v tetris.default.svc.cluster.local.
✅ 클러스터 외부 DNS
☑️ tcpdump about internal DNS (google.com)
# 운영 서버
(Ted:N/A) [root@operator-host ~]# kubectl exec -it $PODNAME1 -- curl -v google.com
# 노드 1번
[ec2-user@ip-192-168-3-215 ~]$ sudo tcpdump -i any -nn udp and src host 192.168.3.113
☑️ 서치 도메인
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
nameserver 10.100.0.10
options ndots:5
✅ coreDNS + nodeLocal DNS 통신 과정 알아보기
☑️ nodelocal dns 설치
wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml
# 환경변수 설정
kubedns=`kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP}`
domain=cluster.local
localdns=169.254.20.10
# __PILLAR__DNS__SERVER__ : kubectl get svc -n kube-system | grep kube-dns | awk '{ print $3 }'
# __PILLAR__DNS__DOMAIN__ : 클러스터 도메인을 나타내며 기본값은 cluster.local
# __PILLAR__LOCAL__DNS__ : NodeLocal DNSCache에 대해 선택된 로컬 수신 IP 주소 (169.254.20.10으로 고정입니다.)
# manifest 변수 수정
sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/__PILLAR__DNS__SERVER__/$kubedns/g" nodelocaldns.yaml
### MAC 일경우
sed -i '' "s|__PILLAR__LOCAL__DNS__|$localdns|g; s|__PILLAR__DNS__DOMAIN__|$domain|g; s|__PILLAR__DNS__SERVER__|$kubedns|g" nodelocaldns.yaml
- 배포 후 확인
kubectl create -f nodelocaldns.yaml
# kubectl get pods -n kube-system | grep node-local
node-local-dns-2fncz 1/1 Running 0 98s
node-local-dns-cb422 1/1 Running 0 98s
node-local-dns-f22bq 1/1 Running 0 98s
- 테스트 전 로그 설정
# kubectl -n kube-system edit configmap node-local-dns
...
apiVersion: v1
data:
Corefile: |
cluster.local:53 {
log
errors
cache {
success 9984 30
denial 9984 5
}
...
# nodelocaldns daemonset 배포
kubectl rollout restart daemonset -n kube-system node-local-dns
# 테스트 pod 배포
kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
- 테스트 pod에 접속해서 nslookup 실행
# k exec -it dnsutils -- /bin/sh
# nslookup
> kubernetes.default
Server: 10.100.0.10
Address: 10.100.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.100.0.1
> google.com
Server: 10.100.0.10
Address: 10.100.0.10#53
Non-authoritative answer:
Name: google.com
Address: 142.250.206.238
- nodelocaldns의 로그 확인
kubectl logs --namespace=kube-system -l k8s-app=node-local-dns -f
# 로그를 확인 해보면 클러스터 내부에 있는 서비스 예를 들면 default 네임스페이스의 kubernetes 같이
# DNS 쿼리를 외부로 보내거나 외부 DNS 서버에서 응답을 기다릴 필요가 없으므로 응답 시간이 훨씬 빠릅니다
[INFO] Added back nodelocaldns rule - {filter INPUT [-p tcp -d 10.200.1.10 --dport 53 -j ACCEPT -m comment --comment NodeLocal DNS Cache: allow DNS traffic]}
[INFO] Added back nodelocaldns rule - {filter INPUT [-p udp -d 10.200.1.10 --dport 53 -j ACCEPT -m comment --comment NodeLocal DNS Cache: allow DNS traffic]}
[INFO] Added back nodelocaldns rule - {raw OUTPUT [-p tcp -s 10.200.1.10 --sport 53 -j NOTRACK -m comment --comment NodeLocal DNS Cache: skip conntrack]}
[INFO] Added back nodelocaldns rule - {raw OUTPUT [-p udp -s 10.200.1.10 --sport 53 -j NOTRACK -m comment --comment NodeLocal DNS Cache: skip conntrack]}
# conntrack을 우회하도록 설정한 iptables 규칙이 추가되었음을 나타냅니다.
# conntrack의 비활성화로 성능을 최적화합니다.
# 1. Skipping iptables DNAT and connection tracking will help reduce conntrack races and avoid UDP DNS entries filling up conntrack table.
# 2. reducing the number of queries for the kube-dns service.
- 왼쪽은 nodelocalDNS 에 조회된 로그 입니다.
- 오른쪽은 coreDNS에 조회된 로그입니다. ( 기본적으로 30초간 캐쉬에 저장해놓고 그 동안에 요청이 들어올때는 캐쉬에서 처리 하기 때문에 요청이 들어오지 않는 모습)
'DevOps' 카테고리의 다른 글
[AWS EKS] (8) EKS 스터디 3주차 ( Storage 성능 지표 및 벤치마크 ) (0) | 2025.02.20 |
---|---|
[AWS EKS] (7) EKS 스터디 3주차 ( Storage - provsioner와 CSI driver 차이점 ) (0) | 2025.02.19 |
[AWS EKS] (5) EKS 스터디 2주차 ( iptables - CNI HOST PORT CHAIN ) (0) | 2025.02.12 |
[AWS EKS] (4) EKS 스터디 2주차 ( VPC CNI ) (0) | 2025.02.12 |
[AWS EKS] (3) EKS 스터디 1주차 ( diskio - CPU성능 관계 ) (0) | 2025.02.04 |