HTTP 장애처럼 보였던 DNS 문제 상황
HTTP, 네트워크, DNS 문제는 장애 상황에서 서로 분리해서 보기가 어렵습니다. 애플리케이션 입장에서는 외부 API 호출이 실패했을 뿐이지만, 그 아래에서는 도메인 이름을 IP로 바꾸는 DNS 조회, TCP 연결, TLS 핸드셰이크, HTTP 요청 전송이 순서대로 일어납니다.
이번 문제도 처음에는 HTTP client timeout으로 보였습니다. 특정 외부 API 호출이 간헐적으로 실패했고, 같은 요청을 재시도하면 성공하는 경우도 있었습니다. 그래서 초반에는 외부 API 서버 부하, 네트워크 일시 장애, keep-alive 연결 문제를 먼저 의심했습니다.
하지만 로그를 조금 더 나누어 보니 이상한 점이 있었습니다. 실제 HTTP 응답 코드가 찍힌 실패보다, 연결을 시작하기도 전에 실패하는 요청이 더 많았습니다. 즉, 서버가 500을 반환한 것이 아니라 요청이 목적지까지 도달하지 못한 쪽에 가까웠습니다.
DNS 장애가 HTTP 요청 실패로 나타나는 이유
DNS는 도메인 이름을 실제 접속 가능한 IP 주소로 변환하는 역할을 합니다. 예를 들어 애플리케이션이 https://api.example.com으로 요청을 보낼 때, HTTP 요청이 바로 나가는 것이 아니라 먼저 api.example.com이 어떤 IP를 가리키는지 확인합니다.
이 DNS 조회가 느려지거나 실패하면 애플리케이션 로그에는 보통 네트워크 오류처럼 보입니다. Java에서는 UnknownHostException, SocketTimeoutException, ConnectException 같은 형태로 나타날 수 있고, HTTP client 설정에 따라 단순 read timeout 또는 connection timeout처럼 보이기도 합니다.
java.net.UnknownHostException: api.example.com
java.net.SocketTimeoutException: connect timed out
org.springframework.web.client.ResourceAccessException:
I/O error on POST request for "https://api.example.com/payments"
여기서 중요한 점은 HTTP 레벨의 장애와 DNS 레벨의 장애를 구분해야 한다는 것입니다. HTTP 상태 코드가 남는다면 요청은 서버까지 도달한 것입니다. 반대로 DNS 해석이나 TCP 연결 단계에서 실패하면 HTTP 응답 자체가 없습니다.
DNS 문제 원인 추적 과정
처음에는 애플리케이션 로그만 보고 접근했습니다. 특정 API 호출에서 timeout이 늘어났고, 재시도하면 일부는 성공했습니다. 이런 패턴은 외부 API의 일시적인 불안정처럼 보이기 쉽습니다.
하지만 같은 시간대에 여러 서버에서 비슷한 문제가 발생했다는 점이 단서였습니다. 특정 인스턴스 하나의 문제라면 서버 리소스나 로컬 네트워크 설정을 의심할 수 있습니다. 그런데 여러 서버에서 동시에 발생했다면 공통 의존성을 봐야 합니다.
1. HTTP 응답 코드가 있는지 먼저 확인
장애를 볼 때 가장 먼저 확인한 것은 실패한 요청에 HTTP status code가 남아 있는지였습니다. 4xx나 5xx가 있다면 상대 서버가 응답했다는 뜻입니다. 반대로 응답 코드가 없고 예외만 있다면 네트워크 하위 계층을 봐야 합니다.
HTTP 500 → 요청이 서버까지 도달했고 서버가 응답함
HTTP 429 → 요청이 서버까지 도달했고 제한 정책에 걸림
timeout without status → DNS, TCP 연결, TLS, 네트워크 경로 문제 가능성
UnknownHostException → DNS 해석 실패 가능성 높음
이 구분만 해도 원인 추적 범위가 많이 줄어듭니다. HTTP 문제인지, 네트워크 문제인지, DNS 문제인지 방향을 잡을 수 있기 때문입니다.
2. 서버 내부에서 직접 DNS 조회 확인
다음으로 애플리케이션 서버 안에서 직접 DNS 조회를 확인했습니다. 로컬 PC에서는 정상인데 서버에서는 실패하는 경우가 있기 때문입니다. 특히 컨테이너, Kubernetes, 사내 DNS, VPC DNS를 쓰는 환경에서는 서버가 바라보는 DNS 경로가 로컬과 다를 수 있습니다.
nslookup api.example.com
dig api.example.com
dig api.example.com +trace
curl -v https://api.example.com/health
여기서 봐야 할 것은 단순히 성공 여부만이 아닙니다. DNS 응답 시간이 비정상적으로 긴지, 응답 IP가 기대한 대역인지, 특정 서버에서만 실패하는지, IPv4와 IPv6 응답이 다르게 나오는지도 함께 확인해야 합니다.
3. 컨테이너와 호스트의 DNS 설정 비교
Docker나 Kubernetes 환경에서는 호스트에서 DNS 조회가 정상이어도 컨테이너 내부에서는 다르게 동작할 수 있습니다. 컨테이너는 별도의 /etc/resolv.conf를 가질 수 있고, 클러스터 DNS를 거쳐 외부 도메인을 조회하는 구조일 수 있습니다.
cat /etc/resolv.conf
nameserver 10.0.0.2
options ndots:5
search default.svc.cluster.local svc.cluster.local cluster.local
특히 Kubernetes에서는 ndots 설정 때문에 외부 도메인 조회 전에 내부 search domain을 여러 번 붙여 조회하는 경우가 있습니다. 도메인 조회가 많거나 DNS 서버가 느린 상황에서는 이 차이가 장애 원인 추적에서 중요해집니다.
DNS 장애에서 헷갈렸던 부분
DNS 문제는 겉으로 보기에는 애플리케이션 장애처럼 보입니다. HTTP client timeout, 외부 API 실패율 증가, 재시도 증가 같은 지표가 먼저 보이기 때문입니다. 그래서 초반에 애플리케이션 코드나 외부 API 상태만 보면 원인을 놓치기 쉽습니다.
HTTP timeout이 모두 같은 의미는 아닙니다
timeout이라는 단어가 같아도 발생 위치는 다를 수 있습니다. DNS 조회가 늦어서 timeout이 날 수도 있고, TCP 연결이 늦을 수도 있으며, 서버에 요청을 보낸 뒤 응답을 기다리다가 read timeout이 날 수도 있습니다.
DNS lookup timeout
→ 도메인을 IP로 바꾸는 단계에서 지연
connection timeout
→ IP는 알지만 TCP 연결이 지연 또는 실패
read timeout
→ 연결 후 응답을 기다리는 중 지연
로그가 이 구분을 충분히 보여주지 않으면 장애 대응이 길어집니다. 그래서 외부 API 호출 로그에는 가능하면 URL, host, status code, exception type, elapsed time을 함께 남기는 편이 좋습니다.
재시도 성공이 정상 상태를 의미하지는 않습니다
DNS 장애가 간헐적으로 발생하면 재시도에서 성공하는 경우가 많습니다. 이 때문에 “재시도하면 되니까 큰 문제는 아니다”라고 판단하기 쉽습니다. 하지만 재시도는 사용자의 응답 시간을 늘리고, 외부 API 호출량도 증가시킵니다.
재시도는 장애를 완전히 해결하는 장치가 아니라 충격을 줄이는 장치입니다. DNS 조회 실패율이 올라간 상태에서 무리하게 재시도 횟수만 늘리면 오히려 문제를 더 복잡하게 만들 수 있습니다.
DNS 문제 해결을 위해 비교했던 대안
DNS 장애를 확인한 뒤에는 몇 가지 대안을 비교했습니다. 단순히 timeout을 늘리는 방식은 당장의 실패를 줄일 수는 있지만, 원인을 해결하는 방법은 아닙니다. 그래서 애플리케이션 설정, DNS 캐시, 인프라 DNS 경로를 나누어 봤습니다.
대안 1. HTTP client timeout 조정
가장 빠르게 적용할 수 있는 방법은 HTTP client의 connection timeout과 read timeout을 조정하는 것입니다. 하지만 DNS 조회 자체가 불안정하다면 timeout을 늘리는 것만으로는 한계가 있습니다.
connectTimeout = 3s
readTimeout = 5s
retry = 1~2회
timeout은 너무 짧아도 문제고, 너무 길어도 문제입니다. 짧으면 일시적인 네트워크 지연에도 실패가 늘고, 길면 장애 상황에서 요청 스레드가 오래 묶입니다. 그래서 서비스의 호출 성격에 맞게 조정해야 합니다.
대안 2. JVM DNS 캐시 설정 확인
Java 애플리케이션에서는 JVM DNS 캐시도 확인해야 합니다. DNS 결과를 너무 오래 들고 있으면 IP 변경을 늦게 반영할 수 있고, 반대로 캐시가 거의 없으면 매번 DNS 조회가 발생해 DNS 서버 의존도가 커질 수 있습니다.
-Dsun.net.inetaddr.ttl=60
-Dsun.net.inetaddr.negative.ttl=10
여기서 inetaddr.ttl은 성공한 DNS 조회 결과를 얼마나 캐시할지와 관련이 있습니다. negative.ttl은 실패한 DNS 조회 결과를 캐시하는 시간입니다. 실패 결과를 길게 캐시하면 일시적인 DNS 실패가 애플리케이션에서 더 오래 지속될 수 있습니다.
대안 3. DNS 경로와 resolver 안정화
가장 중요하게 본 부분은 애플리케이션 서버가 실제로 어떤 DNS resolver를 바라보고 있는지였습니다. 같은 도메인이라도 로컬 PC, EC2, 컨테이너, Kubernetes Pod에서 조회 경로가 다를 수 있습니다.
운영 환경에서는 DNS resolver가 단일 장애 지점처럼 동작하지 않도록 구성해야 합니다. 내부 DNS, VPC DNS, 클러스터 DNS, 외부 DNS로 이어지는 경로 중 어디에서 지연이 생기는지 확인하고, 필요한 경우 캐시 DNS나 resolver 설정을 조정해야 합니다.
실제 적용한 해결 방법
이번 경우에는 애플리케이션 코드 수정만으로 끝내지 않았습니다. HTTP client timeout과 retry 설정을 정리하면서, 동시에 DNS 조회 경로를 확인하고 JVM DNS 캐시 설정을 명시했습니다.
HTTP client 설정을 명확히 분리
기존에는 timeout 설정이 기본값에 가깝게 남아 있어 장애 상황에서 어디까지 기다리는지 명확하지 않았습니다. 그래서 connection timeout, read timeout, retry 정책을 외부 API별로 분리했습니다.
외부 결제 API
- connection timeout: 짧게
- read timeout: API 특성에 맞게
- retry: 멱등성이 보장되는 요청에만 제한적으로 적용
조회성 API
- connection timeout: 짧게
- read timeout: 짧게
- retry: 1회 정도로 제한
특히 결제나 정산처럼 중복 호출이 민감한 API는 재시도 기준을 더 엄격하게 잡아야 합니다. DNS 문제 때문에 요청이 실패한 것처럼 보여도 실제로 상대 서버에 도달했는지 애매한 경우가 있기 때문입니다.
DNS 캐시 TTL을 운영 기준에 맞게 조정
DNS 캐시는 무조건 길게 가져가는 것이 답은 아닙니다. 외부 API 제공자가 IP를 바꾸거나 로드밸런서 구성을 변경할 수 있기 때문입니다. 반대로 캐시가 너무 짧으면 DNS resolver 장애에 애플리케이션이 민감해집니다.
JAVA_TOOL_OPTIONS="
-Dsun.net.inetaddr.ttl=60
-Dsun.net.inetaddr.negative.ttl=10
"
이 값은 서비스마다 다르게 봐야 합니다. IP 변경이 잦은 외부 연동이라면 너무 긴 캐시는 위험할 수 있고, 안정적인 도메인이라면 짧은 캐시가 불필요한 DNS 조회를 늘릴 수 있습니다.
로그에 DNS와 네트워크 단서를 남김
장애 이후에는 외부 호출 로그를 조금 더 쓸모 있게 바꿨습니다. 단순히 “API 호출 실패”만 남기면 다음 장애에서도 같은 분석을 반복하게 됩니다.
external_api=payment
host=api.example.com
method=POST
path=/payments
status=none
exception=UnknownHostException
elapsed_ms=1203
retry_count=1
status가 없는 실패와 status가 있는 실패를 구분해서 볼 수 있게 되면 원인 추적이 빨라집니다. 운영에서는 로그의 양보다 해석 가능한 구조가 더 중요합니다.
DNS 장애 이후 남긴 체크리스트
HTTP, 네트워크, DNS 장애는 다시 발생할 수 있습니다. 그래서 한 번 겪은 뒤에는 장애 대응 문서에 확인 순서를 남겨두는 편이 좋습니다. 기억에 의존하면 비슷한 문제가 다시 왔을 때 같은 실수를 반복하기 쉽습니다.
1. HTTP status code가 남았는가?
2. UnknownHostException 또는 DNS 관련 예외가 있는가?
3. 서버 내부에서 dig/nslookup 결과가 정상인가?
4. 컨테이너 내부 resolv.conf는 기대한 설정인가?
5. 특정 서버에서만 발생하는가, 전체 서버에서 발생하는가?
6. JVM DNS cache TTL은 어떻게 설정되어 있는가?
7. retry가 장애를 완화하는가, 호출량을 더 늘리는가?
8. 외부 API 제공자의 DNS 또는 endpoint 변경 공지가 있었는가?
이 체크리스트는 특별한 도구보다 효과가 있었습니다. 장애 상황에서는 복잡한 분석보다 기본 순서를 빠르게 밟는 것이 더 낫습니다.
DNS 문제를 예방하기 위한 운영 기준
DNS 문제를 완전히 없앨 수는 없습니다. 대신 장애로 번지는 범위를 줄이고, 문제가 생겼을 때 빠르게 구분할 수 있도록 준비해야 합니다.
외부 API 호출은 항상 네트워크 실패를 전제로 설계합니다
외부 API는 언제든 실패할 수 있습니다. DNS, TCP, TLS, HTTP 어느 단계에서든 문제가 생길 수 있습니다. 그래서 timeout, retry, fallback, 멱등성 기준을 함께 봐야 합니다.
특히 결제나 주문처럼 중복 요청이 문제가 되는 기능에서는 retry를 쉽게 늘리면 안 됩니다. 재시도를 하더라도 요청 식별자, idempotency key, 거래 상태 조회 같은 보완 장치가 필요합니다.
DNS 조회 실패를 모니터링 대상으로 봅니다
애플리케이션 오류율만 보는 것보다 예외 유형을 나누어 보는 것이 좋습니다. UnknownHostException, connection timeout, read timeout, HTTP 5xx를 같은 실패로 묶으면 원인 파악이 늦어집니다.
실무에서는 실패율 그래프 하나보다 실패의 종류를 나눈 지표가 더 쓸모 있습니다. 같은 1% 실패율이라도 HTTP 500인지, DNS 조회 실패인지에 따라 대응 방식이 완전히 달라집니다.
서버 안에서 확인하는 습관이 필요합니다
로컬 PC에서 curl이 성공한다고 운영 서버에서도 성공한다고 보면 안 됩니다. 운영 서버는 다른 DNS resolver, 다른 네트워크 경로, 다른 방화벽 정책을 사용할 수 있습니다.
# 서버 내부에서 확인
dig api.example.com
curl -v https://api.example.com/health
# 컨테이너 내부에서 확인
docker exec -it app-container sh
cat /etc/resolv.conf
nslookup api.example.com
문제가 발생한 위치에서 직접 확인해야 합니다. 이 원칙을 지키지 않으면 로컬에서는 정상인데 운영에서는 실패하는 문제를 오래 붙잡게 됩니다.
정리: DNS 장애는 HTTP 로그 뒤에 숨어 있습니다
DNS 문제는 겉으로 드러날 때 HTTP 장애처럼 보이는 경우가 많습니다. 외부 API timeout, 네트워크 오류, 간헐적 실패라는 형태로 나타나기 때문입니다. 하지만 HTTP 응답 코드가 없는 실패라면 DNS 조회, TCP 연결, TLS 단계까지 함께 확인해야 합니다.
이번 경험에서 얻은 가장 큰 기준은 단순합니다. HTTP status code가 있으면 HTTP 레벨에서 보고, status code가 없다면 그 아래 네트워크 계층을 먼저 봅니다. 그리고 서버 내부에서 DNS 조회를 직접 확인합니다.
DNS는 평소에는 눈에 잘 보이지 않지만, 장애가 나면 서비스 전체의 외부 연동을 흔들 수 있습니다. 그래서 timeout 설정, JVM DNS 캐시, resolver 경로, 예외 로그를 미리 정리해두는 것이 좋습니다.
'개발 > 기타' 카테고리의 다른 글
| Gateway Timeout 발생했을 때 대응 방법: HTTP 504 원인 분석과 실무 점검 순서 (0) | 2026.06.08 |
|---|---|
| API retry 로직 잘못 짜서 장애 난 사례: http 네트워크 오류를 안전하게 다루는 방법 (1) | 2026.06.07 |
| API 호출 Latency 갑자기 증가한 원인 분석: HTTP, 네트워크, 외부 API 문제를 구분하는 방법 (0) | 2026.06.05 |
| HTTP Keep-Alive 설정 안 해서 생긴 성능 문제와 해결 과정 (0) | 2026.06.04 |
| 외부 API 호출 시 간헐적 실패 원인 분석: HTTP API 장애를 추적하는 실무 기준 (0) | 2026.06.02 |
