목록으로 돌아가기
Network
7분 읽기

DNS 동작 원리 완전 정리 — Anycast·CNAME flattening·DoH 실전 가이드

DNS는 단순 이름-IP 변환기가 아니라 전 세계에 분산된 계층형 key-value 데이터베이스다. 재귀 질의·캐시·Anycast·DoH·CNAME flattening 등 실전 디버깅에 필요한 개념을 정리한다.

ㅂㄹㄱ

2026-04-06 ·

들어가며

DNS를 "도메인 사면 A 레코드 하나 찍고 끝"으로 알고 살아도 평소에는 큰 문제가 없다. dig는 장애가 났을 때나 한 번씩 치는 명령어이고, ping이 안 가면 "DNS가 이상한가?" 정도로만 의심하면 그만이다.

Cloudflare로 사이드 프로젝트를 옮기면서 그 가정이 깨졌다. DNS를 제대로 이해하지 않으면 아무것도 디버깅할 수 없다는 걸 깨달았다. Proxy 모드, CNAME flattening, ANAME, DNS-over-HTTPS, Anycast까지. 대충 넘어갔던 개념들을 다시 뜯어봐야 했다.

이 글은 그 과정에서 정리한 DNS의 동작 원리다.


DNS는 분산 데이터베이스다

DNS를 "이름 → IP 변환기"로 설명하는 글이 많지만, 본질은 전 세계에 분산된 계층형 key-value 데이터베이스다. 키는 도메인 이름, 값은 레코드(A, AAAA, MX, TXT 등)다.

계층 구조는 이렇게 생겼다.

graph TD
    Root["<b>.</b><br/>Root"] --> TLD["<b>com</b><br/>TLD"]
    TLD --> SLD["<b>hyochan</b><br/>SLD"]
    SLD --> Sub["<b>blog</b><br/>Subdomain"]
Diagram: graph TD Root["<b>.</b><br/>Root"] --> TLD["<b>com</b><br/>TLD"] TLD --> SLD["<b>hyochan</b><br/>SLD"] SLD --> Sub["<b>blog</b><br/>Subdomain"]

오른쪽에서 왼쪽으로 읽는다. blog.hyochan.site. 끝에 붙는 점 하나가 루트다. 평소에는 생략돼 있을 뿐이다.

각 계층은 자기 아래 계층을 누가 관리하는지만 알려주는 역할을 한다. 루트는 .com을 누가 관리하는지 안다. .comhyochan.site를 누가 관리하는지 안다. 그 끝에 실제 레코드가 있다.


재귀 질의: 한 번의 curl 뒤에서 벌어지는 일

브라우저에 blog.hyochan.site를 입력하면 뭐가 일어나는가. 단순화하면 이렇다.

sequenceDiagram
    participant C as Client
    participant R as Recursive<br/>Resolver<br/>(1.1.1.1)
    participant Root as Root Server
    participant TLD as .com TLD
    participant NS as Authoritative<br/>NS<br/>(Cloudflare)

    C->>R: 1. blog.hyochan.site A?
    R->>Root: 2. 루트에 질의
    Root-->>R: "com은 a.gtld-servers.net"
    R->>TLD: 3. .com에 질의
    TLD-->>R: "hyochan.site는 ns1.cloudflare.com"
    R->>NS: 4. Cloudflare NS에 질의
    NS-->>R: A = 104.21.x.x
    R-->>C: 104.21.x.x
Diagram: sequenceDiagram participant C as Client participant R as Recursive<br/>Resolver<br/>(1.1.1.1) participant Root as Root Server participant TLD as .com TLD participant NS as Authoritative<br/>NS<br/>(Cloudflare) C->>R: 1. blog.hyochan.site A? R->>Root: 2. 루트에 질의 Root-->>R: "com은 a.gtld-servers.net" R->>TLD: 3. .com에 질의 TLD-->>R: "hyochan.site는 ns1.cloudflare.com" R->>NS: 4. Cloudflare NS에 질의 NS-->>R: A = 104.21.x.x R-->>C: 104.21.x.x

핵심 배역은 두 명이다.

역할하는 일
Recursive Resolver클라이언트 대신 루트부터 끝까지 물어보고 다니는 심부름꾼. 1.1.1.1, 8.8.8.8이 대표적
Authoritative NS"이 도메인의 진짜 답은 내가 갖고 있다"고 말하는 서버. 도메인 등록자가 지정한다

클라이언트는 1번 요청 한 번만 한다. 나머지 2~4번은 Resolver가 혼자 돌아다니면서 처리한다. "재귀(recursive)"라는 이름이 붙은 이유다.


캐시: DNS가 빠른 이유이자 느린 이유

모든 질의가 매번 루트부터 시작하면 루트 서버는 즉시 터진다. 그래서 DNS는 모든 계층에서 캐시한다.

graph TD
    A[Browser cache] --> B[Client OS<br/>systemd-resolved, mDNSResponder]
    B --> C[Recursive Resolver<br/>ISP, 1.1.1.1]
    C --> D["Authoritative NS<br/><i>여기서만 '진실'이 나온다</i>"]
Diagram: graph TD A[Browser cache] --> B[Client OS<br/>systemd-resolved, mDNSResponder] B --> C[Recursive Resolver<br/>ISP, 1.1.1.1] C --> D["Authoritative NS<br/><i>여기서만 '진실'이 나온다</i>"]

각 응답에는 TTL(Time To Live)이 붙는다. "이 값은 N초 동안 믿어도 된다"는 뜻이다. 짧으면 변경 반영이 빠르고, 길면 트래픽이 적다.

도메인 이전 작업할 때 "DNS 전파에 최대 48시간 걸릴 수 있다"는 문구를 본 적 있을 거다. 진짜 전파가 느려서가 아니다. 어딘가의 캐시가 아직 TTL을 다 쓰지 못했을 뿐이다. Cloudflare 같은 곳은 TTL을 5분으로 짧게 잡아두기 때문에 실질적으로는 훨씬 빠르다.


레코드 타입: A, AAAA, CNAME, 그리고 그 함정들

자주 쓰는 레코드만 추리면 이렇다.

타입용도
AIPv4 주소blog.hyochan.site → 104.21.1.2
AAAAIPv6 주소blog.hyochan.site → 2606:4700::...
CNAME다른 도메인www.example.com → example.com
MX메일 서버 도메인메일 라우팅
TXT임의 문자열SPF, DKIM, 도메인 소유 증명
NS네임서버"이 존은 저 NS가 관리함"
SOA존 메타데이터시리얼 번호, TTL 기본값

CNAME에는 유명한 함정이 하나 있다. 루트 도메인(apex)에는 CNAME을 붙일 수 없다. RFC 1034가 그렇게 정했다. example.com 자체에 CNAME을 박으면 안 되고, www.example.com 같은 서브도메인만 가능하다.

그럼 루트 도메인을 Netlify나 Vercel 같은 호스팅에 연결하고 싶을 땐? 고정 IP가 없는데? 여기서 ANAME / ALIAS / CNAME flattening 같은 꼼수가 등장한다.

  • CNAME flattening (Cloudflare): 루트에 CNAME을 걸어두면 Cloudflare가 응답 시점에 실제 A 레코드로 풀어서 내려준다. 클라이언트 입장에서는 그냥 A 레코드로 보인다.
  • ANAME / ALIAS: DNS 공급자가 내부적으로 비슷한 일을 한다.

겉보기엔 CNAME이지만, 내려갈 땐 A다.


dig로 직접 들여다보기

개념만 읽으면 와닿지 않는다. 직접 쳐보는 게 빠르다.

# 기본 A 레코드 조회
dig blog.hyochan.site

# 특정 resolver 지정 (Cloudflare)
dig @1.1.1.1 blog.hyochan.site

# 재귀 질의 과정 추적
dig +trace blog.hyochan.site

# 특정 레코드 타입만
dig blog.hyochan.site MX
dig blog.hyochan.site TXT

# 짧게 답만 보기
dig +short blog.hyochan.site

+trace가 특히 교육적이다. 루트부터 TLD, authoritative까지 내려가는 과정을 그대로 출력해준다. 위에서 그림으로 그린 단계가 실제로 찍힌다.

응답에는 네 개의 섹션이 있다.

;; QUESTION SECTION:     ← 내가 뭘 물었는지
;; ANSWER SECTION:       ← 진짜 답 (A, AAAA 등)
;; AUTHORITY SECTION:    ← 이 답의 권위자 NS
;; ADDITIONAL SECTION:   ← 덤으로 딸려오는 정보 (NS의 IP 등)

처음엔 ANSWER만 봐도 충분하지만, 디버깅할 땐 나머지 섹션이 결정적일 때가 많다.


Anycast: 하나의 IP, 수백 개의 서버

1.1.1.1을 한국에서 ping 하면 10ms 안에 돌아온다. 미국에서 ping 해도 10ms 안에 돌아온다. 같은 IP인데 어떻게?

답은 Anycast다. 하나의 IP를 전 세계 여러 데이터센터가 동시에 BGP로 광고한다. 클라이언트의 패킷은 라우팅상 가장 가까운 곳으로 간다. IP는 하나지만 물리적 서버는 수백 개인 셈이다.

graph TD
    IP["blog.hyochan.site<br/>→ 104.21.1.2 (Anycast)"]
    IP --> Seoul[Seoul PoP]
    IP --> Tokyo[Tokyo PoP]
    IP --> Frankfurt[Frankfurt PoP]
    KR[한국 사용자] --> Seoul
    JP[일본 사용자] --> Tokyo
    DE[독일 사용자] --> Frankfurt
Diagram: graph TD IP["blog.hyochan.site<br/>→ 104.21.1.2 (Anycast)"] IP --> Seoul[Seoul PoP] IP --> Tokyo[Tokyo PoP] IP --> Frankfurt[Frankfurt PoP] KR[한국 사용자] --> Seoul JP[일본 사용자] --> Tokyo DE[독일 사용자] --> Frankfurt

Cloudflare, Google DNS, AWS Route 53 같은 대형 DNS 서비스가 다 이 방식이다. 덕분에 재귀 질의도, 권위 응답도 지리적으로 가까운 곳에서 받을 수 있다.


DoH / DoT: DNS도 암호화해야 한다

전통적 DNS는 UDP 53 평문이다. 같은 네트워크에 있는 누구나 내가 어떤 사이트를 조회하는지 볼 수 있고, 중간에서 응답을 위조할 수도 있다(DNS spoofing).

이걸 해결하려고 나온 게 둘이다.

프로토콜포트특징
DoT (DNS over TLS)853TLS로 감싼 DNS. 포트로 식별 가능
DoH (DNS over HTTPS)443HTTPS 트래픽에 섞여 들어감. 차단하기 어려움

브라우저들은 이미 DoH를 기본으로 켜고 있다. Firefox는 Cloudflare, Chrome은 OS 설정의 resolver가 DoH를 지원하면 자동 업그레이드한다.

개발자 입장에서 중요한 건, 회사나 ISP가 가로채던 DNS 기반 차단/모니터링이 더 이상 안 먹힌다는 점이다. 사내 DNS 필터를 운영한다면 정책을 다시 짜야 한다.


흔한 삽질 기록

1. "왜 내 변경이 반영 안 되죠?" — TTL과 네거티브 캐시

레코드를 바꿨는데 반영이 안 된다면 보통은 캐시다. 특히 NXDOMAIN(존재하지 않음) 응답도 캐시된다(네거티브 캐시). dig +noall +answer 여러 번 찍으며 TTL 카운트다운을 관찰하면 금방 드러난다.

팁: 변경 예정일 전날에 TTL을 60초 같이 짧게 내려두고, 변경한 뒤 다시 올리는 게 정석이다.

2. Cloudflare Proxy 모드와 dig의 불일치

Cloudflare 대시보드에서 오렌지 구름(Proxy) 켜두면, dig로 보이는 IP는 실제 서버 IP가 아니라 Cloudflare의 Anycast IP다. "DNS가 잘못됐다"고 착각하기 쉬운데, 의도된 동작이다.

실제 origin IP를 확인하려면 Cloudflare 대시보드의 DNS 탭을 봐야 한다. 외부에 노출되면 DDoS 보호가 의미 없어지니 조심.

3. MX 레코드에 IP를 쓰려는 시도

MX 레코드의 값은 도메인이어야 한다. IP를 직접 넣으면 안 된다. 메일 서버용 A 레코드를 따로 만들고, MX는 그 도메인을 가리키게 한다.

mail.hyochan.site.    IN A   192.0.2.1
hyochan.site.         IN MX  10 mail.hyochan.site.

앞의 10은 우선순위다. 숫자가 작을수록 우선이다.


정리

개념한 줄 요약
재귀 질의Resolver가 루트부터 authoritative까지 대신 물어본다
캐시 & TTL모든 계층에서 캐시. TTL이 전파 속도를 좌우한다
A / AAAA / CNAMEIPv4 / IPv6 / 도메인 별칭. 루트엔 CNAME 못 건다
CNAME flattening루트에 CNAME을 걸어도 A로 풀어서 응답
Anycast하나의 IP를 전 세계 여러 PoP가 공유
DoT / DoHDNS 암호화. DoH는 HTTPS에 섞여 차단 회피

DNS는 "도메인 → IP"로 요약하기엔 아까운 시스템이다. 인터넷 초창기에 설계된 것 치고는 놀랄 만큼 잘 버티고 있고, Anycast·DoH·CNAME flattening 같은 확장이 그 위에 쌓이고 있다.

디버깅할 때 dig +trace 한 번 쳐보는 습관만 들여도 인프라 문제의 절반은 빠르게 원인이 잡힌다. "DNS가 이상한가?" 정도의 막연한 의심에서 벗어나는 첫 걸음이다.


참고자료

자주 묻는 질문

apex 도메인에 CNAME을 걸 수 없는 이유는?
RFC 1034에서 도메인 apex에는 SOA·NS 외 다른 레코드와 CNAME을 공존시킬 수 없도록 규정했기 때문이다. 루트 도메인을 호스팅 서비스에 연결하려면 Cloudflare의 CNAME flattening이나 다른 DNS 공급자의 ANAME/ALIAS 같은 우회 메커니즘이 필요하다.
dig으로 보이는 IP와 실제 origin IP가 다른 이유는?
Cloudflare에서 Proxy(오렌지 구름)를 켜면 외부 dig 질의에는 Cloudflare Anycast IP가 응답되고 실제 origin IP는 숨겨진다. 의도된 동작이며 origin IP는 Cloudflare 대시보드에서만 확인할 수 있다. origin IP가 외부에 노출되면 DDoS 보호가 의미 없어진다.
DoT와 DoH는 뭐가 다른가?
DoT(DNS over TLS)는 포트 853을 써서 식별·차단이 가능하다. DoH(DNS over HTTPS)는 포트 443의 HTTPS 트래픽에 섞여 들어가 차단이 어렵다. 브라우저는 대부분 DoH를 기본값으로 쓰기 때문에 사내 DNS 필터링 정책을 다시 짜야 할 수 있다.
DNS 전파에 48시간이 걸린다는 말은 사실인가?
전파 자체는 빠르다. 48시간은 각 계층 캐시의 TTL이 만료되기까지 걸리는 최대치다. 변경 예정 전날 TTL을 60초로 낮춰두고 변경 후 다시 올리면 체감 전파 시간을 분 단위로 줄일 수 있다.
MX 레코드에 IP를 직접 넣을 수 있나?
안 된다. RFC 974에서 MX 레코드 값은 도메인 이름이어야 한다고 규정했다. 메일 서버용 A 레코드를 따로 만들고 MX 레코드는 그 도메인을 가리키게 한다. MX 앞에 붙는 숫자는 우선순위이며 작을수록 우선이다.
`dig +trace`는 언제 쓰는가?
재귀 resolver를 거치지 않고 루트부터 TLD, authoritative NS까지 단계별로 직접 질의하면서 응답을 출력해주는 옵션이다. 'DNS가 어디서부터 잘못됐나'를 추적해야 할 때 가장 빠른 진단 도구다. 캐시 오염이나 위임(delegation) 문제를 잡을 때 특히 유용하다.