DNS 심화와 실무
라운드 로빈·GeoDNS·장애 조치와 DoH·DoT·DNSSEC의 역할을 이해하고 캐시와 보안 문제를 진단합니다.
DNS의 기본 동작과 레코드를 이해했으니, 이제 실무에서 자주 마주치는 라우팅, 프라이버시, 보안, 장애 진단을 살펴보겠습니다.
DNS는 단순한 “도메인 → IP 변환기”가 아니라 분산 데이터베이스, 캐시, 정책 기반 응답, 보안 검증이 함께 움직이는 인프라입니다.
DNS 라운드 로빈
하나의 이름에 여러 개의 A/AAAA 레코드를 두면 응답에 여러 IP가 포함될 수 있고, 권한 DNS 서버나 리졸버는 응답 순서를 바꾸어 줄 수 있습니다.
이를 흔히 DNS 라운드 로빈이라고 부릅니다.
권한 DNS는 같은 이름에 여러 A/AAAA 레코드를 응답할 수 있다. 실제 분산 비율은 캐시 TTL과 클라이언트 선택 방식에 좌우된다.
- 권한 DNS
www.example.com에 대해 여러 주소를 함께 응답한다.
- 리졸버 캐시
TTL 동안 응답 묶음을 보관하므로 즉시 새 비율로 바뀌지 않는다.
- 클라이언트 선택
first, random, happy-eyeballs 정책에 따라 실제 접속 IP가 정해진다.
| 기대 효과 | 주의할 한계 | 운영 보완 |
|---|---|---|
| 가벼운 접속 분산 | 정확한 로드밸런싱은 아님 | 헬스 체크와 LB를 함께 둔다 |
| 구현이 단순함 | 캐시 때문에 장애 IP가 남을 수 있음 | 짧은 TTL과 모니터링이 필요함 |
다만 DNS 라운드 로빈은 L4/L7 로드밸런서처럼 연결 단위로 정교하게 분산하지 않습니다.
리졸버, 운영체제, 브라우저가 응답을 캐시할 수 있고, 클라이언트가 응답 목록 중 어떤 주소를 선택하는지도 구현에 따라 달라집니다.
또한 단순 라운드 로빈만으로는 서버 장애를 자동 감지하지 못하므로, 실제 운영에서는 헬스 체크, 짧은 TTL, 로드밸런서, Anycast, CDN과 함께 설계합니다.
GeoDNS와 글로벌 서비스 라우팅
GeoDNS는 질의 위치나 네트워크 정보를 기준으로 다른 응답을 돌려주는 DNS 운영 방식입니다.
예를 들어 한국 사용자는 서울 리전, 미국 사용자는 버지니아 리전의 IP를 받도록 할 수 있습니다.
권한 DNS는 보통 최종 사용자 IP가 아니라 재귀 리졸버 IP 또는 ECS 일부 정보를 보고 지역별 응답을 고른다.
- 1. 사용자
사용자 서울에서 example.com을 질의한다.
- 2. 재귀 리졸버
재귀 리졸버 질의를 모아 권한 DNS에 전달한다.
- 3. 권한 DNS
권한 DNS 리졸버 위치나 ECS를 보고 리전을 선택한다.
- 4. 응답
응답 서울, 도쿄, 버지니아 중 하나의 IP를 반환한다.
주의할 점은 권한 DNS 서버가 보통 최종 사용자의 IP가 아니라 재귀 리졸버의 IP를 본다는 것입니다.
EDNS Client Subnet(ECS)을 쓰면 일부 클라이언트 네트워크 정보가 권한 서버에 전달될 수 있지만, 성능과 프라이버시의 절충이 생기며 모든 리졸버가 같은 방식으로 지원하는 것도 아닙니다.
DNS 라우팅 정책과 장애 조치
클라우드 DNS와 CDN은 단순 응답 외에도 가중치, 지연 시간, 지리 위치, 헬스 체크 기반 장애 조치 같은 정책을 제공합니다.
이 정책들은 DNS 응답을 바꾸는 방식이므로 TTL과 캐시의 영향을 항상 받습니다.
가중치, 지연 시간, 지역, 장애 조치는 권한 DNS의 응답 선택 규칙이다. 이미 배포된 값은 TTL 동안 캐시에 남아 전환이 늦게 보일 수 있다.
- 질의
사용자는 보통 권한 DNS를 직접 묻지 않고 재귀 리졸버를 거친다.
- 정책 엔진
비율, 지연 시간, 지역, 헬스 체크를 기준으로 후보 IP를 고른다.
- 응답 캐시
선택된 답은 TTL 동안 리졸버와 클라이언트에 남는다.
| 정책 | 주로 쓰는 목적 | 주의할 점 |
|---|---|---|
| Weighted | 점진 이전, A/B 테스트 | 리졸버 캐시 때문에 순간 비율은 다를 수 있다. |
| Latency / Geo | 가까운 리전이나 지역별 엔드포인트 선택 | 사용자 위치가 아니라 리졸버 위치 기준일 수 있다. |
| Failover | 주 리전 장애 시 백업 응답 | 이미 받은 IP는 TTL 만료 전까지 남는다. |
| Multi-value | 여러 건강한 IP 반환 | 정교한 부하 분산은 L4/L7 로드밸런서가 더 적합하다. |
| DNS 라우팅 정책 | 판단 기준 | 적합한 사용 사례 | 주의할 점 |
|---|---|---|---|
| 단순(Simple) | 고정 레코드 | 단일 서버, 내부 도메인 | 장애 감지 없음 |
| 라운드 로빈 | 여러 값의 순서나 목록 | 가벼운 분산, 실습 환경 | 정확한 비율 보장 아님 |
| 가중치(Weighted) | 설정한 비율 | 점진적 이전, A/B 테스트 | 캐시 때문에 순간 비율은 흔들림 |
| 지리적(Geolocation) | 리졸버/ECS 기반 위치 | 지역별 콘텐츠, 규제 분리 | 최종 사용자 위치와 다를 수 있음 |
| 지연 시간(Latency) | 측정된 리전 지연 시간 | 글로벌 API, CDN | 실시간 네트워크 상태와 차이 가능 |
| 장애 조치(Failover) | 헬스 체크 결과 | 주/백업 리전 | TTL 동안 이전 응답이 남을 수 있음 |
| 다중 값(Multi-value) | 헬스 체크를 통과한 여러 값 | DNS 수준의 단순 고가용성 | 로드밸런서 대체재로 과신 금지 |
DNS over HTTPS와 DNS over TLS
전통적인 DNS는 보통 53번 포트의 UDP 또는 TCP로 질의와 응답을 주고받습니다.
이 구간이 평문이면 같은 네트워크의 관찰자는 어떤 이름을 질의했는지 볼 수 있고, 공격자는 응답을 위조하려고 시도할 수 있습니다.
암호화 DNS는 질의 내용을 중간망에서 읽기 어렵게 만든다. 다만 최종 접속 IP나 계정 로그인, 애플리케이션 트래픽까지 숨기는 기술은 아니다.
- 기본 DNS
UDP/TCP 53. 네트워크 관찰자가 질의 이름을 볼 수 있다.
- DoT
TLS 위 DNS, 전용 포트 853. 운영 정책이 명확하다.
- DoH
HTTPS 위 DNS, 보통 443. 웹 트래픽과 같이 흐르지만 앱별 정책 분리가 필요하다.
- 1 사용자 기기
사용자 기기 DNS 메시지를 만든 뒤 선택한 리졸버로 보낸다.
- 2 암호화 터널
암호화 터널 TLS 또는 HTTPS가 질의 이름을 중간망에서 가린다.
- 3 리졸버 처리
리졸버 처리 리졸버는 복호화 후 일반 DNS 해석을 수행한다.
| 숨겨지는 것 | 남는 메타데이터 | 운영 판단 |
|---|---|---|
| DNS 질의 이름과 응답 내용 | 리졸버 IP, 접속 시각, 최종 목적지 IP | 프라이버시와 통제 정책을 함께 봐야 함 |
| 중간망의 DNS 변조 | 신뢰할 리졸버 선택 문제 | 기업망은 허용 리졸버와 로그 정책을 정해야 함 |
DNS over TLS(DoT)는 DNS 메시지를 TLS 연결 위에 실어 보내며 기본 포트는 853입니다.
DNS over HTTPS(DoH)는 DNS 질의를 HTTPS 요청으로 보냅니다.
둘 다 클라이언트와 선택한 리졸버 사이의 DNS 질의를 암호화하지만, 리졸버 운영자는 여전히 질의를 볼 수 있고, 목적지 IP나 TLS SNI 같은 다른 신호가 항상 사라지는 것은 아닙니다.
| 프로토콜 | 일반 포트 | 전송 방식 | 보호하는 구간 | 운영상 특징 |
|---|---|---|---|---|
| 전통 DNS | 53 | UDP/TCP | 기본적으로 암호화 없음 | 단순하고 널리 지원되지만 관찰 쉬움 |
| DoT | 853 | DNS over TLS | 클라이언트 ↔ 리졸버 DNS 메시지 | DNS 전용 포트라 정책 적용이 비교적 쉬움 |
| DoH | 443 | DNS over HTTPS | 클라이언트 ↔ DoH 서버 HTTP 교환 | HTTPS와 같은 포트를 써서 앱별 설정이 쉬움 |
# 현재 시스템의 DNS 서버 확인
# Windows
ipconfig /all | findstr "DNS Servers"
# Linux/macOS
cat /etc/resolv.conf
# Cloudflare의 JSON API 예시입니다. RFC 8484의 application/dns-message 형식과는 다릅니다.
curl -s "https://cloudflare-dns.com/dns-query?name=example.com&type=A" \
-H "Accept: application/dns-json"DNS 캐시 포이즈닝과 DNSSEC
DNS 캐시 포이즈닝은 재귀 리졸버가 권한 서버의 진짜 응답보다 먼저 도착한 위조 응답을 믿고 캐시에 저장하게 만드는 공격입니다.
현대 리졸버는 트랜잭션 ID, 질의 이름, 질의 타입, 출발지 포트 무작위화, 응답 출처 확인 같은 조건으로 위조 난도를 높입니다.
공격자는 리졸버가 권한 서버의 진짜 응답을 받기 전에, 질의와 맞아 보이는 위조 응답을 먼저 도착시키려 한다.
- 1 리졸버 질의
리졸버 질의 TXID + qname + qtype source port 권한 서버로 질의를 보낸다.
- 2 공격자 경쟁
공격자 경쟁 spoofed answer TXID, 포트, 이름, 타입을 맞춘 위조 응답을 밀어 넣는다.
- 3 캐시 오염
캐시 오염 victim.example → fake IP 성공하면 TTL 동안 잘못된 IP가 재사용된다.
| 공격자가 노리는 것 | 방어가 줄이는 위험 |
|---|---|
| 응답을 먼저 도착시키기 | 랜덤 포트와 TXID로 추측 난이도를 높인다. |
| 가짜 권한 정보를 섞기 | bailiwick 검사가 권한 밖 데이터를 막는다. |
| 데이터 변조 숨기기 | DNSSEC 검증이 서명 불일치를 잡는다. |
DNSSEC(DNS Security Extensions)는 DNS 응답에 서명을 붙여 데이터 출처와 무결성을 검증하는 확장입니다.
리졸버는 루트부터 TLD, 도메인 영역까지 이어지는 신뢰 체인을 따라 DS, DNSKEY, RRSIG 같은 레코드를 확인합니다.
DNSSEC은 DNS 데이터를 암호화하지 않는다. 대신 DS, DNSKEY, RRSIG로 “이 응답이 해당 영역의 키로 서명됐는지” 확인한다.
- Root
Trust Anchor 검증 리졸버가 신뢰하는 시작 키다.
- TLD
DS 상위 영역이 하위 영역 키 해시를 가리킨다.
- Zone
DNSKEY 영역의 공개키로 서명 검증을 준비한다.
- Answer
RRSIG + RRset 응답 레코드 묶음이 바뀌지 않았는지 확인한다.
| 필요 조건 | 실패 시 증상 |
|---|---|
| 부모 DS와 자식 DNSKEY가 일치 | SERVFAIL로 보일 수 있다. |
| RRSIG 유효 기간과 키 롤오버 관리 | 정상 레코드도 검증 실패가 된다. |
# DNSSEC 관련 레코드와 응답 확인
dig +dnssec example.com
# 검증 리졸버가 Authenticated Data 플래그를 세웠는지 확인
dig +dnssec +adflag example.com | grep "flags"
# DNSKEY와 DS 확인
dig DNSKEY example.com +short
dig DS example.com +shortDNSSEC은 무결성과 출처 인증을 제공하지만 기밀성은 제공하지 않습니다.
즉, 응답이 변조되지 않았는지 검증하는 기술이지, 어떤 도메인을 질의했는지 숨기는 기술은 아닙니다.
질의 프라이버시까지 고려하려면 DoT나 DoH 같은 암호화 DNS 전송과 함께 봐야 합니다.
DNS 실무 문제 해결
DNS 장애는 “사이트가 안 열린다”로 보이지만 원인은 다양합니다.
로컬 캐시, 리졸버 장애, 권한 DNS 설정 오류, DNSSEC 검증 실패, 도메인 만료, 방화벽, 실제 서버 장애를 단계별로 분리해야 합니다.
ping 하나로 DNS 문제를 확정하기는 어렵다. 같은 이름을 여러 위치에서 조회해 어느 계층에서 다른 답이 나오는지 좁힌다.
- 1 로컬
컬 기기 캐시 배제 hosts OS cache browser cache
- 2 리졸버
리졸버 공용 DNS와 비교 dig @1.1.1.1 dig @8.8.8.8
- 3 권한
권한 원본 상태 조회 dig +trace dig @ns1 zone
- 4 서비스
서비스 DNS 밖 문제 분리 curl -v tls / http status
| 문제 | 흔한 증상 | 확인할 것 | 대응 방향 |
|---|---|---|---|
| 캐시 오래됨 | 서버 이전 후 옛 IP로 접속 | TTL, OS/브라우저/리졸버 캐시 | TTL 사전 조정, 캐시 플러시 |
| NXDOMAIN | 도메인 없음 | 등록 만료, 오타, 위임 누락 | 도메인/존/NS 설정 확인 |
| SERVFAIL | 해석 실패 | 권한 서버 장애, DNSSEC 검증 실패 | 권한 서버와 DS/DNSKEY 체인 확인 |
| 느린 DNS | 첫 접속 지연 | 리졸버 응답 시간, 네트워크 경로 | 다른 리졸버 비교, 로컬 캐시 확인 |
| 잘못된 IP | 엉뚱한 서버로 접속 | 레코드 오타, 오래된 캐시, 하이재킹 | 권한 서버 직접 조회, 보안 점검 |
import socket
import time
def dns_health_check(domains):
"""여러 도메인의 기본 DNS 해석 상태 확인"""
results = []
for domain in domains:
start = time.time()
try:
ip = socket.gethostbyname(domain)
elapsed = (time.time() - start) * 1000
results.append((domain, ip, f"{elapsed:.1f}ms", "OK"))
except socket.gaierror as e:
elapsed = (time.time() - start) * 1000
results.append((domain, "-", f"{elapsed:.1f}ms", str(e)))
print(f"{'도메인':<25} {'IP':<18} {'시간':<10} {'상태'}")
print("-" * 70)
for domain, ip, time_str, status in results:
print(f"{domain:<25} {ip:<18} {time_str:<10} {status}")
domains = [
"google.com",
"github.com",
"naver.com",
"example.com",
"nonexistent-domain-test.xyz",
]
dns_health_check(domains)다음 장에서는 웹 개발자가 매일 사용하지만 깊이 이해하는 경우가 드문 프로토콜, HTTP를 자세히 살펴보겠습니다.