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)로 내려간다.
재귀적 조회 과정
클라이언트가 db.prod.example.com을 처음 조회하면 다음 과정이 일어난다.
주요 캐싱 계층
| 위치 | 역할 | TTL 따름 |
|---|---|---|
| 애플리케이션 JVM/glibc | 로컬 DNS 캐시 | 별도 설정 필요 (Java: networkaddress.cache.ttl) |
OS (/etc/hosts, nscd) | 시스템 레벨 캐시 | TTL 따름 (nscd는 자체 TTL 가짐) |
| 재귀 리졸버 (8.8.8.8, 회사 내부) | 가장 큰 캐시 계층 | TTL 따름 |
| 권한 NS | 최종 정답 | TTL 설정 원본 |
DNS 레코드 타입
| 타입 | 용도 | 예시 |
|---|---|---|
| A | IPv4 주소 | db.prod.example.com → 10.0.1.50 |
| AAAA | IPv6 주소 | 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에 쿼리가 늘어난다. 길수록 캐시 효율이 높지만 변경 전파가 느리다.
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-systemSplit-Horizon DNS (스플릿 DNS)
같은 도메인 이름이 내부 네트워크에서는 내부 IP로, 외부에서는 공인 IP로 응답하도록 구성하는 패턴이다.
(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)
쿠버네티스 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.50dig 응답 읽는 법
;; 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: 72Query 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 yamlCoreDNS의 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 pod | CoreDNS 재시작 |
핵심 요약
- 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