728x90

내부 DNS는 인프라 하는 사람들에게 오랫동안 애매하게 방치된 영역이었다. 공개 DNS는 Cloudflare나 Route53 같은 걸 쓰고, 사내망은 CoreDNS나 BIND, 클라우드 안에서는 각 벤더의 Private Hosted Zone... 이렇게 서너 개 시스템이 각자 놀고 있는 경우가 대부분이다. 2026년 7월 GA된 Cloudflare Internal DNS는 바로 이 파편화를 하나의 제어 평면으로 묶겠다는 이야기다. 실무자 입장에서 이게 진짜 쓸만한 건지, 어디서 삽질하게 될지를 정리해봤다.

1. 왜 지금 이게 화제인가 — 내부 DNS 파편화라는 오래된 통증

혼합 환경을 굴려본 사람이라면 이 그림이 익숙할 거다.

  • 공개 도메인(example.com)은 외부 DNS 제공자
  • 사내망(*.corp.internal)은 온프레미스 CoreDNS 또는 BIND
  • AWS 안쪽은 Route53 Private Hosted Zone
  • GCP는 Cloud DNS의 private zone
  • 보안 정책은 또 별도 방화벽/프록시 계층에서

문제는 이 시스템들이 공통 제어 평면을 공유하지 않는다는 점이다. 특히 Split-horizon DNS(같은 호스트명을 내부/외부 사용자에게 다르게 응답하는 구성)를 하면 지옥문이 열린다. 내부용 뷰와 외부용 뷰를 각각 별도 시스템에서 관리하다가, 어느 한쪽만 레코드를 수정하는 순간 두 시스템이 어긋나기(drift) 시작한다. 그리고 그 drift는 꼭 금요일 저녁에 장애로 터진다.

Cloudflare가 미는 메시지는 명확하다. "공개 DNS, 내부 DNS, Zero Trust 정책을 같은 글로벌 네트워크와 하나의 API에서 다루자." 원문에 따르면 Enterprise 고객은 Cloudflare Gateway에 추가 비용 없이 포함된다고 한다. 이 부분은 계약 형태에 따라 다를 수 있으니 영업/공식 문서 확인은 필요하다.

2. 동작 원리 — Gateway Resolver + Internal Authoritative DNS

구조를 단순화하면 두 개의 컴포넌트로 나뉜다.

  • Gateway Resolver: 재귀 해석(recursive)과 정책 평가를 담당. 2020년에 나온 그 Gateway 맞고, 공개 해석은 1.1.1.1 인프라를 그대로 탄다. 표현식(expression) 기반으로 쿼리를 필터링하거나 다른 업스트림으로 리다이렉트할 수 있다.
  • Internal Authoritative DNS: 내부 존의 권한 있는(authoritative) 레코드를 서빙. Cloudflare가 10년 넘게 굴려온 그 authoritative 플랫폼 위에 올라간다.

여기서 다루는 객체가 세 가지인데, 이걸 이해하면 절반은 끝난다.

  • Internal Zones: 내부 리소스의 실제 레코드(앱, 서비스 엔드포인트, DB 등)를 담는 존
  • DNS Views: 특정 사용자/디바이스 집합이 봐야 할 "해석 컨텍스트". Split-horizon이 별도 시스템 없이 되는 핵심.
  • Resolver Policies: Gateway 안에 존재하며, 매칭되는 쿼리를 특정 View로 라우팅

중요한 개념이 Zone reference다. intranet.local 같은 공용 존을 한 번만 정의하고 여러 View에서 참조(reference)한다. 기존 Split-horizon이 강요하던 "레코드 복붙 → drift 발생" 패턴을 DRY하게 바꾸는 게 이 참조 구조다. 개인적으로 이 부분이 제일 실용적이라고 본다. CoreDNS로 뷰 나눌 때 존 파일 두 벌 관리하다가 한쪽만 고쳐서 사고 나는 걸 실제로 몇 번 봤기 때문이다.

쿼리 한 방이 흘러가는 경로

클라이언트 쿼리가 들어오면 이렇게 처리된다.

  1. 먼저 Gateway Resolver에 도착해서 정책이 평가된다.
  2. Resolver policy가 매칭되고 내부 View를 가리키면 → Internal Authoritative DNS로 라우팅, 해당 View의 존에서 응답.
  3. 정책이 차단(block)하면 → resolver 단에서 드롭.
  4. 둘 다 아니면 → 공개 경로로, 1.1.1.1이 공개 DNS 계층에서 해석.

여기서 편한 게, View는 내부에서 이름을 못 찾으면 공개 해석으로 폴백(fallback)할 수 있다. 즉 클라이언트는 이게 내부 이름인지 공개 이름인지 몰라도 되고, 하나의 리졸버가 둘 다 처리한다. 이걸 CoreDNS로 하려면 forwardfallthrough 조합을 손으로 짜야 하는데, 그걸 정책으로 추상화한 셈이다.

변경 전파 경로

레코드 변경은 대시보드든 Terraform이든 직접 API 호출이든 전부 동일한 DNS Records API를 탄다. 쓰기 경로가 하나라 감사(audit)와 추론이 단순해진다. 변경은 코어 데이터센터에 저장·검증된 뒤 글로벌 네트워크로 복제되고, 영향받은 캐시 엔트리가 무효화되어 TTL 만료를 기다리지 않고 몇 초 안에 반영된다고 한다. 이건 온프레미스 BIND에서 TTL 걸어놓고 "왜 아직 옛날 값 나와?" 하던 경험을 아는 사람에겐 꽤 매력적인 부분이다.

3. 기존 솔루션과 비교 — CoreDNS / Route53 PHZ / Pi-hole

실무에서 자주 쓰는 것들과 성격을 비교해보면 이렇다. (수치성 벤치마크는 원문에 없어 생략, 성격 위주로만 정리한다.)

항목Cloudflare Internal DNSCoreDNSRoute53 Private Hosted ZonePi-hole
주 용도내부/공개 통합 + Zero Trust 정책K8s/자체 내부 DNSAWS VPC 내부 DNS가정/소규모 광고차단 DNS
제어 평면Cloudflare 단일 평면직접 운영AWS 콘솔/API직접 운영
Split-horizonView + Zone reference로 네이티브view 플러그인/수동 구성VPC 단위로 사실상 horizon사실상 미지원
Zero Trust 연동Gateway 정책과 통합없음(별도 계층)없음없음
클라우드 종속없음(어느 연결이든)없음AWS 락인없음
운영 부담매니지드본인이 다 짊어짐매니지드(AWS 한정)직접 운영

정리하면, CoreDNS는 K8s 클러스터 내부용으로는 여전히 최고지만 조직 전체의 사용자·디바이스 정책까지 다루지는 않는다. Route53 PHZ는 AWS 안에서는 훌륭하지만 온프레미스와 다른 클라우드를 아우르는 순간 벽에 부딪힌다. Cloudflare Internal DNS의 차별점은 "DNS 해석 + 누가 접근 가능한가"를 같은 정책 엔진에서 결정한다는 지점이다. Pi-hole은 애초에 결이 다르니 비교표에 넣긴 했지만 엔터프라이즈 대안은 아니다.

4. 실전 구성 — Split-horizon DNS 세 단계로 세우기

원문 기준으로 설정은 크게 세 단계다. 존 생성 → 뷰 생성 → 리졸버 정책 정의. API로 가보자.

4-1. 내부 존과 첫 레코드 생성

# 1) 내부 존 생성
curl -X POST "https://api.cloudflare.com/client/v4/accounts/zones" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account": { "id": "'"$ACCOUNT_ID"'" },
    "name": "corp.internal",
    "type": "internal"
  }'

기대 출력(예시):

{
  "success": true,
  "errors": [],
  "messages": [],
  "result": {
    "id": "023e105f4ecef8ad9ca31a8372d0c353",
    "name": "corp.internal",
    "type": "internal",
    "status": "active"
  }
}
# 2) 내부 A 레코드 추가 (예: DB 엔드포인트)
curl -X POST "https://api.cloudflare.com/client/v4/zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "A",
    "name": "db.corp.internal",
    "content": "10.0.1.50",
    "ttl": 300
  }'

4-2. DNS View 생성 후 존 연결

curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/internal_dns/views" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-view",
    "zones": ["023e105f4ecef8ad9ca31a8372d0c353"]
  }'

기대 출력(예시):

{
  "success": true,
  "result": {
    "id": "9f7b3c1a55d24e6f8a0b1c2d3e4f5a6b",
    "name": "production-view",
    "zones": ["023e105f4ecef8ad9ca31a8372d0c353"]
  }
}

4-3. Gateway Resolver Policy로 라우팅

마지막은 Zero Trust 대시보드에서 Gateway location을 만들고, 조건을 정한 뒤 해석 방식(resolution method)을 Internal DNS View로 지정, 위에서 만든 production-view를 선택하면 된다. 여기서부터 정책에 매칭되는 쿼리는 내부 존에서 해석된다.

4-4. Terraform으로 IaC 관리

Terraform 지원이 있고, Terraform 역시 동일한 DNS Records API를 타기 때문에 전파 경로가 대시보드/직접 API와 완전히 같다. 즉 IaC로 바꿔도 별도 반영 경로를 따로 신경 쓸 필요가 없다. 아래는 개념 예시이며, 실제 리소스명·인자는 공식 프로바이더 문서 확인이 필요하다.

# 개념 예시 (실제 스키마는 provider 문서 확인 필요)
resource "cloudflare_dns_record" "db_internal" {
  zone_id = "023e105f4ecef8ad9ca31a8372d0c353"
  type    = "A"
  name    = "db.corp.internal"
  content = "10.0.1.50"
  ttl     = 300
}

연결 방식은 뭐든 된다

Gateway Resolver로 DNS 트래픽이 흐르기만 하면 된다. Cloudflare One Client(구 WARP), DoH, DoT, 표준 53번 포트 DNS, PAC 파일, Cloudflare WAN 모두 지원한다. 특히 Cloudflare WAN을 쓰면 연결된 네트워크의 모든 디바이스가 개별 클라이언트 설치 없이 내부 호스트명을 해석할 수 있다고 한다. 레거시 장비가 깔린 지점망/공장망처럼 에이전트 설치가 곤란한 환경에서 이 부분이 꽤 유용할 것으로 보인다.

5. 운영 관점 — 트레이드오프와 흔한 함정

흔한 함정 1: 폴백을 믿고 존을 어설프게 나눴을 때

View가 내부에서 못 찾으면 공개로 폴백한다는 게 편하긴 한데, 이게 함정이 될 수도 있다. 내부 존에 존재해야 할 레코드가 오타나 누락으로 빠지면, 차단되지 않고 조용히 공개 해석으로 넘어가 엉뚱한 외부 IP를 물어오는 경우가 생긴다. 이런 건 에러가 안 나서 더 위험하다. 배포 후 내부 이름은 반드시 dig로 응답 소스를 검증하는 습관을 들이는 게 좋다.

dig db.corp.internal +short
# 기대: 10.0.1.50
# 만약 공인 IP가 나온다면 → 폴백으로 새어나간 것. 존/뷰/정책 매칭 재점검

흔한 함정 2: 존 타입/이름 충돌 에러

같은 이름의 존을 중복 생성하거나, 이미 공개 존으로 등록된 이름을 내부 존으로 다시 만들려 할 때 대략 이런 형태의 응답을 만나게 된다(에러 형식은 실제와 다를 수 있으니 참고용):

{
  "success": false,
  "errors": [
    {
      "code": 1061,
      "message": "zone with name 'corp.internal' already exists"
    }
  ],
  "result": null
}

이럴 땐 새로 만들지 말고 기존 존을 여러 View에서 참조(Zone reference)하는 방향으로 가야 한다. Split-horizon을 한다고 존을 복제 생성하는 순간 Cloudflare가 굳이 없애려던 drift를 스스로 다시 만드는 꼴이다.

흔한 함정 3: 정책이 매칭 안 돼서 전부 공개로 나가는 경우

Resolver Policy의 location 조건(소스 IP 범위, 디바이스 상태 등)이 실제 클라이언트와 안 맞으면, 쿼리는 정책에 걸리지 않고 그냥 공개 경로로 간다. "내부 이름이 하나도 안 잡혀요" 증상의 대부분은 존/레코드 문제가 아니라 정책 매칭 조건 문제다. Gateway의 DNS 로그(단일 대시보드)를 열어서 해당 쿼리가 어떤 정책에 매칭됐는지부터 확인하는 게 순서다. 이게 로그가 한 군데 모인다는 통합 제어 평면의 실질적 이점이기도 하다.

트레이드오프

  • 종속성: 결국 Cloudflare 제어 평면에 DNS 해석 경로가 묶인다. 멀티 벤더 전략을 중시하는 조직은 이 점을 감안해야 한다.
  • Enterprise 중심: 원문상 Enterprise + Gateway 사용자가 대상이다. 소규모 팀이 가볍게 쓰기엔 결이 안 맞을 수 있다.
  • 클러스터 내부 DNS는 여전히 CoreDNS: K8s 내부의 서비스 디스커버리까지 이걸로 대체하는 그림은 아니다. 조직 레벨 이름 해석과 클러스터 내부 해석은 계층이 다르다.

6. 정리 — 누가 언제 써야 하나

한 줄 요약: 공개 DNS·내부 DNS·Zero Trust 정책이 각자 따로 놀아서 drift와 장애에 시달려온 조직이라면, 이 셋을 하나의 API/정책 엔진/감사 로그로 합쳐준다는 게 Cloudflare Internal DNS의 핵심 가치다.

이런 팀에 잘 맞는다.

  • 온프레미스 + 여러 클라우드가 섞여 있고, 내부 DNS가 환경마다 파편화된 조직
  • Split-horizon을 두 벌 존 관리로 버티다가 drift 장애를 겪어본 팀
  • 이미 Cloudflare Zero Trust / Gateway / WAN을 쓰고 있어 제어 평면을 하나로 모으고 싶은 곳
  • 레거시 DNS 어플라이언스 리프레시 주기와 스케일링 병목을 걷어내고 싶은 경우

반대로 K8s 클러스터 내부 해석만 필요하거나, 소규모여서 CoreDNS/자체 리졸버로 충분한 팀이라면 굳이 지금 갈아탈 이유는 크지 않다. 다만 원문 마지막이 예고하듯 "이름 해석 → 서비스 도달 → 접근 인가"를 하나의 플랫폼에서 결정하는 방향으로 통합이 더 진행될 걸로 보이니, Cloudflare 생태계를 이미 쓰는 조직이라면 흐름은 지켜볼 가치가 있다.

참고 자료

※ 본문의 API 응답·에러 메시지 일부는 형식 이해를 돕기 위한 예시이며, 실제 응답 스키마와 다를 수 있다. 정확한 스키마·리소스명·요금 조건은 공식 문서 확인이 필요하다.

728x90

+ Recent posts