/인프라/Temporary failure in name resolution 해결: 호스트·Docker·K8s DNS 진단 런북
InfrastructureDNS트러블슈팅systemd-resolved

Temporary failure in name resolution 해결: 호스트·Docker·K8s DNS 진단 런북

This article is not yet available in English. Showing the original Korean version.

Temporary failure in name resolution, Could not resolve host, SERVFAIL, NXDOMAIN을 에러 문구만 보고 원인 계층을 판정하는 표와 30초 진단 명령 5개. resolv.conf, Docker daemon.json, K8s ndots·CoreDNS 처방까지 복붙으로 해결하세요.

Temporary failure in name resolution 해결: 호스트·Docker·K8s DNS 진단 런북

지금 본 에러 문구를 그대로 찾으세요

어제까지 잘 돌던 서버에서 apt-get updateTemporary failure in name resolution으로 죽거나, 컨테이너를 재시작했더니 curl: (6) Could not resolve host가 뜨는 상황이라면 제대로 찾아왔습니다. 이 글은 DNS 개념 설명서가 아니라 에러 문구 → 명령어 → 처방만 이어지는 런북입니다. 개념(재귀 질의, CoreDNS 플러그인 구조 등)은 다루지 않습니다.

적용 범위: Ubuntu 22.04/24.04·Debian 12·RHEL 9 계열 호스트, Docker Engine 2427, Kubernetes 1.281.31. 바로 아래 판정표부터 보세요.

에러 원문 판정표: 5개 문구, 5개 계층

에러 원문 (그대로)1순위 의심 계층첫 명령어
Temporary failure in name resolution (EAI_AGAIN)로컬 리졸버 설정 부재/빈 파일, 네트워크 스택cat /etc/resolv.conf
curl: (6) Could not resolve host: example.com애플리케이션 레벨 해석 실패 — search 도메인, 오타, 컨테이너 상속getent hosts example.com
dig 응답의 status: SERVFAIL업스트림은 응답하나 해석 실패 — DNSSEC, forward 대상 장애dig @8.8.8.8 example.com
dig 응답의 status: NXDOMAIN도메인 부재/오타, search 도메인이 붙은 엉뚱한 FQDN 질의dig +search +trace example.com
;; connection timed out; no servers could be reached53/UDP 차단 — 방화벽·보안그룹·NetworkPolicync -zvu <dns_ip> 53

읽는 순서: 위 표에서 내 문구를 찾았으면 다음 장의 5단계 진단을 그대로 위에서 아래로 실행합니다. 5단계는 어떤 문구든 공통으로 통과시키는 계층 분리기입니다.

30초 계층 분리 진단 트리

cat /etc/resolv.conf — 리졸버 주소가 있긴 한가

Bash
cat /etc/resolv.conf
ls -l /etc/resolv.conf

정상 출력 (systemd-resolved 스택)

TEXT
# This is /run/systemd/resolve/stub-resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
search ap-northeast-2.compute.internal

이상 출력 예시

TEXT
# 케이스 A: 아예 비어 있음 (출력 없음)
# 케이스 B: search만 있고 nameserver 줄이 없음
search example.internal
# 케이스 C: 심볼릭 링크가 깨짐
lrwxrwxrwx 1 root root 39 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
cat: /etc/resolv.conf: No such file or directory

케이스 A·B·C는 곧바로 Temporary failure in name resolution을 만듭니다. 임시 복구는 아래 한 줄이지만, 재부팅·DHCP 갱신 때 덮어써지므로 반드시 4장의 영구 설정까지 진행해야 합니다.

Bash
# 임시 복구 (재부팅 시 사라짐)
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

resolvectl status — stub 리졸버가 업스트림을 알고 있는가

Bash
resolvectl status

정상 출력 (일부 발췌)

TEXT
Link 2 (eth0)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.0.0.2
       DNS Servers: 10.0.0.2
        DNS Domain: ap-northeast-2.compute.internal

이상 출력

TEXT
Link 2 (eth0)
    Current Scopes: none          <- 업스트림 없음
       DNS Servers:               <- 비어 있음
         Protocols: ... DNSSEC=yes/supported   <- 사내 DNS에서 SERVFAIL 유발 가능

Current Scopes: none이면 리졸버가 물어볼 곳을 모르는 상태입니다. DNSSEC=yes인데 사내/폐쇄망 DNS를 쓰고 있다면 SERVFAIL의 유력 용의자입니다.

dig @8.8.8.8 vs dig — 핵심 분기점

Bash
dig +short @8.8.8.8 example.com
dig +short example.com
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
관찰 결과판정이동할 처방
@8.8.8.8만 성공, 그냥 dig는 실패로컬 리졸버/resolv.conf 문제호스트 처방
둘 다 실패 (connection timed out)네트워크·방화벽 53 차단⑤번 + 보안그룹
둘 다 실패 (SERVFAIL)업스트림 장애 또는 DNSSECresolved.conf DNSSEC=false 검토
둘 다 성공인데 curl만 실패NSS·프록시·컨테이너 상속 문제Docker/프록시 처방
둘 다 성공, curl도 성공인데 앱만 실패애플리케이션 내부 캐시/설정앱 재기동, no_proxy 확인

dig/etc/nsswitch.conf를 거치지 않고 직접 UDP 질의를 던지므로, dig는 되는데 curl/ping이 안 되면 NSS 계층 또는 프록시 환경변수를 봐야 합니다. 이때 대조용으로 아래를 함께 칩니다.

Bash
getent hosts example.com      # NSS 경로를 그대로 타는 조회

ss -lunp | grep :53 — stub 리졸버가 실제로 떠 있는가

Bash
sudo ss -lunp | grep :53

정상

TEXT
UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=13))

이상: 출력이 아예 없다면 resolv.conf는 127.0.0.53을 가리키는데 거기서 듣는 프로세스가 없는 상태입니다. 전형적인 Temporary failure in name resolution 조합입니다.

Bash
systemctl status systemd-resolved
sudo systemctl enable --now systemd-resolved

nc -zvu + dig +tcp — UDP만 막혔는가, TCP도 막혔는가

Bash
nc -zvu 10.0.0.2 53          # UDP 53 도달성
dig +tcp @10.0.0.2 example.com   # TCP 53 도달성

정상: Connection to 10.0.0.2 53 port [udp/domain] succeeded! 이상: 무응답 후 타임아웃 → 보안그룹/iptables/NetworkPolicy에서 53 아웃바운드가 막힌 것입니다. UDP는 비연결형이라 nc -zvu가 오탐을 낼 수 있으므로 dig +tcp 결과와 반드시 교차 확인합니다.

판정 결과 → 처방 매핑

진단 결과처방 위치
resolv.conf 비어 있음/링크 깨짐호스트 — netplan / nmcli / resolved.conf
Current Scopes: none호스트 — 업스트림 명시
SERVFAIL + DNSSEC=yes호스트 — DNSSEC=false
호스트는 OK, 컨테이너만 실패Docker — daemon.json "dns"
Pod 지연/간헐 실패, 외부 도메인K8s — ndots 튜닝
모든 Pod가 동시에 실패K8s — NetworkPolicy egress 53

계층별 처방 ① 호스트

stub-resolv.conf vs resolv.conf, 무엇을 가리켜야 하나

링크 대상내용쓸 때
/run/systemd/resolve/stub-resolv.confnameserver 127.0.0.53기본값. 캐시·per-link DNS·분할 DNS를 쓸 때
/run/systemd/resolve/resolv.conf실제 업스트림 IP 직접 기재컨테이너 런타임이나 앱이 127.0.0.53을 못 쓰는 경우

두 번째 형태로 바꾸려면 다음과 같이 링크를 교체합니다. 파일을 직접 편집하지 말고 링크를 바꾸는 것이 핵심입니다.

Bash
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
resolvectl status | head -20

영구 설정 3가지 방식 — 어느 환경에서 무엇을 쓰나

방식적합 환경판단 기준
netplanUbuntu 18.04+ 서버, 클라우드 인스턴스/etc/netplan/*.yaml이 존재하면 여기가 진짜 소스
nmcli (NetworkManager)RHEL/Rocky 9, 데스크톱, Ubuntu Desktopnmcli 명령이 존재하고 NetworkManager.service active
resolved.conf인터페이스와 무관한 전역 폴백을 걸 때DHCP가 주는 DNS 외에 전역 기본값이 필요할 때

netplan (Ubuntu 22.04/24.04 서버)

YAML
# /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      dhcp4-overrides:
        use-dns: false
      nameservers:
        addresses: [10.0.0.2, 1.1.1.1]
        search: [example.internal]
Bash
sudo chmod 600 /etc/netplan/01-netcfg.yaml
sudo netplan generate && sudo netplan apply
resolvectl status | grep -A2 'Link 2'

nmcli (RHEL 9 / NetworkManager)

Bash
nmcli con show
sudo nmcli con mod "System eth0" ipv4.dns "10.0.0.2 1.1.1.1"
sudo nmcli con mod "System eth0" ipv4.dns-search "example.internal"
sudo nmcli con mod "System eth0" ipv4.ignore-auto-dns yes
sudo nmcli con up "System eth0"

ipv4.ignore-auto-dns yes를 빼면 DHCP가 주는 DNS가 계속 앞에 붙어 설정이 무력화됩니다.

systemd-resolved 전역 설정

INI
# /etc/systemd/resolved.conf
[Resolve]
DNS=10.0.0.2 10.0.0.3
FallbackDNS=1.1.1.1
Domains=~.
DNSSEC=false
DNSStubListener=yes
Bash
sudo systemctl restart systemd-resolved
resolvectl status
resolvectl query example.com

Domains=~.는 "모든 도메인을 이 링크의 DNS로 보낸다"는 라우팅 지시로, VPN이나 다중 인터페이스에서 질의가 엉뚱한 링크로 새는 것을 막습니다.

DNSSEC=false를 언제 켜나: 공인 DNS에서는 정상인데 사내 DNS로 물으면 SERVFAIL이 나고, resolvectl statusDNSSEC=yes가 보이며, dig +cd(체크 비활성)로 질의하면 성공하는 3박자가 맞으면 사내 존의 DNSSEC 서명 미구성이 원인일 가능성이 큽니다. 인터넷 구간 검증을 포기하는 설정이므로 폐쇄망/사내 전용 구간에 한정해 적용하고, 변경 근거를 기록으로 남기세요.

Bash
dig +cd @10.0.0.2 internal.example.com   # 성공하면 DNSSEC 검증 실패가 원인

계층별 처방 ② Docker

호스트는 되는데 컨테이너만 안 되는 메커니즘

호스트 /etc/resolv.confnameserver 127.0.0.53 하나만 있으면, 그 루프백 주소는 컨테이너 네임스페이스 안에서 의미가 없습니다. Docker는 이런 로컬 전용 주소를 걸러내고 기본 공인 DNS(8.8.8.8 등)로 대체하는데, 외부 인터넷이 막힌 폐쇄망에서는 그 순간부터 컨테이너 DNS가 전멸합니다. 확인은 컨테이너 내부에서 합니다.

Bash
docker run --rm alpine cat /etc/resolv.conf
# 이상 신호: nameserver 8.8.8.8  (사내망인데 공인 DNS가 박혀 있음)
docker run --rm alpine nslookup registry.example.internal

근본 처방: daemon.json

JSON
{
  "dns": ["10.0.0.2", "10.0.0.3"],
  "dns-search": ["example.internal"],
  "dns-opts": ["ndots:1", "timeout:2", "attempts:2"]
}
Bash
sudo vi /etc/docker/daemon.json
sudo systemctl restart docker
docker run --rm alpine cat /etc/resolv.conf   # 10.0.0.2 가 보이면 성공

상황별 대안

상황명령/설정
일회성 확인docker run --dns 10.0.0.2 --rm alpine nslookup example.com
Compose 서비스 단위아래 YAML
--network host호스트 resolv.conf를 그대로 사용 → 호스트가 되면 컨테이너도 됨. 증상이 달라지는 이유가 여기 있음
Docker Desktop / rootless내부 가상 리졸버를 거치므로 daemon.json 대신 Desktop 설정 또는 --dns로 우회
YAML
# docker-compose.yml
services:
  api:
    image: myorg/api:1.4.0
    dns:
      - 10.0.0.2
    dns_search:
      - example.internal
Bash
docker compose config | grep -A3 dns
docker compose up -d --force-recreate api

daemon.json 수정 후 이미 떠 있던 컨테이너는 재생성해야 새 resolv.conf가 반영됩니다. 재시작만으로는 바뀌지 않는 경우가 많습니다.

계층별 처방 ③ Kubernetes

ndots:5가 만드는 지연과 간헐 실패

Pod의 기본 /etc/resolv.conf는 다음과 같습니다.

TEXT
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
nameserver 10.96.0.10
options ndots:5

api.example.com은 점이 2개라 ndots:5 기준에 미달하므로, 리졸버는 먼저 search 도메인을 하나씩 붙여 api.example.com.default.svc.cluster.local 같은 질의를 45회 던지고 전부 NXDOMAIN을 받은 뒤에야 절대 질의로 넘어갑니다. 결과는 수백 ms수 초 지연과 부하 시 간헐 실패입니다.

즉효 처방 1 — FQDN에 마침표: 코드/설정의 호스트명을 api.example.com.(끝에 점)으로 바꾸면 search 단계를 건너뜁니다.

즉효 처방 2 — ndots 조정

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      dnsPolicy: ClusterFirst
      dnsConfig:
        options:
          - name: ndots
            value: "2"
          - name: timeout
            value: "2"
          - name: attempts
            value: "2"
      containers:
        - name: api
          image: myorg/api:1.4.0

ndots:2로 낮추면 클러스터 내부 서비스는 svc.ns 형태(점 1개)로 여전히 search가 적용되고, 외부 도메인은 곧바로 절대 질의로 갑니다. 다만 service-name 같은 점 없는 짧은 이름만 쓰는 코드가 있으면 영향이 없는지 스테이징에서 먼저 확인하세요.

Bash
kubectl apply -f deploy.yaml
kubectl exec -it deploy/api -- cat /etc/resolv.conf   # options ndots:2 확인

전 Pod가 동시에 죽었다면 NetworkPolicy egress

egress 정책을 걸면서 kube-dns 53을 열어주지 않으면 네임스페이스 전체가 한꺼번에 이름 해석에 실패합니다. 아래를 그대로 적용합니다.

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: prod
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

TCP 53을 빼먹으면 응답이 512바이트를 넘어 TCP로 재시도되는 질의만 골라서 실패하는, 재현이 까다로운 장애가 됩니다. Cilium 같은 eBPF 기반 CNI를 쓴다면 hubble observe --protocol dns로 드롭 지점을 직접 볼 수 있습니다.

CoreDNS 실측 로그 켜기

Bash
kubectl -n kube-system edit cm coredns
TEXT
.:53 {
    log            # 이 줄 추가
    errors
    health
    kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure }
    forward . /etc/resolv.conf
    cache 30
}
Bash
kubectl -n kube-system rollout restart deploy/coredns
kubectl logs -n kube-system -l k8s-app=kube-dns -f | grep example.com

forward . /etc/resolv.conf노드의 resolv.conf를 그대로 따른다는 뜻입니다. 노드 한 대의 DNS 설정이 오염되면 그 노드에 스케줄된 CoreDNS Pod를 통해 클러스터 전체 질의가 실패할 수 있으므로, 노드가 의심되면 업스트림을 명시로 고정합니다.

TEXT
forward . 10.0.0.2 10.0.0.3 {
    max_concurrent 1000
}

테스트 파드 한 줄:

Bash
kubectl run -it --rm dnsutils --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.7 --restart=Never -- bash
# 안에서: dig +short kubernetes.default.svc.cluster.local ; dig +short api.example.com.

CoreDNS 자체 동작을 더 깊게 봐야 한다면 Kubernetes Pod DNS 실패(CoreDNS) 5분 진단 글로 이어가세요. 지연이 반복적으로 문제가 되는 대규모 클러스터에서는 NodeLocal DNSCache 도입이 표준 대응 항목으로 자리잡았습니다.

재발 방지와 자주 하는 오진 3가지

캐시 플러시 명령 모음

Bash
resolvectl flush-caches                     # systemd-resolved
sudo systemctl restart dnsmasq              # dnsmasq
sudo nscd -i hosts                          # nscd 사용 환경
kubectl -n kube-system rollout restart deploy/coredns   # CoreDNS

판정 규칙: 캐시를 지웠는데도 증상이 그대로면 캐시는 원인이 아니었습니다. 더 이상 플러시를 반복하지 말고 진단 ③번(dig 분기점)으로 돌아가세요.

TTL 오해 바로잡기

레코드를 바꿨는데 반영이 안 되는 것은 장애가 아니라 TTL 만료 대기인 경우가 대부분입니다. 남은 TTL은 다음으로 확인합니다.

Bash
dig +noall +answer example.com
# example.com. 287 IN A 93.184.216.34   <- 287초 남음
dig +noall +answer @8.8.8.8 example.com   # 권威 경로 값과 대조

오진 3종과 반증 명령 한 줄

오진실제 원인반증 명령
DNS 서버 주소만 계속 바꿔본다53 포트가 방화벽/보안그룹에서 막힘nc -zvu 10.0.0.2 53
"해석이 5초나 걸린다 = 해석 실패"IPv6 AAAA 조회 타임아웃curl -4 https://example.com (즉시 성공하면 IPv6 문제)
사내에서만 특정 도메인이 안 풀린다프록시 환경변수와 no_proxy 누락env | grep -i proxy

세 번째 케이스의 처방:

Bash
export no_proxy="localhost,127.0.0.1,.example.internal,10.0.0.0/8,.svc,.cluster.local"
export NO_PROXY="$no_proxy"

크론에 거는 DNS 헬스체크

Bash
#!/usr/bin/env bash
# /usr/local/bin/dns-healthcheck.sh
set -u
TARGETS=("internal.example.internal" "example.com")
FAILED=()
for d in "${TARGETS[@]}"; do
  dig +short +time=2 +tries=1 "$d" | grep -qE '^[0-9]' || FAILED+=("$d")
done
[ ${#FAILED[@]} -eq 0 ] && exit 0
logger -t dns-healthcheck "DNS resolution failed: ${FAILED[*]}"
exit 1
Bash
sudo chmod +x /usr/local/bin/dns-healthcheck.sh
echo '*/5 * * * * root /usr/local/bin/dns-healthcheck.sh' | sudo tee /etc/cron.d/dns-healthcheck

내부 도메인과 외부 도메인을 각각 하나씩 두는 것이 핵심입니다. 둘 다 실패하면 리졸버/네트워크, 내부만 실패하면 사내 DNS나 forward 설정, 외부만 실패하면 업스트림·프록시 구간으로 즉시 좁혀집니다. 실패 시 알림은 logger 대신 사내 Webhook 호출로 바꿔 쓰면 됩니다.

자주 묻는 질문 (FAQ)

Q. /etc/resolv.conf를 직접 고쳤는데 재부팅하면 원래대로 돌아갑니다. A. 이 파일은 systemd-resolved 또는 NetworkManager가 자동 생성하는 결과물입니다. 소스인 netplan nameservers:, nmcli con mod ipv4.dns, /etc/systemd/resolved.confDNS= 중 해당 환경의 것을 수정한 뒤 netplan apply 또는 서비스 재시작을 해야 유지됩니다.

Q. 호스트에서는 dig가 되는데 컨테이너 안에서만 이름 해석이 안 됩니다. A. 컨테이너 내부에서 cat /etc/resolv.conf를 먼저 확인하세요. 호스트가 127.0.0.53 하나만 갖고 있으면 Docker가 이를 상속시키지 못해 공인 DNS로 대체하고, 폐쇄망에서는 그대로 실패합니다. /etc/docker/daemon.json"dns": ["10.0.0.2"]를 넣고 데몬 재시작 후 컨테이너를 재생성하면 해결됩니다.

Q. Pod에서 외부 도메인 조회가 느리고 가끔 실패합니다. DNS 서버 문제인가요? A. 서버보다 options ndots:5로 인한 search 도메인 헛질의가 원인인 경우가 많습니다. 호스트명 끝에 마침표를 붙여 FQDN으로 만들거나, dnsConfig.optionsndots:2를 지정해 확인해 보세요. 확인은 kubectl exec -- cat /etc/resolv.conf로 옵션 반영 여부를 보는 것부터 시작합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·

Comments

Be the first to comment.