Skip to content

필사 모드: DNS 완전 가이드: 이름이 주소가 되는 경로를 끝까지 따라가기

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며

장애 회의에서 "DNS 문제 같습니다"라는 말이 나오면 대개 그 자리에서 조사가 멈춥니다. DNS는 하나의 시스템이 아니라 성격이 다른 다섯 구간이 이어 붙은 경로이기 때문입니다. 어느 구간이 문제인지 말하지 않은 진단은 진단이 아닙니다.

이 블로그에는 이미 DNS 심층 분석DNS 해석 순서 디버깅이 있습니다. 앞의 글은 프로토콜 구조를, 뒤의 글은 리눅스 호스트의 해석 순서를 다룹니다. 이 글은 그 사이를 메웁니다. 레코드를 바꾸고, 전파를 기다리고, 캐시를 비우고, 어느 구간이 범인인지 판별해야 하는 운영자의 관점에서 절차와 판단 기준을 정리합니다.

명령은 BIND 9 계열의 dig, glibc 스텁 리졸버, systemd-resolved를 기준으로 합니다. 이 글에 나오는 모든 옵션은 매뉴얼과 RFC 원문에서 확인한 것만 실었고, 근거는 마지막 참고 자료에 URL과 확인 날짜로 남겼습니다.

dig -v
resolvectl status

1. 이름 해석 경로 — 질의 하나가 지나가는 다섯 구간

이름 하나가 주소가 되기까지 다음 다섯 구간을 지납니다. 각 구간은 서로 다른 이유로, 서로 다른 증상을 내며 실패합니다.

  1. 애플리케이션과 스텁 리졸버. 프로그램이 이름 해석 함수를 부르면 시스템의 스텁 리졸버가 처리합니다. RFC 8499는 스텁 리졸버를 "스스로 전체 해석을 수행할 수 없고 실제 해석은 재귀 리졸버에 의존하는 리졸버"로 정의합니다. 이 구간에는 /etc/hosts와 이름 서비스 스위치 설정이 함께 걸립니다.
  2. 리졸버 선택. /etc/resolv.conf가 어느 서버로 보낼지, 어떤 검색 도메인을 붙일지 결정합니다.
  3. 재귀 리졸버의 캐시. 캐시에 있으면 여기서 끝납니다. 대부분의 질의가 이 구간에서 끝나기 때문에, 변경 사고의 대부분도 이 구간에서 발생합니다.
  4. 위임 추적. 캐시에 없으면 루트에서 시작해 TLD를 거쳐 권한 서버까지 내려갑니다. RFC 1034는 이 과정을 "SNAME에서 시작해 부모, 조부모 순으로 로컬에 있는 네임서버 레코드를 찾고, 없으면 루트 서버가 들어 있는 안전벨트(safety belt) 설정에서 출발한다"고 기술합니다.
  5. 권한 서버의 응답. RFC 8499의 권한 서버 정의는 "존의 내용을 로컬 지식으로 알고 있어서 다른 서버에 묻지 않고 답할 수 있는 서버"입니다.

구간을 나누는 이유는 각 구간을 따로 물어볼 수 있기 때문입니다.

getent hosts api.internal.example.com
cat /etc/resolv.conf
resolvectl status
dig @127.0.0.53 api.internal.example.com
dig @10.0.0.53 api.internal.example.com
dig @ns1.example.com api.internal.example.com

getent는 이름 서비스 스위치를 거치는 경로이고 dig는 그렇지 않습니다. 이 둘의 결과가 다르면 문제는 DNS가 아니라 스텁 리졸버 구간에 있습니다.

/etc/resolv.conf의 지시어는 다음과 같습니다. 값은 매뉴얼 기준입니다.

  • nameserver — 질의를 보낼 서버의 주소입니다. 최대 3개까지만 사용됩니다.
  • search — 짧은 이름에 붙일 검색 도메인 목록입니다. glibc 2.25 이하에서는 6개, 총 256자로 제한되었고 2.26부터 제한이 없어졌습니다.
  • options ndots:n — 이름에 점이 몇 개 이상 있어야 검색 도메인을 붙이지 않고 절대 이름으로 먼저 질의할지의 임계값입니다. 기본값 1, 최대 15입니다.
  • options timeout:n — 응답 대기 시간입니다. 기본 5초, 최대 30초입니다.
  • options attempts:n — 포기하기 전 시도 횟수입니다. 기본 2회, 최대 5회입니다.
  • options rotate — 나열된 네임서버를 순환 선택합니다.
  • options single-request — IPv4와 IPv6 질의를 병렬이 아니라 순차로 보냅니다.

여기서 두 가지 계산이 나옵니다. 첫째, 첫 번째 네임서버가 죽어 있으면 사용자는 기본값 기준 5초를 그대로 체감합니다. 타임아웃과 시도 횟수를 줄이지 않으면 이중화는 있으나 마나입니다. 둘째, ndots 값이 크면 짧은 이름은 물론이고 점이 몇 개 들어간 이름까지 검색 도메인을 먼저 시도하므로, 외부 이름 하나를 푸는 데 실패 질의가 여러 번 앞서 나갑니다. 컨테이너 환경에서 이름 해석이 느린 전형적인 원인입니다.

검색 도메인이 관여하는지 아닌지는 다음으로 갈라 볼 수 있습니다.

dig +search api
dig +nosearch api
dig api.internal.example.com.

마지막 줄처럼 이름 끝에 점을 붙이면 절대 이름이 되어 검색 도메인을 건너뜁니다. 애플리케이션 설정에 외부 도메인을 적을 때 후행 점을 붙여 두면 불필요한 질의가 사라집니다.


2. 레코드 타입 — 무엇을 어디에 두는가

타입담는 것운영에서 걸리는 제약
A / AAAAIPv4 / IPv6 주소가장 기본. 둘 다 있으면 클라이언트가 선택
CNAME다른 이름으로의 별칭같은 이름에 다른 데이터와 공존 불가
MX메일 수신 서버대상이 별칭이면 안 됨
NS하위 존의 권한 서버대상이 별칭이면 안 됨
SOA존의 권한 정보와 네거티브 캐시 TTL존 정점에 반드시 존재
TXT자유 텍스트소유 확인, 메일 정책 등이 여기에 얹힘
PTR주소에서 이름으로in-addr.arpa / ip6.arpa 아래에 위치
SRV서비스의 호스트와 포트대상이 별칭이면 안 됨
CAA인증서 발급 허용 CA상위로 올라가며 탐색됨

몇 가지는 근거를 그대로 인용해 둘 가치가 있습니다.

CNAME은 혼자여야 합니다. RFC 1034는 "어떤 노드에 CNAME 레코드가 있으면 다른 데이터가 있어서는 안 된다"고 못 박습니다. 정규 이름과 별칭의 데이터가 달라지는 것을 막기 위해서입니다. 그리고 RFC 8499는 존 정점(apex)을 "SOA와 그에 대응하는 권한 NS 레코드 집합의 소유자에 해당하는 지점"으로 정의합니다. 정점에는 SOA와 NS가 반드시 있으므로, 정점에는 CNAME을 둘 수 없습니다. 루트 도메인을 CDN에 붙이려다 막히는 사고의 정확한 근거가 이것입니다.

NS와 MX의 대상은 별칭이면 안 됩니다. RFC 2181은 "NS 레코드의 값으로 쓰이는 도메인 이름, 그리고 MX 레코드 값의 일부로 쓰이는 도메인 이름은 별칭이어서는 안 된다"고 규정합니다. SRV도 마찬가지입니다. RFC 2782는 대상에 대해 "이 이름에 대한 주소 레코드가 하나 이상 있어야 하며, 그 이름은 별칭이어서는 안 된다"고 요구합니다. SRV의 형식은 _Service._Proto.Name TTL Class SRV Priority Weight Port Target이고, 우선순위가 낮은 대상을 먼저 시도하며 같은 우선순위 안에서는 가중치에 비례해 선택합니다.

CAA는 위로 올라가며 찾습니다. RFC 8659는 CA가 "지정된 레이블에서 시작해 루트를 포함하지 않는 지점까지 DNS 이름 트리를 올라가며 CAA 레코드 집합을 찾는다"고 정의합니다. 태그는 issue, issuewild, iodef가 있고, 발급을 제한하는 태그가 하나도 없으면 CAA는 발급을 제한하지 않습니다. 서브도메인에 CAA를 안 넣었는데 상위의 CAA에 막히는 상황이 여기서 나옵니다.

크기 제한도 기억해 둘 값이 있습니다. RFC 1035는 레이블 63옥텟, 이름 255옥텟, UDP 메시지 512옥텟을 상한으로 정했습니다. 512옥텟 제한은 RFC 6891의 EDNS(0)로 확장되며, 요청자는 OPT 레코드의 클래스 필드에 자신이 받을 수 있는 최대 UDP 페이로드 크기를 적습니다. 이 문서는 출발점으로 4096옥텟을, 폴백 구간으로 1280~1410바이트를 제시합니다. 응답이 이보다 크면 TC 비트가 서고 TCP로 다시 물어야 합니다.


3. TTL과 캐시 — "전파"의 실체

DNS에는 푸시가 없습니다. 변경이 퍼지는 것이 아니라 캐시가 만료되는 것입니다. 이 한 문장을 이해하면 전파 관련 사고의 대부분이 사라집니다.

RFC 1034는 TTL을 "레코드가 버려지기 전까지 얼마나 오래 캐시될 수 있는지"로 정의합니다. RFC 2181은 여기에 실무에서 걸리는 규칙을 더합니다. 하나의 레코드 집합 안에서 모든 레코드의 TTL은 같아야 하며, 서버는 TTL이 서로 다른 집합을 보내서는 안 됩니다. 또한 TTL은 부호 없는 32비트 값이지만 최대값은 2147483647이고, 최상위 비트가 선 값은 0으로 취급해야 합니다.

없다는 사실도 캐시됩니다. RFC 2308은 두 종류를 구분합니다.

  • NXDOMAIN — 응답 코드가 이름 오류이며, 질의한 도메인 자체가 존재하지 않습니다.
  • NODATA — 응답 코드는 정상인데 답 구역에 관련 레코드가 없습니다. 이름은 있고 그 타입만 없는 상태입니다.

네거티브 응답의 캐시 시간은 SOA의 MINIMUM 필드와 SOA 레코드 자신의 TTL 중 작은 값입니다. 같은 문서는 실무 권고도 남겼습니다. "1시간에서 3시간 사이의 값이 잘 동작하는 것으로 확인되었고 기본값으로 삼을 만하다. 하루를 넘는 값은 문제를 일으키는 것으로 확인되었다." 오타 난 레코드를 고쳤는데 한나절 동안 계속 실패하는 상황의 원인이 대개 이 값입니다.

변경 절차는 TTL을 중심으로 짭니다.

  1. 변경 예정일 기준으로 기존 TTL만큼 앞서서 TTL을 낮춥니다(예: 300초).
  2. 기존 TTL이 다 지날 때까지 기다립니다. 이 대기를 건너뛰면 낮춘 TTL이 아직 캐시에 반영되지 않은 리졸버가 남습니다.
  3. 레코드를 변경합니다.
  4. 권한 서버와 여러 리졸버에서 각각 확인합니다.
  5. 안정되면 TTL을 원래 값으로 되돌립니다.

캐시에 남은 시간은 관찰할 수 있습니다.

dig +noall +answer +ttlunits example.com
dig +noall +answer example.com
dig @1.1.1.1 +noall +answer example.com
dig @ns1.example.com +noall +answer example.com

리졸버에 반복해서 물으면 TTL이 줄어드는 것이 보입니다. 남은 초가 곧 그 리졸버의 캐시 만료까지 남은 시간입니다. 반면 권한 서버에 직접 물으면 항상 원래 설정값이 나옵니다.


4. 위임과 네임서버 — 권한이 갈라지는 지점

RFC 8499는 위임을 "부모 존에 자식 원점에 대한 NS 레코드 집합을 추가해 별도의 존을 만드는 과정"으로 정의하고, 그 경계를 존 컷(zone cut)이라 부릅니다. 여기서 실무자가 반드시 알아야 할 비대칭이 하나 있습니다. RFC 2181은 "존 컷을 나타내는 NS 레코드는 새로 만들어진 자식 존의 소유"라고 규정합니다. 즉 부모(TLD)가 가진 NS 목록과 자식 존이 스스로 답하는 NS 목록은 다를 수 있고, 실제 권한은 자식 쪽입니다.

여기서 두 가지 흔한 사고가 나옵니다.

  • 레지스트라에서 네임서버를 바꿨는데 반영이 안 된다. 바뀐 것은 부모의 위임입니다. 자식 존이 여전히 옛 서버 목록을 답하고 있으면 리졸버가 무엇을 캐시했느냐에 따라 결과가 갈립니다.
  • 네임서버 목록에 실제로 응답하지 않는 서버가 섞여 있다. 리졸버는 응답 없는 서버를 만나면 재시도하므로, 이름은 결국 풀리지만 간헐적으로 느려집니다.

글루(glue)가 필요한 이유도 여기서 나옵니다. RFC 1034는 존 아래쪽 경계를 설명하는 NS 레코드들이 "그 존의 권한 데이터가 아니다"라고 말하고, 하위 존 네임서버의 주소 레코드를 부모가 함께 제공한다고 기술합니다. 네임서버의 이름이 자기가 담당하는 존 아래에 있으면, 주소를 알기 위해 그 존에 물어야 하고 그 존에 물으려면 주소가 필요한 순환이 생깁니다. 부모가 주는 주소 레코드가 이 순환을 끊습니다.

부모와 자식을 각각 물어 비교하는 것이 진단의 핵심입니다.

dig NS example.com
dig +trace example.com
dig +nssearch example.com
dig SOA example.com @ns1.example.com
dig SOA example.com @ns2.example.com

+trace는 매뉴얼 표현으로 "루트 네임서버부터의 위임 경로를 추적"합니다. +nssearch는 "해당 존의 권한 네임서버를 찾으려 시도"합니다. 마지막 두 줄로 모든 권한 서버의 SOA 일련번호가 같은지 확인하세요. 번호가 어긋나 있으면 존 전송이 밀린 것이고, 사용자는 서버에 따라 다른 답을 받고 있는 중입니다.


5. dig 읽는 법

기본형은 dig @server name type입니다. 매뉴얼은 각 자리를 "질의할 네임서버의 이름이나 주소", "조회할 레코드의 이름", "필요한 질의의 타입"으로 설명합니다.

출력은 헤더, 질문, 답, 권한, 추가 구역과 통계로 나뉩니다. 읽는 순서는 status → flags → 각 구역입니다.

status에 오는 값의 의미는 RFC 1035의 응답 코드입니다.

  • NOERROR — 오류 없음. 다만 답 구역이 비어 있으면 NODATA이며 이름은 존재합니다.
  • NXDOMAIN — 이름 오류. 그 이름이 존재하지 않습니다.
  • SERVFAIL — 서버 실패. 이름이 없다는 뜻이 아니라 답을 만들지 못했다는 뜻입니다. 상위 도달 실패나 DNSSEC 검증 실패가 대표적입니다.
  • REFUSED — 거부. 정책상 답하지 않겠다는 뜻이며, 재귀를 허용하지 않는 리졸버에 외부에서 물었을 때 나옵니다.

flags에 오는 글자도 각각 뜻이 있습니다.

  • qr — 응답 메시지라는 표시입니다.
  • aa — 권한 있는 답입니다. 이 글자가 없으면 캐시에서 온 답입니다.
  • tc — 잘렸습니다. TCP로 다시 물어야 합니다.
  • rd / ra — 재귀를 요청했다 / 재귀가 가능하다.
  • ad — DNSSEC 검증을 통과했음을 리졸버가 표시한 것입니다.
  • cd — 검증을 끄고 물었다는 표시입니다.

자주 쓰는 조합을 목적별로 정리하면 다음과 같습니다.

dig +short A example.com
dig +noall +answer example.com
dig +noall +authority +additional example.com
dig @1.1.1.1 example.com
dig +norecurse @10.0.0.53 example.com
dig -x 93.184.216.34
dig -t MX example.com
dig +dnssec example.com
dig +cd example.com
dig +multiline SOA example.com
dig +tcp example.com
dig +time=2 +tries=1 example.com
dig +stats example.com
dig +yaml example.com

각 옵션의 근거는 매뉴얼 문장 그대로입니다. +short는 "간결한 답을 낼지 전환"하고, +norecurse는 "질의의 RD(recursion desired) 비트를 끄며", +dnssec는 "DNSSEC OK(DO) 비트를 설정해 DNSSEC 레코드를 요청"하고, +cd는 "질의에 CD(checking disabled) 비트를 설정"합니다. -x는 "주소를 이름으로 매핑하는 간이 역방향 조회"이고, +time=T+tries=T는 대기 시간과 시도 횟수를, +subnet은 "EDNS CLIENT-SUBNET 옵션을 지정한 주소로 전송"합니다.

+norecurse의 쓰임을 하나 짚어 둡니다. 리졸버에 재귀를 끄고 물으면 그 리졸버의 캐시에 있는 것만 답합니다. 캐시 적중 여부를 확인하거나, 캐시를 오염시키지 않고 상태를 들여다볼 때 씁니다.

반대로 +trace에는 함정이 있습니다. +trace는 루트부터 각 단계를 직접 질의하므로 여러분의 리졸버를 우회합니다. 그래서 +trace는 정상인데 애플리케이션은 실패하는 상황이 흔합니다. 이때 범인은 권한 서버가 아니라 리졸버 구간입니다.


6. 흔한 장애 — 전파 지연, 스테일 캐시, CNAME 제약

전파 지연. 앞에서 본 것처럼 전파는 만료입니다. "48시간 걸린다"는 안내는 대부분 낡은 TTL 설정을 전제로 한 보수적 표현입니다. 변경 전에 TTL을 낮추지 않았다면 최대 기존 TTL만큼 기다리는 것 외에 방법이 없고, 권한 서버에서 이미 새 값이 나오는데 사용자만 옛 값을 본다면 그것은 정상 동작입니다.

스테일 캐시. 캐시는 한 곳에 있지 않습니다. 최소한 다음 위치를 각각 확인해야 합니다.

  • 사내 재귀 리졸버의 캐시
  • 호스트의 로컬 캐시(systemd-resolved 등)
  • 런타임 자체의 캐시(일부 런타임은 자체 이름 캐시를 가집니다)

비우는 명령은 계층마다 다릅니다.

sudo rndc flush
sudo rndc flushname api.example.com
sudo rndc flushtree example.com
sudo rndc dumpdb -cache
resolvectl flush-caches
resolvectl query api.example.com
resolvectl statistics

rndc flush는 "서버의 캐시를 비우고", flushname은 "주어진 이름을 뷰의 DNS 캐시에서, 해당하는 경우 네임서버 주소 데이터베이스와 불량 서버 캐시, SERVFAIL 캐시에서" 제거합니다. flushtree는 "주어진 이름과 그 모든 하위 도메인"을 같은 범위에서 제거합니다. resolvectl flush-caches는 "서비스가 로컬에 유지하는 모든 DNS 레코드 캐시를 비웁니다". 전체 비우기보다 이름 단위 비우기를 먼저 시도하세요. 캐시를 통째로 버리면 그 뒤 몇 분간 상위 질의가 폭증합니다.

CNAME 제약. 세 가지가 반복됩니다. 정점에 CNAME을 둘 수 없고, MX와 NS와 SRV의 대상이 별칭이면 안 되며, CNAME이 있는 이름에는 다른 레코드를 함께 둘 수 없습니다. 앞의 두 개는 규격 위반이라도 당장은 동작하는 것처럼 보이는 경우가 있어 더 위험합니다. 동작하는 것처럼 보이는 규격 위반이 가장 늦게, 가장 나쁜 시점에 드러납니다.

NODATA를 NXDOMAIN으로 오해. NOERROR인데 답이 비어 있으면 이름은 있습니다. AAAA만 없는 이름에 IPv6로 접속을 시도하는 클라이언트가 대표적인 사례이며, 이때 이름 자체를 지웠다 다시 만드는 식의 대응은 아무 효과가 없습니다.

TCP 차단. DNSSEC 서명이나 레코드 수 때문에 응답이 커지면 TC 비트가 서고 TCP로 재질의합니다. 방화벽이 UDP 53만 열어 두었다면 평소에는 멀쩡하다가 특정 이름에서만 실패합니다. 판별은 간단합니다.

dig +tcp example.com
dig +dnssec +tcp example.com

이름은 풀리는데 접속이 안 됨. dig가 정상 응답하면 DNS 구간은 무죄입니다. 그다음은 연결 계층의 문제이며 진단 도구가 달라집니다.


7. DNSSEC — 무엇을 보장하고 무엇을 보장하지 않는가

RFC 4033의 표현을 그대로 옮기면 DNSSEC은 "DNS에 데이터 출처 인증과 데이터 무결성을 제공"합니다. 그리고 같은 문서는 제공하지 않는 것을 명시합니다. "DNSSEC은 기밀성, 접근 제어 목록, 또는 질의자를 구별하는 다른 수단을 제공하도록 설계되지 않았다." 나아가 "DNSSEC은 서비스 거부 공격에 대한 보호를 제공하지 않는다"고 하며, 오히려 암호 연산에 기반한 새로운 부류의 서비스 거부 공격을 만든다고 경고합니다. 도청 방지를 원한다면 다른 계층의 대책이 필요합니다.

레코드는 네 종류입니다.

  • DNSKEY — 존의 공개 키를 담습니다.
  • RRSIG — 레코드 집합에 대한 디지털 서명입니다.
  • DS — 부모 존의 위임 지점에 놓여 자식의 키를 가리킵니다.
  • NSEC — 이름이나 타입이 존재하지 않음을 인증합니다.

신뢰 체인은 DNSKEY 집합과 DS 집합이 번갈아 이어지며 각 고리가 다음 고리를 보증하는 구조이고, 검증은 트러스트 앵커에서 시작합니다. 검증 결과는 네 가지 상태로 정리됩니다. 트러스트 앵커와 체인이 있고 모든 서명을 검증한 Secure, 서명되지 않았다는 사실이 증명된 Insecure, 서명되어야 하는데 검증에 실패한 Bogus, 해당 구간이 안전하다고 알려 줄 트러스트 앵커가 없는 Indeterminate입니다.

운영자에게 중요한 것은 실패가 SERVFAIL로 나타난다는 점입니다. 존 내용은 그대로인데 서명 유효 기간이 지나면, 검증하는 리졸버에서만 조회가 실패합니다. 검증하지 않는 리졸버에서는 멀쩡하게 보입니다. 판별은 검증을 끄고 물어보는 것입니다.

dig +dnssec example.com
dig +cd example.com
delv example.com

+cd를 붙였더니 답이 나온다면 그것은 DNSSEC 검증 실패입니다. delv는 매뉴얼이 "DNS 질의를 보내고 결과를 검증하는 도구"라고 설명하는 전용 도구입니다.

사내 환경에서는 한 가지가 더 걸립니다. RFC 4033은 부모로부터 인증 체인이 오지 않는 서명된 존을 "보안의 섬(island of security)"이라 부르며, 그런 존은 대역 외 수단으로 키를 신뢰할 수 있을 때만 인증된다고 설명합니다. 서명되지 않은 내부 존을 검증 리졸버가 다루는 방식이 여기에 걸리므로, 내부 존을 별도로 처리하도록 리졸버 설정을 맞춰야 합니다.


8. 사내 DNS 운영

분할 지평. 같은 이름에 대해 내부와 외부에 다른 답을 주는 구성입니다. 편리하지만 "어디서 물었느냐"가 답을 바꾸므로, 장애 신고를 받을 때 질문자의 위치를 먼저 확인해야 합니다. 신고자가 VPN에 붙어 있었는지 아닌지가 결론을 바꿉니다.

포워더인가 재귀인가. RFC 8499는 포워더를 "권한 네임서버 체인을 직접 사용하는 대신 질의를 해석하는 데 쓰이는 네임서버"로 정의합니다. 사내 리졸버가 상위로 포워딩하는 구성이라면, 상위가 죽었을 때 사내 리졸버는 살아 있으면서 아무것도 답하지 못하는 상태가 됩니다. 헬스 체크를 프로세스 생존이 아니라 실제 질의 성공으로 잡아야 하는 이유입니다.

리졸버 이중화의 현실. 앞서 본 대로 nameserver는 3개까지만 쓰이고, 기본 타임아웃은 5초, 기본 시도 횟수는 2회입니다. 첫 서버가 응답하지 않으면 사용자는 그 시간을 그대로 체감합니다. 사내 표준 resolv.conf에는 타임아웃과 시도 횟수를 명시하고, 필요하면 rotate로 부하를 분산하세요.

변경과 검증. 존을 바꾼 뒤에는 모든 권한 서버에서 같은 답이 나오는지 확인합니다.

sudo rndc reload
sudo rndc status
sudo rndc querylog on
dig SOA example.com @ns1.example.com +short
dig SOA example.com @ns2.example.com +short
sudo rndc querylog off

rndc reload는 "설정 파일과 존을 다시 읽고", rndc reconfig는 "설정 파일을 다시 읽고 새 존을 적재하지만 파일이 바뀌었더라도 기존 존은 다시 읽지 않습니다". 이 차이를 모르면 "리로드했는데 왜 안 바뀌느냐"가 반복됩니다. querylog는 질의 로깅을 켜고 끄며, 부하가 큰 서버에서는 켠 채로 두지 말고 조사 구간에만 켜세요.

컨테이너 환경. 오케스트레이터가 넣어 주는 resolv.conf에는 검색 도메인이 여러 개, ndots가 큰 값으로 들어가는 경우가 많습니다. 결과적으로 외부 이름 하나를 푸는 데 실패 질의가 여러 번 선행합니다. 애플리케이션 설정의 외부 도메인에 후행 점을 붙이는 것만으로 상당 부분이 사라집니다.

무엇을 감시할 것인가. 다음 네 가지면 대부분의 사고를 먼저 발견할 수 있습니다.

  • 모든 권한 서버의 SOA 일련번호 일치
  • 서명 유효 기간의 잔여 일수(서명을 쓴다면)
  • 재귀 리졸버의 질의 지연과 SERVFAIL 비율
  • 도메인과 위임 정보의 만료일

9. 진단 순서 요약

증상을 받았을 때 다음 순서로 좁힙니다. 각 단계는 앞 단계를 배제하는 데 의미가 있습니다.

  1. 애플리케이션인가 시스템인가. getent hostsdig의 결과를 비교합니다. 다르면 스텁 리졸버 구간입니다.
  2. 어느 리졸버인가. /etc/resolv.confresolvectl status로 실제로 쓰는 서버를 확인하고, 그 서버에 직접 물어봅니다.
  3. 캐시인가 권한인가. 리졸버 답과 권한 서버 답을 비교합니다. 다르면 캐시이고, TTL이 남은 시간을 알려 줍니다.
  4. 위임인가. dig +trace와 부모·자식 NS 비교로 위임을 확인합니다.
  5. 검증인가. SERVFAIL이라면 +cd로 다시 물어봅니다. 답이 나오면 DNSSEC 검증 실패입니다.
  6. DNS가 아닌가. 이름이 정상적으로 풀린다면 이 시점에서 DNS 조사를 종료하고 연결 계층으로 넘깁니다.
getent hosts example.com
dig +short example.com
dig @1.1.1.1 +short example.com
dig @ns1.example.com +short example.com
dig +trace example.com
dig +cd example.com

퀴즈: 실력을 확인해 보세요

퀴즈 1: dig는 정상 응답하는데 애플리케이션에서만 "이름을 찾을 수 없음"이 납니다. 가장 먼저 확인할 것은?

정답: 스텁 리졸버 경로입니다. dig는 이름 서비스 스위치를 거치지 않고 /etc/resolv.conf만 쓰기 때문에 두 경로가 다릅니다

설명: 확인 순서는 getentdig를 나란히 놓는 것에서 시작합니다.

getent hosts api.example.com
dig +short api.example.com
cat /etc/resolv.conf

getent만 실패한다면 이름 서비스 스위치 설정, /etc/hosts, 컨테이너에 마운트된 resolv.conf, 검색 도메인과 ndots 설정을 봅니다. 애플리케이션이 자체 리졸버를 내장한 경우도 있으므로 런타임의 이름 해석 설정도 함께 확인하세요.

퀴즈 2: A 레코드를 바꿨는데 일부 사용자만 여전히 옛 주소로 갑니다. 무엇이 일어나고 있으며 다음 변경 때는 무엇을 다르게 해야 하나요?

정답: 리졸버 캐시가 아직 만료되지 않은 것입니다. 다음에는 변경보다 최소한 기존 TTL만큼 앞서서 TTL을 낮춰 두어야 합니다

설명: DNS에는 푸시가 없습니다. 변경이 퍼지는 것이 아니라 캐시가 만료되는 것입니다. 남은 시간은 리졸버마다 다르며, 관찰할 수 있습니다.

dig @1.1.1.1 +noall +answer +ttlunits example.com
dig @ns1.example.com +noall +answer example.com

리졸버 쪽 TTL은 반복 질의할수록 줄어들고, 권한 서버 쪽은 항상 원래 값입니다. 이 둘이 다른 것은 정상입니다.

퀴즈 3: 사내 리졸버에서는 SERVFAIL, 공용 리졸버에서는 정상입니다. 무엇을 의심하고 어떻게 한 번에 판별하나요?

정답: DNSSEC 검증 실패를 의심하고, 검증을 끄고 물어봐서 판별합니다

설명: SERVFAIL은 "그 이름이 없다"가 아니라 "답을 만들지 못했다"입니다. 검증 실패와 상위 도달 실패가 대표적인 원인입니다.

dig @10.0.0.53 example.com
dig @10.0.0.53 +cd example.com
delv example.com

+cd를 붙였을 때 답이 나오면 검증 단계에서 막힌 것입니다. 서명 유효 기간 만료나 DS 불일치를 먼저 확인하세요. +cd를 붙여도 실패한다면 검증이 아니라 도달성이나 상위 응답의 문제입니다.

퀴즈 4: 루트 도메인 example.com을 CDN 호스트 이름으로 바꾸려고 CNAME을 넣었더니 존이 거부됩니다. 규격상 이유는?

정답: 존 정점에는 SOA와 NS가 반드시 있어야 하는데, CNAME은 같은 이름에 다른 데이터와 공존할 수 없기 때문입니다

설명: RFC 1034는 어떤 노드에 CNAME이 있으면 다른 데이터가 있어서는 안 된다고 규정합니다. 그리고 존 정점은 SOA와 권한 NS 레코드 집합의 소유자로 정의됩니다. 두 규칙이 동시에 성립할 수 없습니다.

같은 이유로 다음도 규격 위반입니다.

example.com.        IN  MX   10 mail-alias.example.com.
mail-alias          IN  CNAME real-mail.example.net.

RFC 2181은 NS와 MX의 대상이 별칭이어서는 안 된다고 규정하고, RFC 2782는 SRV의 대상에 대해 같은 요구를 합니다.

퀴즈 5: 응답이 NOERROR인데 ANSWER 섹션이 비어 있습니다. 이 이름은 존재하지 않는 것인가요?

정답: 존재합니다. 이것은 NODATA이며, 이름은 있고 질의한 타입만 없는 상태입니다

설명: RFC 2308은 NXDOMAIN과 NODATA를 구분합니다. NXDOMAIN은 이름 자체가 없는 것이고, NODATA는 응답 코드가 정상인데 답 구역에 관련 레코드가 없는 것입니다.

dig AAAA example.com
dig A example.com
dig ANY example.com

두 경우 모두 네거티브 응답으로 캐시되며, 캐시 시간은 SOA의 MINIMUM 필드와 SOA 레코드 자신의 TTL 중 작은 값입니다. 그래서 잘못된 레코드를 고쳐도 한동안 실패가 이어질 수 있습니다.

퀴즈 6: dig +trace는 올바른 주소를 보여 주는데 서버의 애플리케이션은 여전히 옛 주소로 붙습니다. 어디를 봐야 하나요?

정답: 리졸버 구간입니다. +trace는 루트부터 직접 질의하므로 그 호스트가 실제로 쓰는 리졸버를 우회합니다

설명: +trace가 정상이라는 것은 권한 서버 쪽이 정상이라는 뜻일 뿐입니다. 실제 경로를 그대로 재현하려면 그 호스트가 쓰는 리졸버에 직접 물어야 합니다.

cat /etc/resolv.conf
dig @10.0.0.53 +noall +answer example.com
dig @10.0.0.53 +norecurse example.com
sudo rndc flushname example.com

+norecurse는 그 리졸버의 캐시에 있는 것만 답하게 하므로, 캐시가 원인인지 즉시 확인할 수 있습니다. 로컬 캐시가 따로 있다면 그쪽도 함께 비웁니다.


마치며

DNS 장애 대응이 어려운 이유는 어렵기 때문이 아니라, 어느 구간을 보고 있는지 말하지 않은 채로 이야기하기 때문입니다. 리졸버가 답한 것인지 권한 서버가 답한 것인지, 캐시에서 온 것인지 방금 조회한 것인지, 검증을 거친 것인지 아닌지를 매번 명시하면 대화가 급격히 짧아집니다.

당장 몸에 붙일 것은 세 가지입니다. getentdig를 나란히 물어 스텁 리졸버 구간을 먼저 배제하고, 리졸버와 권한 서버에 각각 물어 캐시와 원본을 구분하고, 레코드를 바꾸기 전에 기존 TTL만큼 앞서서 TTL을 낮춰 두세요. 이 세 가지만으로 DNS 관련 사고 대부분이 사라지거나, 최소한 몇 분 안에 어느 구간의 문제인지 결론이 납니다.


참고 자료


이어서 읽기

현재 단락 (1/233)

장애 회의에서 "DNS 문제 같습니다"라는 말이 나오면 대개 그 자리에서 조사가 멈춥니다. DNS는 하나의 시스템이 아니라 **성격이 다른 다섯 구간이 이어 붙은 경로**이기 때문...

작성 글자: 0원문 글자: 14,782작성 단락: 0/233