네트워크 기초 · DNS · 이론
DNS — dig 는 되는데 애플리케이션은 실패하는 이유
한 줄 요약
dig 와 애플리케이션은 애초에 다른 경로로 이름을 풀기 때문에, dig 가 성공했다는 사실은 애플리케이션이 성공한다는 근거가 되지 못한다.
왜 이게 필요했나
장애 상황에서 가장 사람을 헷갈리게 하는 조합이 있다. dig +short api.internal.example.com 은 주소를 잘 돌려주는데 같은 호스트의 애플리케이션은 이름을 못 찾는다고 죽는다. 여기서 대부분 "DNS 는 멀쩡한데 애플리케이션이 이상하다"는 결론으로 넘어가 라이브러리를 뒤지기 시작한다. 이 방향은 거의 항상 헛수고다.
어떻게 동작하나
먼저 DNS 자체의 구조다. 리졸버는 루트 서버에게 물어 TLD 서버를 알아내고, TLD 서버에게 물어 권한 서버를 알아내고, 권한 서버에게서 최종 답을 받는다(재귀 질의와 반복 질의의 조합). 각 응답에는 TTL 이 붙어 있어 그 시간만큼 캐시된다.
레코드 종류도 알아 둘 만하다. A 는 IPv4 주소, AAAA 는 IPv6 주소, CNAME 은 다른 이름으로의 별칭, MX 는 메일 서버, NS 는 권한 서버, TXT 는 임의 문자열, SRV 는 서비스의 호스트와 포트다.
이제 핵심이다. 애플리케이션이 실제로 밟는 경로는 다르다. C, 파이썬, 루비, PHP, 그리고 리눅스의 자바까지 대부분 glibc 의 getaddrinfo() 를 호출하고, glibc 는 다음 순서를 따른다.
1. /etc/nsswitch.conf 의 hosts 행을 읽어 어떤 소스를 어떤 순서로 쓸지 정한다.
2. files 면 /etc/hosts 를 본다.
3. dns 면 /etc/resolv.conf 의 nameserver, search, options 를 적용해 질의한다.
4. resolve 면 systemd-resolved 에 물어본다.
dig, nslookup, host 는 이 경로를 통째로 건너뛴다. nsswitch.conf 도 /etc/hosts 도 보지 않고 resolv.conf 에서 nameserver 주소만 가져다 UDP 53 으로 질의를 던진다. 그래서 /etc/hosts 에만 있는 이름을 dig 는 못 찾고, 반대로 누군가 디버깅하다 남겨 둔 낡은 /etc/hosts 항목 때문에 애플리케이션만 옛 IP 로 접속하는 상황이 생긴다.
애플리케이션과 같은 경로를 재현하는 도구는 getent hosts 다. getent ahosts 를 쓰면 IPv4 와 IPv6 를 시도 순서대로 보여 준다. 정리하면 이렇다. dig 는 DNS 서버의 답을 보여 주고, getent 는 애플리케이션이 받을 답을 보여 준다. 둘의 결과가 다르면 그 차이 자체가 원인의 위치다.
현장에서 만나는 모습
쿠버네티스 파드의 resolv.conf 에는 options ndots:5 와 네 개의 search 도메인이 들어 있다. ndots 는 "점 개수가 이 값보다 적으면 search 목록을 먼저 붙여 보라"는 뜻이다. 덕분에 파드 안에서 api 나 api.prod 같은 짧은 이름을 쓸 수 있다.
문제는 외부 도메인이다. api.stripe.com 은 점이 2개라 5보다 적으므로 search 도메인 네 개를 먼저 붙여 본다. A 와 AAAA 를 함께 물으므로 외부 도메인 하나를 풀기 위해 질의 10개가 나가고 그중 8개는 NXDOMAIN 을 받으려고 나간 것이다. 파드가 수백 개면 CoreDNS 부하의 대부분이 실패할 것이 확정된 질의로 채워진다.
여기서 흔히 도는 처방을 정정해야 한다. "ndots 를 1로 낮추면 된다"는 조언은 클러스터를 깨뜨린다. ndots 가 1이면 점이 하나인 payments.prod 같은 크로스 네임스페이스 호출이 search 를 거치지 않아 전부 실패한다. 안전한 하한선은 2다. 더 확실한 방법은 외부 도메인 끝에 점을 붙여 절대 이름으로 만드는 것이다. 뒤의 점 하나가 search 도메인 네 개를 없앤다.
응답 코드도 뭉뚱그리면 안 된다. NXDOMAIN 은 그 이름이 없다는 확정이고, SERVFAIL 은 리졸버가 답을 만들지 못했다는 뜻이며, ANSWER 가 0 인 NOERROR 는 이름은 있지만 그 타입의 레코드가 없다는 뜻이다. 마지막 것이 가장 자주 오진된다. A 는 있는데 AAAA 가 없는 경우가 대표적이다.
이어지는 퀴즈에서 확인할 것
dig 와 getent 의 차이를 설명할 수 있는지, ndots 가 만드는 지연의 메커니즘을 이해했는지 확인한다.