Table of contents
- 이름이 없으면 아무도 못 찾아온다
- 도메인은 나무처럼 자란다
- 도메인 이름이 IP가 되기까지
- 재귀 질의 vs 반복 질의
- 자주 쓰는 DNS 레코드 타입
- TTL과 캐싱 — DNS의 심장
- dig 출력 읽기
- DNS 보안 — 믿을 수 있는가
- 실무에서 자주 만나는 DNS 이슈
- 시리즈를 마치며
이름이 없으면 아무도 못 찾아온다
2편에서 컴퓨터가 IP 주소로 서로를 찾는다고 했다. 그런데 사람이 93.184.216.34를 외워서 웹사이트에 들어가는 건 불가능하다. 기계는 숫자를, 사람은 이름을 쓴다. 그 번역자가 DNS(Domain Name System)다.
DNS는 단순히 “이름을 IP로 바꾸는 주소록” 이상이다. 전 세계에 분산된 거대한 계층형 데이터베이스이고, 초당 수조 건의 질의를 처리하며, 지연 시간이 밀리초 단위로 체감되는 시스템이다. DNS가 1초라도 삐끗하면 수많은 서비스가 동시에 보이지 않게 된다. 그래서 DNS는 인프라 운영에서 가장 많이 긁히는 상처 중 하나이기도 하다.
이번 편에서는 브라우저에 example.com을 입력했을 때 그 이름이 IP가 되기까지 무슨 일이 벌어지는지, 네임서버들 사이의 분업은 어떻게 이뤄지는지, 캐싱과 TTL이 왜 모든 DNS 이야기의 중심에 있는지를 본다.
도메인은 나무처럼 자란다
DNS의 이름은 점(.)으로 구분된 계층 구조다. 오른쪽으로 갈수록 상위 계층이다. www.example.com을 잘라보면 이렇다.
. ← 루트 (보통 생략됨)
com ← TLD (Top-Level Domain)
example.com ← 2차 도메인
www.example.com ← 서브도메인
엄밀히는 맨 끝에 점이 하나 더 있다(www.example.com.). 이 점이 루트를 가리킨다. 평소에는 생략되지만 DNS 설정 파일을 쓸 때 가끔 마주치게 된다. 이 트리 구조가 DNS 설계의 출발점이다.
flowchart TB
ROOT["."]
ROOT --> COM["com"]
ROOT --> KR["kr"]
ROOT --> ORG["org"]
COM --> EX["example.com"]
COM --> GOOG["google.com"]
KR --> COKR["co.kr"]
COKR --> NAV["naver.co.kr"]
EX --> WWW["www.example.com"]
EX --> API["api.example.com"]
이 트리의 각 계층마다 담당하는 네임서버가 따로 있다. 누구 하나가 모든 걸 알지 않는다. 각자는 자기 바로 아래 계층으로 “그건 저기 가서 물어보시오”라고 위임한다. 이 위임(delegation) 구조가 DNS의 확장성을 만든다.
도메인 이름이 IP가 되기까지
브라우저에 www.example.com을 치고 엔터를 누른 순간부터 연결이 시작되기까지, DNS가 하는 일을 하나씩 따라가 본다.
sequenceDiagram
participant U as 브라우저
participant R as Resolver<br/>(ISP/Cloudflare)
participant Root as 루트 네임서버
participant TLD as .com TLD<br/>네임서버
participant Auth as example.com<br/>권한 네임서버
U->>R: www.example.com의 A 레코드?
Note over R: 캐시에 있나? 없음.
R->>Root: www.example.com?
Root-->>R: .com은 저기 물어봐 (NS)
R->>TLD: www.example.com?
TLD-->>R: example.com은 저기 물어봐 (NS)
R->>Auth: www.example.com?
Auth-->>R: 93.184.216.34
R-->>U: 93.184.216.34
Note over U: TCP 연결 시작
각 단계를 풀어보자.
- 브라우저 → OS 리졸버(Stub Resolver): 브라우저는 OS에 “이 이름 좀 IP로 바꿔줘” 하고 부탁한다. 리눅스라면
getaddrinfo()같은 시스템 콜을 통한다 - OS → 재귀 리졸버(Recursive Resolver): OS는 자기가 설정된 리졸버(보통 라우터, ISP, 혹은
1.1.1.1,8.8.8.8같은 공개 리졸버)에 질의를 전달한다. 재귀 리졸버가 실제 추적 작업을 대행한다 - 리졸버 → 루트 네임서버: 리졸버가 루트에 “
.com을 누가 관리해?”라고 묻는다. 루트는.comTLD 네임서버의 주소를 알려준다. 실제 루트 서버는 전 세계 13개 주소(A~M)로 에니캐스트 운영된다 - 리졸버 → TLD 네임서버:
.com서버에 “example.com을 누가 관리해?”라고 묻는다. 답은example.com의 권한 네임서버(Authoritative Name Server) 주소다 - 리졸버 → 권한 네임서버: 이제
example.com을 관리하는 서버에 직접 물어본다. “www.example.com의 A 레코드는?” 그리고 최종 답(IP)을 받는다 - 리졸버 → OS → 브라우저: 답이 역순으로 전달되고, 리졸버는 이 결과를 캐시한다. 같은 질의가 또 오면 루트까지 안 가고 바로 답한다
이 과정 전체가 보통 수십 ms 안에 끝난다. 캐시에 있으면 1ms도 안 걸린다. 반대로 캐시가 비어 있고 모든 단계를 밟아야 하면 수백 ms가 걸리기도 한다.
재귀 질의 vs 반복 질의
DNS 질의는 두 가지 방식으로 동작한다. 이 구분을 분명히 해두면 위 시퀀스 다이어그램의 의미가 더 명확해진다.
재귀 질의(recursive query)는 “끝까지 알아내서 나한테 최종 답을 달라”는 요청이다. 브라우저나 OS가 리졸버에 보내는 질의가 이것이다. 리졸버는 답을 알 때까지 책임지고 추적한다.
반복 질의(iterative query)는 “네가 아는 만큼만 답해. 나머지는 내가 쫓아간다”는 요청이다. 리졸버가 루트/TLD/권한 서버에 던지는 질의가 모두 반복 질의다. 각 네임서버는 자기가 아는 한도 내에서 답하고, 모르면 “이쪽에 물어봐”만 알려준다.
flowchart LR
C["클라이언트"] -->|재귀| R["리졸버"]
R -->|반복| N1["루트"]
R -->|반복| N2["TLD"]
R -->|반복| N3["권한"]
왜 이렇게 나눴는가? 루트 네임서버가 모든 클라이언트의 재귀 질의를 처리하면 세상이 끝난다. 재귀는 리졸버가 대신해주고, 상위 네임서버는 부담 없이 “위임 정보”만 빠르게 답한다. 확장성과 부담 분산을 위한 설계다.
자주 쓰는 DNS 레코드 타입
DNS는 단순히 “이름 → IP”가 아니라, 다양한 종류의 정보를 저장한다. 각 정보에 레코드 타입이 붙는다. 핵심 타입들을 정리하면 이렇다.
| 타입 | 용도 | 예시 |
|---|---|---|
| A | 이름 → IPv4 주소 | example.com → 93.184.216.34 |
| AAAA | 이름 → IPv6 주소 | example.com → 2606:2800:220:1::e |
| CNAME | 이름 → 또 다른 이름(별칭) | www.example.com → example.com |
| MX | 메일 서버 지정 | example.com → mail.example.com (우선순위 10) |
| TXT | 임의 텍스트. SPF/DKIM/도메인 소유 증명 | example.com → "v=spf1 include:_spf.google.com ~all" |
| NS | 이 도메인의 권한 네임서버 | example.com → ns1.example.com |
| PTR | IP → 이름 (역방향 조회) | 93.184.216.34 → example.com |
| SRV | 서비스/포트 지정 | _xmpp._tcp.example.com |
| CAA | 이 도메인의 인증서 발급 권한 | example.com → "letsencrypt.org" |
CNAME은 특히 주의 깊게 봐야 한다. “이 이름은 사실 저 이름이다”라는 별칭이다. 예를 들어 www.example.com을 example.com의 CNAME으로 걸면, www.example.com을 조회한 리졸버는 다시 example.com을 조회해서 A 레코드를 얻는다. 이 연쇄 때문에 조회가 한 번 더 일어나고, CNAME을 너무 깊게 걸면 지연이 쌓인다.
CNAME에는 큰 제약이 있다. 같은 이름에 CNAME과 다른 레코드를 함께 둘 수 없다. 그래서 루트 도메인(example.com)에는 CNAME을 못 쓴다(루트엔 반드시 SOA와 NS가 있어야 하므로). 이 때문에 AWS는 ALIAS, Cloudflare는 CNAME Flattening이라는 사유 확장을 제공한다.
MX와 TXT는 메일과 엮여 있다. MX는 메일이 어디로 가야 하는지 알려주고, TXT는 발송 도메인의 정당성을 증명한다(SPF, DKIM, DMARC). 이 셋이 제대로 설정 안 되면 보낸 메일이 스팸함으로 들어간다. 자세한 건 Google Workspace 관리자 가이드 같은 공식 문서에 잘 나와 있다.
TTL과 캐싱 — DNS의 심장
DNS 레코드에는 반드시 TTL(Time To Live)이 붙는다. 초 단위 숫자이며 “이 답을 캐시에 얼마나 보관해도 되는지”를 의미한다. TTL이 300이면 5분, 3600이면 한 시간이다.
example.com. 300 IN A 93.184.216.34
↑
TTL (초)
캐싱은 DNS의 성능을 만든다. 한 번 조회된 이름은 TTL이 만료되기 전까지 리졸버가 대신 답한다. 루트와 TLD 서버가 세상을 감당할 수 있는 이유가 이 캐싱이다.
동시에 TTL은 변경 반영 속도를 결정한다. IP를 바꾸고 싶을 때 TTL이 86400(하루)이면 최악의 경우 하루 동안 옛 IP가 살아 있을 수 있다. 그래서 이사하기 전날에는 TTL을 짧게(60초, 300초) 줄여놓고, 이사가 끝난 뒤 다시 길게 되돌리는 운영 기법을 쓴다. TTL 값을 아무 생각 없이 크게 잡아두면 장애 대응이 느려진다.
flowchart LR
U1["클라 A"] --> R["리졸버 캐시"]
U2["클라 B"] --> R
U3["클라 C"] --> R
R -- "캐시 미스 시에만" --> AUTH["권한 네임서버"]
다만 모든 계층이 TTL을 정직하게 지키지는 않는다. 브라우저도 자기만의 DNS 캐시를 들고 있고, OS에도 nscd/systemd-resolved의 캐시가 있고, 공공 리졸버도 자기 정책으로 TTL을 덮어쓸 수 있다. 실제로 “TTL을 60초로 낮췄는데 왜 아직도 옛 IP로 가지?”의 답은 대부분 어느 계층의 캐시다.
dig 출력 읽기
DNS를 진단할 때 가장 기본 도구가 dig다. 설치돼 있으면 바로 써볼 수 있다.
dig example.com
# 출력 예시
# ; <<>> DiG 9.18.0 <<>> example.com
# ;; global options: +cmd
# ;; Got answer:
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
#
# ;; QUESTION SECTION:
# ;example.com. IN A
#
# ;; ANSWER SECTION:
# example.com. 300 IN A 93.184.216.34
#
# ;; Query time: 12 msec
# ;; SERVER: 1.1.1.1#53(1.1.1.1)
각 섹션을 읽는 법.
- HEADER: 질의의 메타데이터.
status: NOERROR면 성공.NXDOMAIN은 “그런 이름 없음”,SERVFAIL은 “리졸버가 답을 못 얻음”. 여기서 갈림길이 크다 - flags:
qr(응답),rd(재귀 원함),ra(재귀 가능),aa(권한 있음 — 권한 네임서버에서 온 답).aa플래그를 보려면 권한 서버에 직접 질의해야 한다 - QUESTION SECTION: 내가 물어본 내용 그대로
- ANSWER SECTION: 답. 이름, TTL, 클래스(IN=Internet), 타입, 값 순서
- Query time / SERVER: 얼마나 걸렸고 어느 리졸버가 답했는지
유용한 옵션들.
# 특정 레코드 타입
dig example.com MX
dig example.com AAAA
dig example.com TXT
# 특정 네임서버에 직접 질의 (캐시 우회)
dig @8.8.8.8 example.com
# 권한 네임서버부터 루트까지 추적
dig +trace example.com
# 답만 간단히
dig +short example.com
dig +trace는 리졸버가 하는 일을 그대로 보여준다. 루트 → TLD → 권한 서버를 차례로 밟으며 답을 찾아간다. 한 번 실행해보면 이 글에서 말한 “위임 과정”을 눈으로 볼 수 있다.
또 하나 자주 쓰는 도구가 nslookup과 host다. 출력이 조금씩 다를 뿐 하는 일은 비슷하다. 프로덕션 디버깅에서는 dig의 정보량이 압도적이라 대부분 이걸 쓴다.
DNS 보안 — 믿을 수 있는가
원래 DNS는 암호화되지 않은 프로토콜이다. 중간의 누군가가 질의/응답을 훔쳐볼 수 있고, 심지어 위조할 수도 있다. 이걸 막기 위한 장치들이 단계적으로 도입됐다.
- DNSSEC: 권한 네임서버가 응답에 디지털 서명을 붙인다. 리졸버는 이 서명을 검증해서 위조 여부를 판별. 구현이 복잡해 보급률은 아직 낮다
- DoT(DNS over TLS): 리졸버와 클라이언트 사이의 DNS 질의를 TLS로 암호화. 853 포트
- DoH(DNS over HTTPS): 같은 목적을 HTTPS 위에서. 방화벽이 HTTPS를 막기 어렵다는 점을 이용. Cloudflare, Google, Firefox 등이 밀고 있다
이 이야기는 이 시리즈의 범위를 넘는다. 더 파려면 Cloudflare Learning Center: DNS가 좋은 출발점이다.
실무에서 자주 만나는 DNS 이슈
DNS는 원리가 단순해 보이지만 현장에서 부딪히는 지점은 많다.
- TTL을 크게 잡아둔 도메인의 마이그레이션: 며칠 전부터 TTL을 줄여놔야 한다
- CNAME과 루트 도메인: 앞서 말했듯 루트엔 CNAME을 못 쓴다. 이 제약을 우회하려면 클라우드 벤더의 ALIAS/Flattening을 써야 한다
- split-horizon DNS: 사내망과 외부에서 같은 도메인을 다른 IP로 해석하게 만드는 설정. 잘못 세팅하면 “회사 안에서는 되는데 밖에서는 안 된다”가 된다
- Kubernetes CoreDNS: 클러스터 내부 서비스 이름을 해석해주는 DNS.
my-svc.my-ns.svc.cluster.local같은 이름이 이 서버를 거친다. CoreDNS가 죽으면 클러스터 전체가 “DNS 장애”를 겪는다 - 부정 캐시(Negative Caching):
NXDOMAIN응답도 캐시된다. 이름을 새로 추가한 직후엔 일시적으로 예전 “없음” 답이 살아 있을 수 있다
운영 중 DNS 이상이 의심될 때의 진단 순서는 대체로 이렇다. dig로 권한 네임서버에 직접 질의해 정답을 확인하고, 공공 리졸버(@8.8.8.8, @1.1.1.1)로 한 번 더 확인하고, 마지막으로 OS/애플리케이션 캐시를 의심한다. 이 순서를 안 지키고 아무 데나 건드리면 원인 찾기가 한없이 길어진다.
시리즈를 마치며
1편에서 OSI와 TCP/IP 모델로 계층의 지도를 그렸다. 2편에서 IP와 서브넷으로 주소 체계를 세웠다. 3편에서 TCP와 UDP로 전송 계층의 두 성격을 들여다봤다. 이번 편에서는 애플리케이션이 가장 먼저 부딪히는 프로토콜 중 하나인 DNS를 파헤쳤다.
네 편을 엮으면 브라우저 주소창에 https://example.com을 입력한 순간부터 서버가 HTML을 돌려주기까지, 각 계층이 무엇을 하는지가 하나의 그림으로 연결된다. DNS가 이름을 IP로 바꾸고, IP가 경로를 안내하고, TCP가 연결을 수립하고, TLS가 암호화를 얹고, HTTP가 요청을 실어 나른다. 이 흐름이 몸에 익으면 네트워크 장애를 마주할 때 “어느 계층에서 막혔는가”부터 묻게 된다.
이 시리즈가 다루지 못한 주제도 많다. HTTP/2·HTTP/3·QUIC 같은 현대 프로토콜, 방화벽과 NAT 트래버설, BGP와 인터넷 라우팅, 로드밸런서 설계, 서비스 메시에서의 트래픽 흐름. 기본기를 다진 다음에 이런 주제로 넘어갈 수 있다.
다음 편에서는 이 위에서 도는 가장 많이 쓰이는 애플리케이션 프로토콜 — HTTP를 해부한다. 요청과 응답의 구조부터 HTTP/2·HTTP/3까지의 진화, 그리고 HTTPS가 당연한 기본값이 된 배경을 살펴본다.

Loading comments...