LabHub
배우기 러닝패스 코스

The certificate was renewed, but the browser still showed the old one

DNS has no propagation, only caches and TTLs

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

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

Concept map: 옛 답이 TTL 만큼 살아남는다. · 캐시와 TTL · 전 · SOA

왜 이게 필요했나

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

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

어떻게 동작하나

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

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

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

$ dig @127.0.0.1 -p 5301 www.lab.internal A +noall +answer
www.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 은 권한 서버가 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 로 짧은 이름이 어떤 질의를 만들어 내는지 서버 로그에서 확인한다.