왜 DNS 응답을 손봐야 하는가
쿠버네티스 클러스터를 운영하다 보면 표준 DNS 해석만으로는 부족한 순간이 온다. 특정 외부 도메인을 내부 프록시로 강제 우회시켜야 하거나, 레거시 온프레미스 존을 조건부로 포워딩해야 하거나, 대량의 외부 조회로 인한 지연을 캐시로 줄여야 하는 경우다. 이럴 때 흔히 kube-dns 설정을 통째로 교체하거나 애플리케이션 코드에 하드코딩된 IP를 박아 넣는데, 이는 유지보수 지옥으로 가는 지름길이다.
CoreDNS는 요청을 플러그인 체인으로 처리한다. 각 요청이 Corefile에 선언된 순서대로 플러그인을 통과하므로, 코드 변경 없이 설정만으로 응답을 정교하게 제어할 수 있다.
플러그인 체인의 동작 원리
핵심은 "선언 순서 ≠ 실행 순서"라는 점이다. Corefile에 적은 순서와 무관하게 CoreDNS는 내부 plugin.cfg에 정의된 우선순위로 체인을 구성한다. 예를 들어 cache는 항상 forward보다 앞에 놓여 캐시 히트 시 업스트림 조회를 건너뛴다. 각 플러그인은 요청을 처리하고 다음으로 넘기거나(Next), 즉시 응답을 반환한다. 이 흐름을 이해하지 못하면 "설정은 맞는데 왜 안 먹지"를 반복하게 된다.
rewrite로 도메인 응답 조작하기
가장 자주 쓰는 것이 rewrite 플러그인이다. 요청 이름이나 응답을 질의 시점에 바꿔치기한다. 아래는 외부 도메인 조회를 내부 서비스로 돌리는 예시다.
. {
errors
health
# 외부 도메인을 클러스터 내부 서비스명으로 재작성
rewrite name api.example.com api-proxy.default.svc.cluster.local
# 정규식으로 특정 서브도메인 전체를 흡수
rewrite name regex (.*)\.legacy\.internal {1}.svc.cluster.local
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
cache 30
forward . /etc/resolv.conf
loop
reload
}
reload를 넣어두면 ConfigMap 변경 시 프로세스 재시작 없이 반영된다. 다만 rewrite는 응답의 TTL이나 추가 레코드까지 세밀하게 다루기 어려우니, 복잡한 로직은 별도 플러그인으로 분리하는 편이 낫다.
조건부 포워딩과 캐시 조합
존별로 다른 업스트림을 지정하고 캐시를 계층적으로 두면 지연과 부하를 동시에 잡을 수 있다.
corp.internal:53 {
cache 300
forward . 10.0.0.10 10.0.0.11 {
policy sequential
health_check 5s
}
}
. {
cache 30
forward . 8.8.8.8 1.1.1.1 {
policy random
}
}
policy sequential은 첫 서버 우선, random은 부하 분산에 유리하다. health_check로 죽은 업스트림을 자동 배제한다.
필요하면 직접 플러그인을 작성한다
기본 플러그인으로 부족하면 외부 플러그인을 컴파일해 넣는다. Go로 작성하며 ServeDNS 하나만 구현하면 체인에 참여한다.
func (p Blocklist) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
qname := r.Question[0].Name
if p.blocked[qname] {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN 반환
w.WriteMsg(m)
return dns.RcodeNameError, nil
}
// 처리 대상이 아니면 다음 플러그인으로
return plugin.NextOrFailure(p.Name(), p.Next, ctx, w, r)
}
여기서 NextOrFailure 호출을 빠뜨리면 체인이 끊겨 뒤쪽 플러그인이 통째로 무력화된다. 가장 흔한 실수다.
방식 비교
| 방식 | 변경 범위 | 적합한 상황 |
|---|---|---|
| rewrite | 설정만 | 단순 도메인 매핑 |
| 조건부 forward | 설정만 | 존별 라우팅·하이브리드 |
| 커스텀 플러그인 | 재컴파일 필요 | 동적 로직·외부 연동 |
운영 시 주의점
첫째, loop 플러그인을 반드시 활성화하라. 재작성이 자기 자신을 다시 조회하는 무한 루프를 조기에 탐지해준다. 둘째, 캐시 TTL을 너무 길게 잡으면 백엔드 IP가 바뀌어도 오래된 응답이 살아남는다. 서비스 디스커버리 대상은 30초 내외가 안전하다. 셋째, 커스텀 플러그인 배포 전 dnstap이나 log 플러그인으로 실제 응답을 검증하라. 넷째, ConfigMap을 수정할 때는 errors 플러그인 로그를 함께 지켜봐야 문법 오류로 전체 DNS가 멈추는 사고를 막을 수 있다. 플러그인 체인은 강력하지만, 그만큼 한 줄의 실수가 클러스터 전체 이름 해석을 마비시킬 수 있음을 잊지 말아야 한다.