본 게시물은 DNS 서버 구축부터 시작해서 GeoDNS를 구현하고 레코드를 추가하는데 필요한 모든 작업환경, 설치 방법, 설정값 등을 작성한 글입니다.

주의! 본 게시물은 전문성이라고는 손톱만큼도 없는 백수 코더가 작성한 게시물입니다.
따라서 잘못된 부분이 존재할 수도 있으니 그런 부분이 있다면 댓글로 지적해주시면 감사하겠습니다.

최종 업데이트 - 2018.10.11

본인은 네임 서버 설정을 위해 Cloudflare의 Free plan을 사용했었습니다. 그러나 wildcard 레코드를 등록할 일이 있었고, Free plan은 wildcard 레코드를 등록할 수 없었습니다.
그리고 마침 GeoDNS를 소규모로 구현해보고 싶었는데, 그 기능을 지원하는 업체는 본인이 사용하기에 약간 비싼 편이었기 때문에 결국 DNS 서버를 직접 구축하기로 했습니다.

하지만 웹 서버, 메일 서버, DBMS, 캐시 서버 등 많은 서비스를 구축해 보긴 했어도 DNS 서버는 지금까지 필요성을 못 느꼈기 때문에 구축해본 적이 없었으나, 역시 my best friend 구글이 있기에 어렵지 않게 시도해 볼 수 있었습니다.

서버 구성

먼저, DNS 서버를 구축하기에 앞서 서버부터 구매해야 합니다.
아래는 본인이 현재 구매한 서버(VPS)들입니다.

VPS 서버

# Provider Transfer CPU RAM Region Name Price
1 OVH 1 2048 UK ov-012-gbr-01 $3.35
2 AWS 1TB 1 512 Singapore am-110-sgp-01 $3.5
3 OVH 1 2048 Canada ov-012-can-02 $3.35
4 Linode 1TB 1 1024 Japan ln-111-jpn-01 $5

GeoDNS를 구현할 필요가 없다면 DNS 서버 2개 이상, 서비스 전용 서버 1~2개(Region 상관없이)면 충분하지만, GeoDNS까지 구현하기 위해서는 서비스 전용 서버를 Region별로 나눠야 합니다.
저는 Asia를 제외한 모든 지역은 ov-012-can-02 서버를 가리키게 하고, Asia 지역은 ln-111-jpn-01 서버를 가리키게 구성할 것입니다.

스토리지

# Provider Transfer Disk Name Price
1 DigitalOcean 1TB 250GB top $5

정적인 파일(css, js, 이미지 등)을 제공할 CDN 스토리지를 구독(subscribe)했습니다.

합계

Total
$20.2

※ 한달에 $20.2가 적은 돈은 아니지만 정적 파일만 제공할 것이 아니라면 최소 $20 정도는 투자할 가치가 있다고 판단하여 요금을 책정했습니다.
(AWS도 경험 삼아 써 보고 싶은데 본인 형편에는 맞지 않아서 ㅠㅠ)


시작하기

위에서 언급한 서버 리스트 중 ov-012-gbr-01am-110-sgp-01을 네임서버로 사용하겠습니다.
본인은 RHEL계열 배포판을 선호하기 때문에 CentOS 7을 선택했습니다.
두 서버에 OS를 설치합니다.

OS 업데이트

OS를 설치했으면 먼저 업데이트를 해야죠.
아래와 같이 입력하면 업데이트를 할 수 있습니다.

# yum update

업데이트 후 재부팅을 해 줍니다.

# shutdown -r now

방화벽 설치 및 설정

그 다음, 방화벽을 설정합니다.
VPS 호스팅을 사용한다면 대부분 방화벽을 기본적으로 제공하지만, 기본 제공 방화벽이 없거나, 부실하거나(망할 AWS Lightsail ㅠㅠ), VPS가 아닌 단독 서버인 경우 서버에서 직접 설정해야 합니다.

firewalld를 설치해줍니다.

# yum install firewalld

firewalld 서비스를 등록하고, 시작해줍니다.

# systemctl enable firewalld
# systemctl start firewalld

zone을 설정해줍니다. 기본 설정은 SSH 서비스 포트가 열려있습니다.

  • SSH는 특정 IP만 접속할 수 있도록 rule을 등록해줍니다.
  • 기본 설정인 SSH 서비스는 삭제해줍니다.
  • DNS를 위한 TCP/UDP 53포트를 추가해줍니다.

IP 주소는 본인의 IP 주소를 입력합니다.
http://www.whatsmyip.org/
screenshot-www.whatsmyip.org-2018.10.04-23-38-05.png

# firewall-cmd --permanent --zone=public --add-rich-rule="rule family="ipv4" source address="<IP 주소>/32" port protocol="tcp" port="22" accept"
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --permanent --zone=public --add-port=53/tcp
# firewall-cmd --permanent --zone=public --add-port=53/udp
# firewall-cmd --reload

firewall-cmd --list-all을 입력하여 설정이 잘 되어 있는지 확인합니다.
image.png

DNS 서버 설치 및 설정

bind 설치

본격적으로 DNS 서버를 설치해봅시다.

# yum install bind bind-*
# systemctl enable named

설치가 끝났다면 /etc/named.conf를 열어 몇 가지 설정을 변경해줍니다.

53번 포트 허용

53번 포트를 모든 host에서 사용할 수 있도록 any로 변경합니다.

listen-on port 53 { any; };
listen-on-v6 port 53 { any; };

DNS 쿼리 허용

allow-query 도 마찬가지로 모든 host에서 DNS Query가 가능하도록 any로 변경합니다.

allow-query     { any; };

DNS zone transfer 막기

보안을 위해 DNS zone transfer를 특정 호스트에서만 허용하도록 합니다.
allow-transfer를 설정해줍시다.

allow-transfer  { 127.0.0.1; 51.68.196.173; 18.136.3.12; };

추가해야 할 IP 주소는 다음과 같습니다.

  • 127.0.0.1
  • 네임서버(들)의 IP 주소 (master 서버와 slave 서버가 동기화하려면 반드시 넣어줘야 합니다.)

모두 설정했다면 저장합니다.

DNS zone 추가하기

먼저 /etc/named.rfc1912.zones 파일을 수정합니다.
master 서버(1차 네임서버)와 slave 서버(2차 네임서버)는 설정하는 것이 약간 다릅니다.

master 서버에는 아래와 같이 추가합니다.

zone "<도메인명>" IN {
    type master;
    file "<도메인명>.zone";
    allow-update { none; };
    allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
};

그리고 slave 서버에는 아래와 같이 추가하면 됩니다.

zone "<도메인명>" IN {
    type slave;
    file "<도메인명>.zone";
    masters { <master 서버 IP 주소>; };
    allow-notify { <master 서버 IP 주소>; };
};

DNS zone 파일 생성

DNS zone 파일은 /var/named에 생성합니다.

<도메인명>.zone 파일을 생성하여 아래와 같은 형식으로 작성합니다.

$TTL 1D
@               IN      SOA     ns.example.com.  root.example.com. (
                                    2018100501  ; Serial
                                    3H          ; refresh
                                    1H          ; retry
                                    2W          ; expire
                                    1D )        ; minimum
                IN      NS      ns.example.com.
                IN      NS      ns2.example.com.
                IN      A       123.45.67.89
                IN      AAAA    2001:db8:0:0:1234::1
www             IN      CNAME   example.com.
ns              IN      A       12.34.56.78
ns2             IN      A       12.34.56.79

설명

$TTL 1D - Time To Live. DNS Query 했을 때 캐시를 저장하는 시간
(1H = 1시간, 3H = 3시간, 24H = 1D = 1일, 1W = 1주, 2W = 2주)

@       - zones 파일에 설정한 도메인을 의미함
IN      - Internet

SOA     - Start of Authority. 도메인 영역을 표시하는 레코드
SOA 레코드 순서 - serial refresh retry expire minimum
Serial  - YYYYMMDDNN 형식으로 입력 (zone 파일을 수정할 때마다 변경)
refresh - 요청하는 간격
retry   - 오류 발생 시 재시도 간격
expire  - 만료 시간
minimum - TTL과 동일하게 설정
---------------------------------------------------------------------
NS      - 네임서버(Name Server) 레코드
A       - IPv4 주소를 지정 ex) 123.45.67.89
AAAA    - IPv6 주소를 지정 ex) 2001:db8:0:0:1234::1
CNAME   - 대체 도메인 이름

※ 도메인을 입력할 경우 도메인 끝에는 반드시 루트를 뜻하는 .을 붙여야 함

record를 추가할 때는 아래처럼 추가합니다.

서브도메인 클래스명 레코드 형식 값(도메인, IP 주소 등)
www IN CNAME example.com.
www             IN      CNAME   example.com.

작성 후 저장하고 master 서버와 slave 서버의 named 서비스를 재시작합니다.

# service named restart

여기까지가 Bind의 기본적인 사용/설정 방법입니다.

GeoDNS 사용하기

만약 글로벌 서비스를 계획하고 있다면 GeoDNS를 설정해 주는 것이 좋습니다.
GeoDNS란, 국가별로 다른 DNS Query 결과를 반환하도록 하는 기능입니다.
예를 들어 미국에 있는 사용자가 사이트를 접속하면 북미 서버로 접속되고 한국에 있는 사용자가 사이트를 접속하면 아시아 서버로 접속되는 것입니다.

geodns.jpg

장점

  • 고객이 전세계 어디서나 빠른 속도로 서비스를 이용할 수 있다.
    ※ 일부 기업은 Anycast 등의 기술도 병행해서 사용하기도 함.
  • 진입장벽이 낮은 편이다 (서버 구매할 자금만 있다면 개인도 어렵지 않게 시도할 수 있음.)

단점

  • 서버 비용이 추가로 발생한다.
  • 세팅이 귀찮다. (하지만 대규모로 확장해야 한다면 이런 자잘한 작업들은 자동화시키는 게 편리할 것이다.)

GeoIP.acl 파일 받기

GeoIP 데이터베이스를 받겠습니다.
https://geoip.site/

screenshot-geoip.site-2018.10.05-22-21-11.png
이렇게 MaxMind, IP2Location, DB-IP 3개가 있는데 아무거나 받아도 상관없습니다.

아, 그 전에 wget이 설치되어 있는지 확인해봐야죠.

# rpm -qa | grep wget

image.png
설치되어 있다면 이렇게 출력되고, 설치되어 있지 않다면 아무것도 출력되지 않을 것입니다.

설치되어 있지 않다면 설치해주세요.

# yum install wget

그리고 GeoIP.acl 파일을 받습니다.

# cd /var/named
# wget https://geoip.site/download/MaxMind/GeoIP.acl

설정 변경하기

GeoDNS를 사용하기 위해서는 설정을 약간 변경하고 zone 파일을 Region별로 따로 마련해야 합니다.

master 서버

/etc/named.rfc1912.zones 파일을 수정합니다.
아래와 같은 부분을 지웁니다.

zone "<도메인명>" IN {
    type master;
    file "<도메인명>.zone";
    allow-update { none; };
    allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
};

/etc/named.conf도 아래와 같은 부분을 지웁니다.

zone "." IN {
    type hint;
    file "named.ca";
};

include "/etc/named.rfc1912.zones";

그리고 /etc/named.confinclude "/etc/named.root.key"; 위에 이렇게 입력합니다.

include "/var/named/GeoIP.acl";

view "asia" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        CN; HK; JP; KP;
        KR; MO; MN; TW;
        BN; KH; ID; LA;
        MY; MM; PH; SG;
        TH; TL; VN; BD;
        BT; IN; MV; NP;
        LK;
    };

    zone "<도메인명>" IN {
        type master;
        file "<도메인명>-asia.zone";
        allow-update { none; };
        allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
    };
};

view "other" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        any;
    };

    zone "<도메인명>" IN {
        type master;
        file "<도메인명>.zone";
        allow-update { none; };
        allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
    };
};

(match-clients 목록 출처 : https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes/blob/master/all/all.csv 서아시아는 위치가 애매해서 추가하지 않았으나, 추후 문제가 생길 시 본 게시물을 수정하겠음)

slave 서버

slave 서버도 마찬가지로 /etc/named.rfc1912.zones 파일을 수정합니다.
아래와 같은 부분을 지웁니다.

zone "<도메인명>" IN {
    type slave;
    file "<도메인명>.zone";
    masters { <master 서버 IP 주소>; };
    allow-notify { <master 서버 IP 주소>; };
};

/etc/named.conf도 아래와 같은 부분을 지웁니다.

zone "." IN {
    type hint;
    file "named.ca";
};

include "/etc/named.rfc1912.zones";

그리고 /etc/named.confinclude "/etc/named.root.key"; 위에 이렇게 입력합니다.

include "/var/named/GeoIP.acl";

view "asia" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        CN; HK; JP; KP;
        KR; MO; MN; TW;
        BN; KH; ID; LA;
        MY; MM; PH; SG;
        TH; TL; VN; BD;
        BT; IN; MV; NP;
        LK;
    };

    zone "<도메인명>" IN {
        type slave;
        file "<도메인명>-asia.zone";
        masters { <master 서버 IP 주소>; };
        allow-notify { <master 서버 IP 주소>; };
    };
};

view "other" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        any;
    };

    zone "<도메인명>" IN {
        type slave;
        file "<도메인명>.zone";
        masters { <master 서버 IP 주소>; };
        allow-notify { <master 서버 IP 주소>; };
    };
};

그리고 master 서버로 가서 zone 파일을 복사합니다.

# cd /var/named
# cp <도메인명>.zone <도메인명>-asia.zone

<도메인명>-asia.zone 파일을 열어서 IP주소를 Asia 서버 IP로 변경합니다.

$TTL 1D
@               IN      SOA     ns.example.com.  root.example.com. (
                                    2018100501  ; Serial
                                    3H          ; refresh
                                    1H          ; retry
                                    2W          ; expire
                                    1D )        ; minimum
                IN      NS      ns.example.com.
                IN      NS      ns2.example.com.
                IN      A       123.45.67.89 <- 이 부분
                IN      AAAA    2001:db8:0:0:1234::1 <- 이 부분
www             IN      CNAME   example.com.
ns              IN      A       12.34.56.78
ns2             IN      A       12.34.56.79

마지막으로 master 서버와 slave 서버의 named 서비스를 재시작합니다.

# service named restart

Troubleshoot

slave 서버 권한 수정하기

slave 서버에서는 자동으로 master 서버로부터 정보를 받아오고 저장하는데, 디렉토리의 group 권한이 부족하면 저장하지 못합니다.
이럴 경우 slave 서버의 /var/named 디렉토리 권한을 변경해야 합니다.

# chmod 770 /var/named

동기화 문제 fix (중요)

2018.10.11 추가
하지만 GeoDNS 사용 시 특정 환경에서 slave서버가 하나의 view로만 동기화되는 현상이 발생하기도 합니다.
view별 Key를 생성하여 이러한 문제를 fix할 수 있습니다.

다음과 같이 입력하여 Key를 생성해줍시다.

# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 128 -n Host asia.<도메인명>.
# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 128 -n Host global.<도메인명>.

image.png

그러면 이렇게 각 Host별 key 파일과 private파일이 생성됩니다.
키가 유출되지 않도록 잘 관리해야 합니다.

생성된 Key들은 conf파일에 사용될 것이기 때문에 zone에 레코드를 꼭 추가할 필요는 없습니다.
여기서 필요한 것은 private 파일들입니다.
먼저 Kasia.<도메인명>.+???+?????.private을 열어줍시다.

image.png

Key 값을 복사해서 메모장 등에 붙여넣기 합니다.
Kglobal.<도메인명>.+???+?????.private 파일의 Key 값도 마찬가지로 복사해서 메모장 등에 붙여넣기 합니다.

master 서버의 /etc/named.conf 파일을 열어줍니다.

앞에서 설정한 부분들에 약간 추가할 것입니다.

include "/var/named/GeoIP.acl";

key "key_asia" {
        algorithm hmac-md5;
        secret "<`Kasia.<도메인명>.+???+?????.private`의 Key 값>";
};

key "key_other" {
        algorithm hmac-md5;
        secret "<`Kglobal.<도메인명>.+???+?????.private`의 Key 값>";
};

acl keys_view { key key_asia; key key_other; };

view "asia" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        key key_asia; !keys_view;
        CN; HK; JP; KP;
        KR; MO; MN; TW;
        BN; KH; ID; LA;
        MY; MM; PH; SG;
        TH; TL; VN; BD;
        BT; IN; MV; NP;
        LK;
    };

    server <2차 네임서버 IP> { keys key_asia; };

    zone "<도메인명>" IN {
        type master;
        file "<도메인명>-asia.zone";
        allow-update { none; };
        allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
    };
};

view "other" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        key key_other; !keys_view; any;
    };

    server <2차 네임서버 IP> { keys key_other; };

    zone "<도메인명>" IN {
        type master;
        file "<도메인명>.zone";
        allow-update { none; };
        allow-transfer { 127.0.0.1; <1차 네임서버 IP>; <2차 네임서버 IP>; };
    };
};

만약 slave 서버가 여러 개라면 각 view마다 server <slave 서버 IP> { keys key_????; };를 더 추가하면 됩니다.


그리고 slave 서버의 /etc/named.conf 파일을 열어줍니다.
Key 설정을 추가해줍니다.

include "/var/named/GeoIP.acl";

key "key_asia" {
        algorithm hmac-md5;
        secret "<`Kasia.<도메인명>.+???+?????.private`의 Key 값>";
};

key "key_other" {
        algorithm hmac-md5;
        secret "<`Kglobal.<도메인명>.+???+?????.private`의 Key 값>";
};

acl keys_view { key key_asia; key key_other; };

view "asia" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        key key_asia; !keys_view;
        CN; HK; JP; KP;
        KR; MO; MN; TW;
        BN; KH; ID; LA;
        MY; MM; PH; SG;
        TH; TL; VN; BD;
        BT; IN; MV; NP;
        LK;
    };

    server <master 서버 IP 주소> { keys key_asia; };

    zone "<도메인명>" IN {
        type slave;
        file "<도메인명>-asia.zone";
        masters { <master 서버 IP 주소>; };
        allow-notify { <master 서버 IP 주소>; };
    };
};

view "other" {
    include "/etc/named.rfc1912.zones";

    zone "." IN {
        type hint;
        file "named.ca";
    };

    match-clients {
        key key_other; !keys_view; any;
    };

    server <master 서버 IP 주소> { keys key_other; };

    zone "<도메인명>" IN {
        type slave;
        file "<도메인명>.zone";
        masters { <master 서버 IP 주소>; };
        allow-notify { <master 서버 IP 주소>; };
    };
};

master 서버와 slave 서버의 named 서비스를 재시작합니다.

# service named restart

끝!