LabHub
배우기 러닝패스 코스

인증서는 갱신됐는데 브라우저는 옛것을 보여줬다 · 이름은 어디서 먼저 답하는가 · 이론

DNS 에는 전파가 없다, 캐시와 TTL 이 있을 뿐

LabHub 에서 이어서 보기

한 줄 요약

이름 하나가 주소가 되기까지는 /etc/hosts → 리졸버 설정 → 캐시 → 권한 서버라는 순서가 있고, 그 사이 어디에서든 옛 답이 TTL 만큼 살아남는다. "DNS 를 바꿨는데 왜 아직 옛 주소로 가느냐" 의 답은 거의 언제나 이 순서 안에 있다.

왜 이게 필요했나

서비스를 새 서버로 옮기고 A 레코드를 바꿨다. 내 노트북에서는 새 주소가 나오는데 고객 절반은 아직 옛 서버에 붙어 있다. 담당자는 "전파(propagation)에 시간이 걸린다" 고 말하지만, 그 말은 설명이 아니라 체념이다. DNS 에는 전파라는 것이 없다. 있는 것은 캐시와 TTL 뿐이고, 각 캐시는 자기가 받아 둔 답을 TTL 이 다할 때까지 그대로 돌려준다. 그러니 "언제 끝나느냐" 는 물음에 정확히 답할 수 있다 — 바꾸기 레코드의 TTL 이 지나면 끝난다.

반대 방향의 사고도 있다. 이름을 만들기 전에 누군가 먼저 조회해 버리면 "없다" 는 답도 캐시된다. 그래서 레코드를 넣은 뒤에도 한동안 NXDOMAIN 이 나오고, 이 시간은 A 레코드의 TTL 이 아니라 존의 SOA 가 정한다. 이 두 가지를 모르면 DNS 장애 앞에서 할 수 있는 일이 기다리는 것뿐이다.

어떻게 동작하나

프로그램이 getaddrinfo() 를 부르면 glibc 는 먼저 [/etc/nsswitch.conf](https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html) 의 hosts: 줄을 본다. 보통 files dns 순서라 /etc/hosts 에 그 이름이 있으면 DNS 는 아예 묻지 않는다. getent hosts <이름> 이 이 순서를 그대로 밟기 때문에 "프로그램이 보는 답" 을 확인할 때는 dig 보다 getent 가 정확하다. dig 는 리졸버 라이브러리를 거치지 않고 DNS 서버에 직접 묻는 도구다.

DNS 로 넘어가면 [/etc/resolv.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html) 가 규칙이다. nameserver 는 최대 MAXNS(현재 3)개까지 적을 수 있고 적힌 순서대로 묻는다. search 목록과 options ndots:n(기본 1)은 짧은 이름을 어떻게 늘려 볼지 정한다 — 이름의 점 개수가 ndots 보다 적으면 검색 도메인을 하나씩 붙여 먼저 물어 보고, 다 실패하면 원래 이름으로 묻는다. 쿠버네티스 파드의 resolv.conf 는 [공식 문서](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/)대로 search <ns>.svc.cluster.local svc.cluster.local cluster.localoptions ndots:5 라서, 파드 안에서 api.example.com 처럼 점이 다섯 개 미만인 외부 이름을 부르면 검색 도메인이 붙은 질의가 먼저 나가고 NXDOMAIN 을 세 번 받은 뒤에야 진짜 이름을 묻는다. 그래서 파드에서 외부 API 가 유독 느릴 때 첫 용의자가 ndots 다.

리졸버(캐시)는 [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034) 가 정한 대로 재귀적으로 권한 서버를 찾아가 답을 받고, 레코드마다 붙어 있는 TTL 초 동안 그 답을 저장한다. TTL 은 권한 서버의 존(zone)이 정하며, 캐시는 남은 시간을 줄여 가며 돌려준다. dig 로 같은 캐시에 두 번 물으면 두 번째 답의 TTL 이 줄어 있는 것이 그 증거다.

$ dig @127.0.0.1 -p 5301 www.lab.internal A +noall +answerwww.lab.internal.   120   IN   A   10.0.0.10$ dig @127.0.0.1 -p 5301 www.lab.internal A +noall +answer     # 3초 뒤www.lab.internal.   117   IN   A   10.0.0.10

CNAME 은 "이 이름은 저 이름의 별명" 이다. 리졸버는 CNAME 을 만나면 사슬을 끝까지 따라가 CNAME 과 마지막 A 를 한 답에 넣어 준다. 사슬이 길수록 캐시할 레코드가 늘고, 각각의 TTL 이 따로 돈다.

없는 이름은 어떻게 캐시되나. [RFC 2308](https://datatracker.ietf.org/doc/html/rfc2308) 은 권한 서버가 NXDOMAIN(이름 없음)이나 NODATA(이름은 있으나 그 타입 없음)를 돌려줄 때 authority 절에 존의 SOA 를 넣고, 그 TTL 을 SOA 의 MINIMUM 필드와 SOA 자체 TTL 중 작은 값으로 두라고 정한다. 리졸버는 그 시간만큼 "없다" 를 기억한다. 존에 레코드를 추가하기 전에 시험 삼아 조회해 봤다면, 추가한 뒤에도 그 시간 동안은 없다는 답을 받는다.

$ dig @127.0.0.1 -p 5301 nope.lab.internal A +noall +comments +authority;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, ...;; AUTHORITY SECTION:lab.internal.   60   IN   SOA   ns1.lab.internal. admin.lab.internal. 2026091101 3600 600 86400 60

현장에서 만나는 모습

이전 작업을 계획할 때 숙련자가 하는 일은 하나다 — 바꾸기 며칠 전에 TTL 을 먼저 낮춘다. 3600 짜리 레코드를 바꾸면 최악의 경우 한 시간 동안 두 서버가 함께 트래픽을 받는다. 미리 60 으로 낮춰 두면(그 변경 자체가 옛 TTL 만큼 걸린다) 실제 이전은 1분 안에 끝난다. 끝난 뒤 TTL 을 되돌리는 것도 잊지 않는다. 낮은 TTL 은 캐시 적중률을 떨어뜨려 권한 서버 부하를 늘린다.

쿠버네티스에서는 ndots:5 가 만드는 추가 질의가 CoreDNS 부하와 지연으로 나타난다. 외부 이름을 끝에 점을 붙여 절대 이름(api.example.com.)으로 적거나, 파드의 dnsConfig 로 ndots 를 낮추는 것이 정석이다. /etc/hosts 도 함정이 된다. 예전에 디버깅하며 넣어 둔 한 줄이 남아 있으면 그 기계만 다른 곳으로 간다 — 그래서 "이 기계에서만 다르다" 는 신고를 받으면 getent hosts 부터 친다.

다음 실습에서 할 것

파드 안에 권한 서버와 캐시 서버를 파이썬으로 띄운다. 존 파일에 TTL 120 짜리 A 레코드와 SOA(minimum 60)를 두고, dig 로 TTL 이 줄어드는 것·CNAME 사슬·NXDOMAIN 의 SOA TTL 을 읽는다. 그 다음 존을 고쳐 권한 서버는 새 답을 주는데 캐시는 옛 답을 주는 상태를 재현하고, 캐시를 비운 뒤 최악의 전파 시간을 계산해 보고서에 적는다. 마지막에는 +search +ndots=5 로 짧은 이름이 어떤 질의를 만들어 내는지 서버 로그에서 확인한다.