Vincent Bernat, 블로그 GeoDNS를 Route 53에서 Bunny 스크립트 DNS로 이전
블로그 운영자 Vincent Bernat는 일곱 지역의 서버를 연결하던 Route 53 GeoDNS를 Bunny의 스크립트 DNS로 옮겼습니다. 스크립트는 질의 위치에서 두 번째 서버까지의 거리가 훨씬 멀 때 가까운 주소 하나만 120초 TTL로 반환하고, 그 외에는 두 주소를 3,600초 TTL로 돌려줍니다. Bernat는 월 청구액이 5달러에서 1달러로 줄었고 성능은 AWS와 비슷하다고 보고했지만, 성능 관측은 시드니 서버를 포함한 구성을 마친 뒤의 초기 결과입니다.

블로그 운영자 Vincent Bernat는 정적 자산 전달에 쓰던 CloudFront와 지리 기반 DNS를 제공하던 Route 53을 Bunny CDN·Bunny DNS로 옮겼습니다. 파리, 뉘른베르크, 애시번, 싱가포르, 새너제이, 상파울루, 시드니의 NixOS 가상머신 일곱 대를 호스트로 둔 개인 운영 사례입니다. Bernat는 Route 53 비용을 전환 이유로 들었으며, 월 청구액이 5달러에서 1달러로 줄었다고 보고했습니다. Bunny CDN은 당시 HTTP/3를 지원하지 않았고, 지원팀은 개발 중이라고 답했지만 일정을 제시하지 않았다고 Bernat는 적었습니다.
Bunny DNS의 스크립트 기능은 DNS 질의마다 사용자가 작성한 JavaScript를 실행해 응답을 계산합니다. Bernat의 스크립트는 질의에 담긴 위치 정보를 읽고 A 질의에는 IPv4 주소를, AAAA 질의에는 IPv6 주소를 반환하도록 작성됐습니다. 서버의 위도·경도와 질의 위치를 RoutingEngine 도우미에 전달해 가까운 살아 있는 서버를 찾습니다. 전체 코드는 IPv4만 지원하는 RoutingEngine의 제약을 우회해 IPv6 질의도 처리합니다. 이용자는 일반적인 호스트 이름을 요청하고, 위치에 따라 선택된 서버 주소를 DNS 응답으로 받습니다.
처음에는 가까운 서버 두 곳의 주소를 함께 반환했습니다. 첫 주소의 서버에 연결할 수 없으면 브라우저가 두 번째 주소를 시도할 수 있지만, 주소가 두 개라는 이유만으로 가까운 서버가 먼저 선택되지는 않습니다. 예를 들어 부에노스아이레스에서는 상파울루 서버까지 약 1,000마일·왕복 30밀리초, 애시번 서버까지 약 5,200마일·왕복 140밀리초인데도 두 주소가 함께 반환됩니다. 리졸버가 응답 순서를 바꾸면 브라우저가 더 먼 서버에 먼저 연결할 수 있습니다.
거리 차이가 클 때는 주소 하나만 반환
Bernat는 HTTPS DNS 레코드로 서버 우선순위를 브라우저에 전달하는 방법도 검토했습니다. RFC 9460은 HTTPS 서비스의 대체 엔드포인트와 우선순위, HTTP/2·HTTP/3 같은 프로토콜 정보를 DNS로 제공하는 방식을 정의합니다. Bernat가 제시한 구성은 첫 번째 서버와 두 번째 서버를 서로 다른 이름으로 지정해 우선순위에 따라 접속하고, 첫 서버를 사용할 수 없으면 다음 서버를 시도하도록 설계됐습니다.
Bernat는 당시 Chrome이 대상 이름이 .이 아닌 HTTPS 레코드를 무시한다고 설명했습니다. 또 Bunny DNS는 first.geo와 second.geo의 IP 주소를 HTTPS 레코드 응답의 추가 섹션에 넣지 않았습니다. 따라서 리졸버는 HTTPS 레코드를 받은 뒤 대상 이름의 주소를 구하는 질의를 한 번 더 보내야 했습니다. 이 환경에서는 의도한 서버별 우선순위 흐름을 구현하기 어려워 스크립트 방식으로 돌아갔습니다.
대신 스크립트는 질의 위치에서 가장 가까운 서버와 두 번째로 가까운 서버까지의 지리적 거리를 비교합니다. 두 번째 서버까지의 거리가 첫 번째 서버까지 거리의 두 배보다 크고, 두 거리의 차이가 1,000마일을 넘으면 가장 가까운 주소 하나만 반환하고 TTL을 120초로 설정합니다. 둘 중 하나라도 조건에 맞지 않으면 두 주소를 모두 반환하고 TTL은 3,600초입니다. Bernat가 공개한 예에서는 시카고 질의에 애시번 주소 하나가 120초 TTL로 돌아왔고, 멕시코 질의에는 애시번과 새너제이 주소가 3,600초 TTL로 함께 돌아왔습니다.
이 규칙은 네트워크 왕복시간이 아니라 질의 위치와 서버 사이의 지리적 거리를 사용합니다. 두 주소를 반환하는 지역에서는 대체 서버가 남지만, DNS 응답 순서가 가까운 서버를 우선시하지는 않습니다. 주소 하나만 받은 뒤 그 서버에 문제가 생기면 DNS 캐시가 만료될 때까지 브라우저가 대체 주소를 받지 못해 짧은 서비스 중단이 생길 수 있습니다. Bernat의 설정은 서버까지의 거리가 크게 벌어진 지역에서 원거리 서버가 먼저 선택될 가능성을 낮추고, 그때 TTL을 줄여 재질의를 유도합니다.
Route 53은 지역 라우팅 외에 DNS-01 방식의 인증서 발급에도 쓰였습니다. Bunny DNS는 단일 영역으로 권한을 세밀하게 제한할 수 없어, Bernat는 인증서 챌린지용 acme 영역을 Rust로 작성한 DNS 서버 Hickory로 옮겼습니다. Let’s Encrypt 인증서 발급 도구 Lego는 DNS UPDATE 프로토콜로 챌린지 레코드를 추가하고, 블로그 도메인의 CNAME은 해당 질의를 acme 영역으로 넘깁니다.
Hickory는 영역에 DNSSEC 서명을 자동으로 붙이지만 Bunny DNS는 DS 레코드를 게시할 수 없습니다. Bernat는 상위 luffy.cx 영역을 deSEC에 두고, Hickory가 관리하는 하위 acme 영역의 DNSKEY에서 DS 값을 계산해 상위 영역에 게시했습니다. 이 구성에서 블로그의 지역 라우팅과 CDN은 Bunny가 맡고, 인증서 챌린지 영역은 Hickory가 관리하며, 상위 DNS의 DS 게시에는 deSEC를 씁니다.
Bernat는 시드니 서버를 포함한 현재 구성을 10월 말에 마무리했으며, 지금까지 성능이 AWS와 비슷하다고 보고했습니다. 다만 현재 구성의 데이터를 더 모으겠다고 덧붙였습니다. 비용과 성능은 Bernat가 자신의 블로그 운영에서 관측한 결과입니다.