LLM WikiAccess-protected knowledge portal
← 스터디 홈
3편 · 약 25분

DNS 심화: 조회 과정, 캐싱, TTL, 도메인 설계

DNS가 인프라 장애의 숨겨진 원인인 이유

RDS 인스턴스를 failover했다. Primary가 바뀌었고 새 엔드포인트 IP도 정상이다. 그런데 애플리케이션은 여전히 구 Primary로 연결을 시도한다. 원인은 DNS 캐시다. TTL이 300초였는데, 커넥션 풀이 재시작되지 않아 5분이 지나도록 구 IP를 바라봤다.

DNS는 "그냥 이름 → IP 변환" 이 아니다. 캐싱 계층이 여러 곳에 있고, TTL이 각 계층마다 독립적으로 동작하며, 부정 응답(NXDOMAIN)도 캐시된다. 이 구조를 모르면 failover, 마이그레이션, 서비스 이전 시 예상치 못한 지연이 발생한다.


DNS 계층 구조

DNS는 트리 구조의 분산 데이터베이스다. 루트(Root)에서 시작해 Top-Level Domain(TLD), 그리고 권한 네임서버(Authoritative NS)로 내려간다.

Root NS (" . ") 13개 루트 서버 클러스터 (a~m.root-servers.net) .com TLD NS Verisign 운영 .org TLD NS PIR 운영 .io TLD NS ICANN 위임 example.com NS AWS Route 53 / Cloudflare amazon.com NS Amazon Route 53 db.example.com A 10.0.1.50 TTL 60 rds.amazonaws.com CNAME → IP TTL 5
DNS 계층 구조

재귀적 조회 과정

클라이언트가 db.prod.example.com을 처음 조회하면 다음 과정이 일어난다.

앱/클라이언트 재귀 리졸버 Root NS TLD NS (.com) 권한 NS ① 쿼리: db.prod.example.com? 로컬 캐시 없음 → 리졸버로 전달 ② 리졸버 캐시 확인 — MISS ③ Root에 .com NS 요청 ④ .com TLD NS 주소 반환 ⑤ TLD에 example.com NS 요청 ⑥ 권한 NS 주소 반환 ⑦ 권한 NS에 db.prod.example.com A 레코드 요청 ⑧ A 레코드: 10.0.1.50, TTL=60 반환 ⑨ 리졸버 캐시 저장 (TTL=60s) ⑩ 클라이언트에 10.0.1.50 반환 OS 캐시 저장 전체 소요 시간: 캐시 MISS 시 수십 ms (Root→TLD→Auth 3단계) | 캐시 HIT 시 <1ms 인기 도메인은 리졸버 캐시에서 바로 응답. Root NS는 거의 매번 캐시 HIT.
DNS 재귀 조회 흐름

주요 캐싱 계층

위치역할TTL 따름
애플리케이션 JVM/glibc로컬 DNS 캐시별도 설정 필요 (Java: networkaddress.cache.ttl)
OS (/etc/hosts, nscd)시스템 레벨 캐시TTL 따름 (nscd는 자체 TTL 가짐)
재귀 리졸버 (8.8.8.8, 회사 내부)가장 큰 캐시 계층TTL 따름
권한 NS최종 정답TTL 설정 원본

DNS 레코드 타입

타입용도예시
AIPv4 주소db.prod.example.com → 10.0.1.50
AAAAIPv6 주소db.prod.example.com → 2001:db8::1
CNAME다른 이름으로 별칭db-ro.prod.example.com → rds.aws.com
NS이 도메인의 권한 NS 목록example.com → ns1.route53.com
SOA도메인 관리 정보 (Serial, TTL 기본값 등)권한 NS가 갖는 마스터 레코드
MX메일 서버우선순위 포함
TXT임의 텍스트 (SPF, DKIM, 도메인 소유 확인)v=spf1 include:...
SRV서비스 위치 (포트 포함)_postgresql._tcp.example.com → host:5432
PTR역방향 조회 (IP → 이름)50.1.0.10.in-addr.arpa → db.prod

CNAME은 A/AAAA 레코드가 있는 곳에서는 사용할 수 없다. example.com 루트 도메인에 CNAME을 달면 NS, SOA 레코드와 충돌한다. 루트 도메인에는 AWS Route 53의 ALIAS 레코드나 Cloudflare의 CNAME Flattening을 사용한다.


TTL 설계 전략

TTL은 리졸버와 클라이언트가 해당 레코드를 캐시할 수 있는 시간(초)이다. 짧을수록 변경이 빠르게 전파되지만, 권한 NS에 쿼리가 늘어난다. 길수록 캐시 효율이 높지만 변경 전파가 느리다.

짧은 TTL (30~60초)
✅ 빠른 failover / 마이그레이션 ✅ DNS 기반 트래픽 전환 유연 ⚠ 권한 NS 쿼리 급증 ⚠ 캐시 효율 낮음 → 지연 증가 가능
사용: 계획된 failover 직전 24~48h 전 낮추기, 클라우드 엔드포인트
긴 TTL (300~3600초)
✅ 높은 캐시 효율 ✅ 권한 NS 부하 감소 ✅ 리졸버 장애 시 캐시로 서비스 유지 ⚠ IP 변경 전파 시간 길어짐
사용: 정적 인프라, CDN origin, 메일 서버
운영 패턴: 계획적 TTL 낮추기
변경 48h 전 → TTL을 60초로 낮춤 캐시 만료 대기 (기존 TTL 소진) IP 변경 적용 (전파 시간 = 새 TTL = 60초) 안정화 후 TTL 원복 (300초)
TTL 설계 트레이드오프

AWS RDS의 기본 DNS TTL은 5초다. Multi-AZ failover 시 DNS가 바뀌는데, JVM의 기본 DNS 캐시 TTL(networkaddress.cache.ttl = -1, 무한)이면 재시작 전까지 구 IP를 계속 사용한다. JVM 기반 애플리케이션은 반드시 TTL을 단기로 설정해야 한다.

# JVM DNS 캐시 TTL 설정 (초)
# -1 = 무한, 0 = 캐시하지 않음
-Dnetworkaddress.cache.ttl=5
-Dnetworkaddress.cache.negative.ttl=0

부정 캐싱 (Negative Caching / NXDOMAIN)

존재하지 않는 도메인을 조회하면 권한 NS가 NXDOMAIN을 반환한다. 재귀 리졸버는 이 결과도 캐시한다.

캐시 기간은 SOA 레코드의 MINIMUM 필드(또는 응답의 TTL 중 더 작은 값)로 결정된다. RFC 2308은 NXDOMAIN 캐시 기간을 최대 3시간(10800초)으로 권고한다.

문제 시나리오: 새 서비스를 배포하면서 DNS 레코드를 늦게 추가했다. 그 사이 애플리케이션이 NXDOMAIN을 받아 캐시했다. 레코드를 추가해도 NXDOMAIN 캐시가 만료될 때까지(최대 수 시간) 연결이 안 된다. 이 경우 리졸버 캐시를 직접 비워야 한다.

# Linux systemd-resolved 캐시 비우기
sudo systemd-resolve --flush-caches

# macOS DNS 캐시 비우기
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

# CoreDNS (Kubernetes): Pod 재시작이 가장 확실
kubectl rollout restart deployment/coredns -n kube-system

Split-Horizon DNS (스플릿 DNS)

같은 도메인 이름이 내부 네트워크에서는 내부 IP로, 외부에서는 공인 IP로 응답하도록 구성하는 패턴이다.

내부 쿼리 (VPC / 사내망)
내부 클라이언트 내부 리졸버
(Route 53 Private Hosted Zone
/ CoreDNS)
db.example.com
→ 10.0.1.50
(프라이빗 IP)
외부 쿼리 (인터넷)
외부 클라이언트 공개 리졸버
(8.8.8.8 / 1.1.1.1)
db.example.com
→ NXDOMAIN
(또는 공인 IP)
사용 이유
내부 서비스가 외부에 노출되지 않도록 NAT hairpinning 회피 (내부→공인IP→내부 방지) 환경별 다른 엔드포인트 (dev/prod 동일 이름)
Split-Horizon DNS 구조

쿠버네티스 CoreDNS는 클러스터 내부에서 service.namespace.svc.cluster.local 형식으로 서비스를 노출한다. Pod가 db-service를 조회하면 CoreDNS가 클러스터 내부 ClusterIP를 반환한다. 이것이 쿠버네티스의 스플릿 DNS다.


운영 진단 도구

dig — DNS 쿼리의 표준 도구

# 기본 A 레코드 조회
dig db.prod.example.com A

# 특정 리졸버로 조회 (기본 리졸버 우회)
dig @8.8.8.8 db.prod.example.com A

# 권한 NS에서 직접 조회 (+norecurse)
dig @ns1.p01.awsdns-01.com db.prod.example.com A +norecurse

# 전체 조회 과정 트레이스
dig db.prod.example.com A +trace

# TTL 포함 상세 출력
dig db.prod.example.com A +ttlunits +nocmd +noall +answer

# SOA 레코드 확인 (negative TTL 포함)
dig example.com SOA

# 역방향 조회 (IP → 호스트명)
dig -x 10.0.1.50

dig 응답 읽는 법

;; ANSWER SECTION:
db.prod.example.com.  56  IN  A  10.0.1.50
                      ↑         ↑
                  남은 TTL    레코드 값

;; AUTHORITY SECTION:
example.com.   172800 IN NS ns1.p01.awsdns-01.com.

;; Query time: 8 msec          ← 리졸버 응답 시간
;; SERVER: 10.0.0.2#53         ← 응답한 리졸버
;; WHEN: Thu Jul 10 09:00:00 UTC 2026
;; MSG SIZE rcvd: 72

Query time: 0 msec이면 리졸버 캐시에서 응답. 8 msec 이상이면 상위 NS까지 조회한 것이다.

nslookup / host

# 간단한 조회
nslookup db.prod.example.com
host db.prod.example.com

# 리버스 조회
nslookup 10.0.1.50

쿠버네티스 CoreDNS

CoreDNS는 Go로 작성된 플러그인 기반 DNS 서버로, 쿠버네티스 클러스터의 기본 DNS다.

# 클러스터 내부 DNS 형식
<service>.<namespace>.svc.<cluster-domain>
  └─ 예: mysql.prod.svc.cluster.local → ClusterIP

# Pod DNS
<pod-ip>.<namespace>.pod.<cluster-domain>
  └─ 예: 10-0-1-50.prod.pod.cluster.local

# CoreDNS 설정 확인
kubectl get configmap coredns -n kube-system -o yaml

CoreDNS의 cache 플러그인 기본 TTL은 30초다. DB failover 후 새 IP가 30초 안에 전파되지 않으면 클러스터 내 연결이 잠시 실패할 수 있다. StatefulSet headless service를 사용하면 각 Pod IP를 직접 조회할 수 있다.


실무 체크리스트: DB 관련 DNS 이슈

상황원인확인 방법해결
failover 후 연결 계속 실패클라이언트 DNS 캐시dig @127.0.0.1로 로컬 캐시 확인캐시 비우기 / JVM TTL 단축
새 서비스 배포 후 연결 안 됨NXDOMAIN 캐시dig +trace 으로 캐시 위치 확인리졸버 캐시 비우기
간헐적 응답 지연DNS 리졸버 응답 느림dig 응답 시간 측정리졸버 변경 (8.8.8.8 / 1.1.1.1)
RDS CNAME이 변경 안 됨높은 TTL 캐시dig 에서 TTL 확인TTL 만료 대기 / 캐시 비우기
K8s 내 서비스 못 찾음CoreDNS 미작동kubectl -n kube-system get podCoreDNS 재시작

핵심 요약

  • DNS 조회는 Root → TLD → 권한 NS 3단계다. 인기 도메인은 재귀 리졸버 캐시에서 바로 응답한다.
  • TTL은 캐시 지속 시간이다. 변경 전 TTL을 낮추고, 변경 후 원복하는 패턴이 안전하다.
  • AWS RDS는 기본 TTL이 5초다. JVM 기반 애플리케이션은 반드시 networkaddress.cache.ttl=5 이하로 설정해야 failover가 빠르게 반영된다.
  • NXDOMAIN도 캐시된다. 새 DNS 레코드 추가 전에 잘못 조회하면 수 시간간 연결이 안 될 수 있다.
  • dig +trace로 전체 조회 경로를 따라가며 문제 계층을 찾는다.

References

  • RFC 1034 (DNS 개념), https://datatracker.ietf.org/doc/html/rfc1034
  • RFC 1035 (DNS 구현), https://datatracker.ietf.org/doc/html/rfc1035
  • RFC 2308 (Negative Caching), https://datatracker.ietf.org/doc/html/rfc2308
  • Cloudflare, "DNS server types", https://www.cloudflare.com/learning/dns/dns-server-types/
  • cr0x.net, "DNS Resolvers: Negative Caching", https://cr0x.net/en/dns-negative-caching-outages/
  • showdns.net, "DNS Resolution Explained", https://showdns.net/learn/dns-resolution-explained
  • AWS Route 53 Docs — Private Hosted Zones, https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html
  • CoreDNS documentation, https://coredns.io/manual/toc/
  • Split Horizon DNS Architecture Guide, https://resorsit.com/insights/dns-split-horizon/
  • hosseinnejati.medium.com, "DNS Deep Dive: Resolution, Caching, and Failure Modes", https://hosseinnejati.medium.com/dns-deep-dive-resolution-caching-and-failure-modes-be066bb0d66f