DNS 심화와 실무
라운드 로빈·GeoDNS·장애 조치와 DoH·DoT·DNSSEC의 역할을 이해하고 캐시와 보안 문제를 진단합니다.
DNS의 기본 동작과 레코드를 이해했으니, 이제 실무에서 자주 마주치는 라우팅, 프라이버시, 보안, 장애 진단을 살펴보겠습니다.
DNS는 단순한 “도메인 → IP 변환기”가 아니라 분산 데이터베이스, 캐시, 정책 기반 응답, 보안 검증이 함께 움직이는 인프라입니다.
DNS 라운드 로빈
하나의 이름에 여러 개의 A/AAAA 레코드를 두면 응답에 여러 IP가 포함될 수 있고, 권한 DNS 서버나 리졸버는 응답 순서를 바꾸어 줄 수 있습니다.
이를 흔히 DNS 라운드 로빈이라고 부릅니다.
다만 DNS 라운드 로빈은 L4/L7 로드밸런서처럼 연결 단위로 정교하게 분산하지 않습니다.
리졸버, 운영체제, 브라우저가 응답을 캐시할 수 있고, 클라이언트가 응답 목록 중 어떤 주소를 선택하는지도 구현에 따라 달라집니다.
또한 단순 라운드 로빈만으로는 서버 장애를 자동 감지하지 못하므로, 실제 운영에서는 헬스 체크, 짧은 TTL, 로드밸런서, Anycast, CDN과 함께 설계합니다.
같은 목록이어도 연결 수는 같지 않다 응답에 A 레코드 주소가 두 개 있다고 해서 다음 100개의 HTTP 요청이 50개씩 나뉘지는 않습니다. 한 클라이언트가 한 주소를 선택한 뒤 기존 연결을 계속 재사용할 수 있고, 다른 클라이언트는 같은 캐시 목록에서 다른 주소를 고를 수 있습니다. DNS의 후보 목록과 실제 요청을 처리한 서버를 각각 관찰해야 합니다.
GeoDNS와 글로벌 서비스 라우팅
GeoDNS는 질의 위치나 네트워크 정보를 기준으로 다른 응답을 돌려주는 DNS 운영 방식입니다.
예를 들어 한국 사용자는 서울 리전, 미국 사용자는 버지니아 리전의 IP를 받도록 할 수 있습니다.
주의할 점은 권한 DNS 서버가 보통 최종 사용자의 IP가 아니라 재귀 리졸버의 IP를 본다는 것입니다.
EDNS Client Subnet(ECS)을 쓰면 일부 클라이언트 네트워크 정보가 권한 서버에 전달될 수 있지만, 성능과 프라이버시의 절충이 생기며 모든 리졸버가 같은 방식으로 지원하는 것도 아닙니다.
DNS 라우팅 정책과 장애 조치
클라우드 DNS와 CDN은 단순 응답 외에도 가중치, 지연 시간, 지리 위치, 헬스 체크 기반 장애 조치 같은 정책을 제공합니다.
이 정책들은 DNS 응답을 바꾸는 방식이므로 TTL과 캐시의 영향을 항상 받습니다.
| 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 over TLS(DoT)는 DNS 메시지를 TLS 연결 위에 실어 보내며 기본 포트는 853입니다.
DNS over HTTPS(DoH)는 DNS 질의를 HTTPS 요청으로 보냅니다.
둘 다 클라이언트와 선택한 리졸버 사이의 DNS 질의를 암호화하지만, 리졸버 운영자는 여전히 질의를 볼 수 있고, 목적지 IP나 TLS SNI 같은 다른 신호가 항상 사라지는 것은 아닙니다.
# 현재 시스템의 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"DoH를 쓸 때는 HTTP 성공과 DNS 해석 성공도 따로 읽습니다. RFC8484의 DNS 메시지 형식에서는 HTTP 200 응답에 NXDOMAIN이나 SERVFAIL인 DNS 메시지가 들어갈 수 있습니다. HTTPS 교환이 성공했어도 원하는 이름의 주소를 얻었다는 뜻은 아닙니다. 위의 제공자 JSON API는 별도 표현 형식이므로 그 API의 응답 필드를 확인해야 합니다. RFC8484의 HTTP·DNS 오류 구분에 이 경계가 나옵니다.
DNS 캐시 포이즈닝과 DNSSEC
DNS 캐시 포이즈닝은 재귀 리졸버가 권한 서버의 진짜 응답보다 먼저 도착한 위조 응답을 믿고 캐시에 저장하게 만드는 공격입니다.
현대 리졸버는 트랜잭션 ID, 질의 이름, 질의 타입, 출발지 포트 무작위화, 응답 출처 확인 같은 조건으로 위조 난도를 높입니다.
DNSSEC(DNS Security Extensions)는 DNS 응답에 서명을 붙여 데이터 출처와 무결성을 검증하는 확장입니다.
리졸버는 루트부터 TLD, 도메인 영역까지 이어지는 신뢰 체인을 따라 DS, DNSKEY, 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 +shortDNS · 서로 다른 보호
전송 암호화는 구간을, DNSSEC은 데이터의 출처와 무결성을 확인한다
DoH·DoT는 클라이언트와 선택한 리졸버 사이를 암호화하지만 그 리졸버는 질의를 읽습니다. DNSSEC은 신뢰 체인에 따른 서명 검증이며 질의 내용을 숨기는 기능은 아닙니다.
위 영역의 실선은 질의와 응답의 교환이고, 아래 점선은 검증 입력입니다. 서명된 영역을 검증하는 예시이며 서명되지 않은 영역을 모두 위조로 분류하지 않습니다. 기존 표에서 각 방식의 남는 경계를 함께 읽습니다.
| 방식 | 처리하는 범위 | 남는 경계 |
|---|---|---|
| 전통 DNS | 전송 방식: UDP/TCP 53의 DNS 교환 | 기본 DNS에는 전송 암호화 없음 |
| DoT | 보통 TCP 853, TLS로 연결 구간 보호 | 선택한 리졸버는 내용을 읽음; 다른 조회 구간은 별도 |
| DoH | 보통 HTTPS 443의 DNS 교환 | DoH 서버는 내용을 읽음; 앱별 리졸버 정책 확인 |
| DNSSEC | 신뢰 앵커에서 검증한 RRset의 출처·무결성 | 서명은 질의 기밀성을 제공하지 않음; 신뢰 체인 필요 |
- 전통 DNS
- 처리하는 범위: 전송 방식: UDP/TCP 53의 DNS 교환남는 경계: 기본 DNS에는 전송 암호화 없음
- DoT
- 처리하는 범위: 보통 TCP 853, TLS로 연결 구간 보호남는 경계: 선택한 리졸버는 내용을 읽음; 다른 조회 구간은 별도
- DoH
- 처리하는 범위: 보통 HTTPS 443의 DNS 교환남는 경계: DoH 서버는 내용을 읽음; 앱별 리졸버 정책 확인
- DNSSEC
- 처리하는 범위: 신뢰 앵커에서 검증한 RRset의 출처·무결성남는 경계: 서명은 질의 기밀성을 제공하지 않음; 신뢰 체인 필요
전송 암호화와 DNSSEC 검증은 서로 다른 보호를 제공하므로 함께 사용할 수 있습니다.
dig +dnssec는 DNSSEC 데이터를 요청하는 옵션이며, 이 명령 자체가 서명을 검증한 것은 아닙니다. AD 플래그를 신뢰하려면 검증 리졸버와 그 응답을 받은 경로도 신뢰할 수 있어야 합니다.
보호 기능을 나누어 판단하기 DNSSEC 검증을 수행하는 리졸버와 서버 인증을 확인한 DoH 연결을 사용하는 학습 모델입니다. 서명된 영역의 응답이 신뢰 체인 검증에 실패했다면, 클라이언트→리졸버 HTTPS가 정상이라는 이유만으로 DNS 데이터를 검증 성공으로 바꿀 수 있을까요? 반대로 DNSSEC 검증에 성공한 데이터를 평문 DNS로 받았다면 같은 네트워크 관찰자로부터 질의 이름까지 숨겨졌다고 말할 수 있을까요?
암호화 구간과 데이터 검증의 풀이
첫 경우에는 서버까지의 HTTPS 구간 보호와 DNSSEC 데이터 검증 결과가 다릅니다. 기대한 서명·신뢰 체인을 검증하지 못한 데이터를 전송 성공만으로 인증된 데이터로 취급할 수 없습니다. 두 번째 경우도 DNSSEC 서명은 데이터의 출처와 무결성 검증을 제공할 뿐 평문 질의를 암호화하지 않습니다. 따라서 질의 이름이 숨겨졌다고 결론 내릴 수 없습니다. DoH·DoT를 사용해도 선택한 리졸버는 질의를 읽고, 그 뒤 권한 서버로 나가는 구간의 전송 보호는 별도 설정입니다. 서명되지 않은 영역을 처음부터 모두 위조된 응답으로 분류하는 것도 아닙니다. RFC4033의 검증 상태와 제공하지 않는 보호를 구분하세요.
DNS 실무 문제 해결
DNS 장애는 “사이트가 안 열린다”로 보이지만 원인은 다양합니다.
로컬 캐시, 리졸버 장애, 권한 DNS 설정 오류, DNSSEC 검증 실패, 도메인 만료, 방화벽, 실제 서버 장애를 단계별로 분리해야 합니다.
| 문제 | 증상과 확인 |
|---|---|
| 캐시 오래됨 | 흔한 증상: 서버 이전 후 옛 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)이 Python 예제는 OS 리졸버를 통한 IPv4 조회 성공 여부와 호출 경과 시간을 보여줍니다. 캐시 시간을 포함할 수 있으며 권한 서버 지연이나 웹 서비스의 정상 동작을 따로 측정하지 않습니다.
이 코드로 판단할 수 없는 것
원래 dns_health_check.py의 OK는 IPv4 주소 문자열을 받았다는 뜻입니다. IPv6 전용 이름이라면 사용할 수 있는 AAAA가 있어도 gethostbyname이 실패할 수 있습니다. 또한 주소를 받았지만 그 서버의 443번 포트가 닫혀 있을 수도 있습니다. 둘을 각각 “DNS 전체 장애”, “웹 서비스 정상”으로 자동 분류하지 않습니다. 경과 시간에는 OS·캐시·이름 해석 호출 전체가 들어가며, time.time()은 경과 시간 측정에 적합한 단조 시계라는 보장도 없습니다. 정확한 소요 시간 계측을 확장한다면 time.perf_counter() 같은 단조 시계를 선택하고, 원 코드의 출력은 간단한 관찰값으로 읽습니다.
다음 장에서는 웹 개발자가 매일 사용하지만 깊이 이해하는 경우가 드문 프로토콜, HTTP를 자세히 살펴보겠습니다.