Cloud Map private DNS에 .local을 붙였더니 왜 조회가 실패했을까
"Cloud Map namespace도 만들었고 Route 53 Private Hosted Zone도 생겼는데, 왜 EC2에서 DNS 조회가 실패할까?"
AWS Cloud Map private DNS를 붙이다가 이런 문제를 만났습니다.
Cloud Map 리소스는 정상적으로 생성됐습니다. Route 53 Private Hosted Zone도 만들어졌고, 서비스 인스턴스도 등록되어 있었습니다. 그런데 정작 Caddy가 실행되는 EC2에서 DNS를 조회하면 실패했습니다.
결론부터 말하면, 원인은 Cloud Map이 아니라 DNS 이름 끝에 붙인 .local 이었습니다. 이번 글은 이 문제만 가볍게 정리해봅니다.
환경
항목 내용
| Service Discovery | AWS Cloud Map private DNS |
| 실행 환경 | EC2, Ubuntu 계열 |
| Resolver | systemd-resolved |
| 처음 namespace | internal.tasteam.local |
| 처음 조회한 이름 | spring.internal.tasteam.local |
| 증상 | 기본 DNS 조회 시 SERVFAIL |
| 해결 | .local 제거 후 internal.tasteam 사용 |
문제 상황
처음에는 Cloud Map private DNS namespace를 이렇게 만들었습니다.
internal.tasteam.local
그리고 Caddy upstream에서는 Spring Boot 인스턴스를 이 이름으로 바라보게 하려고 했습니다.
spring.internal.tasteam.local:8080
Cloud Map service와 Route 53 Private Hosted Zone은 정상적으로 생성됐습니다. 그래서 당연히 EC2에서도 이름이 풀릴 거라고 생각했습니다.
하지만 Caddy 서버에서 직접 조회해보면 실패했습니다.
nslookup spring.internal.tasteam.local
결과는 SERVFAIL이었습니다.
이상했던 점은 VPC resolver를 직접 지정하면 응답이 왔다는 점입니다.
nslookup spring.internal.tasteam.local 10.11.0.2
즉, Cloud Map이나 Route 53 Private Hosted Zone 자체가 깨진 상황은 아니었습니다.
처음 의심한 지점
처음에는 Cloud Map 설정을 의심했습니다.
- namespace가 VPC에 제대로 연결됐는지
- service에 A 레코드가 있는지
- 인스턴스가 Cloud Map에 등록됐는지
- EC2의 VPC DNS 설정이 켜져 있는지
그런데 리소스 상태는 정상이었습니다.
특히 VPC resolver를 직접 찍었을 때 응답이 온다는 점이 중요했습니다.
기본 조회: 실패
VPC resolver 직접 조회: 성공
이 차이가 말해주는 건 하나였습니다. 문제는 Cloud Map에 레코드가 없다는 게 아니라, 호스트의 기본 DNS 해석 경로가 이 이름을 VPC DNS로 보내지 않고 있다는 점이었습니다.
실제 원인
문제는 .local이었습니다.
.local은 일반적인 사내 도메인처럼 아무 데나 붙여도 되는 suffix가 아닙니다. mDNS, 즉 Multicast DNS에서 특별하게 다루는 이름입니다. mDNS는 중앙 DNS 서버 없이 같은 로컬 네트워크 안에서 이름을 찾기 위한 방식입니다.
그래서 Ubuntu 계열에서 많이 쓰는 systemd-resolved는 .local로 끝나는 이름을 일반 DNS 서버로 보내지 않을 수 있습니다. 일반적인 Route 53 private DNS 이름처럼 처리되지 않고, mDNS 쪽 이름으로 취급될 수 있는 거죠.
이 상황을 단순화하면 아래처럼 볼 수 있습니다.
spring.internal.tasteam.local 조회
↓
호스트의 기본 resolver가 .local을 특별 취급
↓
VPC DNS로 정상 질의되지 않음
↓
SERVFAIL 또는 조회 실패
반대로 VPC resolver를 직접 지정하면 중간 resolver 판단을 건너뛰기 때문에 응답이 왔습니다.
nslookup spring.internal.tasteam.local 10.11.0.2
이건 Cloud Map 레코드가 존재한다는 뜻입니다. 다만 기본 조회 경로에서는 .local 때문에 원하는 DNS 서버까지 질의가 도달하지 못한 것입니다.
해결 방법
가장 단순한 해결은 .local을 쓰지 않는 것이었습니다. 저희는 namespace를 아래처럼 바꿨습니다.
변경 전: internal.tasteam.local
변경 후: internal.tasteam
Caddy upstream도 같이 바꿨습니다.
변경 전: spring.internal.tasteam.local:8080
변경 후: spring.internal.tasteam:8080
이후 기본 조회 경로에서도 정상적으로 이름이 풀렸습니다.
getent hosts spring.internal.tasteam
Cloud Map private DNS를 내부용으로 쓴다고 해서 꼭 .local을 붙일 필요는 없었습니다. 오히려 .local을 붙이면서 호스트 resolver와 충돌할 가능성이 생겼습니다.
확인 방법
비슷한 문제가 생기면 아래 순서로 확인하면 됩니다.
1. 기본 resolver로 조회하기
getent hosts spring.internal.tasteam.local
또는 아래처럼 확인합니다.
nslookup spring.internal.tasteam.local
여기서 실패하면 애플리케이션이나 Caddy를 보기 전에 DNS 해석부터 의심합니다.
2. VPC resolver를 직접 지정해서 조회하기
nslookup spring.internal.tasteam.local 10.11.0.2
여기서 성공하면 Cloud Map 레코드는 존재하는 것입니다. 즉, 문제는 레코드 생성이 아니라 호스트의 기본 DNS 경로일 가능성이 큽니다.
3. resolver 상태 보기
Ubuntu 계열이라면 아래 명령으로 systemd-resolved 상태를 볼 수 있습니다.
resolvectl status
기본 DNS 서버, search domain, routing domain, mDNS 설정을 같이 확인합니다.
4. .local 제거 후 다시 조회하기
가능하다면 Cloud Map namespace에서 .local을 제거한 이름으로 다시 구성합니다.
internal.tasteam.local 피함
internal.tasteam 사용
그리고 실제 애플리케이션이 실행되는 호스트에서 조회합니다.
getent hosts spring.internal.tasteam
여기서 정상적으로 private IP가 나오면 Caddy upstream으로 사용할 준비가 된 것입니다.
주의할 점
.local을 쓰면 무조건 모든 환경에서 실패한다는 뜻은 아닙니다. resolver 설정에 따라 .local을 특정 DNS 서버로 보내도록 강제로 구성할 수도 있습니다.
하지만 Cloud Map private DNS처럼 운영 환경에서 안정적으로 조회되어야 하는 이름이라면, 굳이 mDNS와 겹치는 suffix를 선택할 이유가 없었습니다.
특히 서버나 컨테이너에서 사용할 내부 DNS 이름이라면 아래처럼 보는 편이 안전했습니다.
선택 판단
| internal.tasteam.local | mDNS와 충돌할 수 있어 피하는 편이 좋음 |
| internal.tasteam | 실제 사용한 방식 |
핵심은 "내부 도메인처럼 보여서 .local을 붙였다"가 문제였다는 점입니다.
돌아보며
이번 문제는 Cloud Map 설정 문제가 아니었습니다. 리소스는 정상적으로 만들어졌고, VPC resolver에 직접 물어보면 응답도 왔습니다. 하지만 실제 애플리케이션은 VPC resolver에 직접 질의하는 게 아니라 호스트의 기본 resolver 경로를 탑니다.
그래서 Cloud Map private DNS를 붙일 때는 아래 두 가지를 나눠서 봐야 했습니다.
Cloud Map에 레코드가 있는가?
실제 서버의 기본 DNS 경로에서 그 이름이 풀리는가?
.local은 내부용이라는 느낌 때문에 자연스럽게 붙이기 쉽습니다. 하지만 Linux 서버 환경에서는 mDNS와 엮이면서 예상과 다르게 동작할 수 있습니다.
Cloud Map private DNS namespace를 만들 때는 .local을 피하고, 실제 애플리케이션이 실행되는 호스트에서 getent hosts로 먼저 확인하는 습관이 가장 빠른 해결책이었습니다.
참고
'AWS' 카테고리의 다른 글
| Docker 컨테이너에서만 EC2 IAM Role 자격증명을 못 가져온 이유 IMDSv2 Hop Limit 트러블슈팅 (0) | 2026.03.01 |
|---|