Browsing Tag

global dns

NCLOUD

[NCLOUD] API로 Global DNS 레코드 추가 자동화하기

안녕하세요. MANVSCLOUD 김수현입니다.

오늘은 네이버 클라우드 플랫폼의 Global DNS 서비스에 대해 이야기하려고 합니다.
특히 대량의 도메인을 콘솔에서 직접 추가하는데 상당한 시간이 소요되는 문제를 해결하기 위해 API를 활용하여 DNS 레코드를 자동으로 등록하는 방법을 살펴보려 합니다.

네이버 클라우드 플랫폼의 Global DNS 서비스는 고객이 따로 DNS 서버를 구축하여 관리하지 않아도 콘솔을 통해 도메인 및 레코드 등록으로 쉽게 관리하고 사용할 수 있는 서비스입니다. 하지만 대량의 도메인을 관리하거나 이전해야 하는 경우 각 도메인을 하나씩 수동으로 등록하는 것은 매우 비효율적이며 시간이 많이 소요됩니다.

이 문제를 해결하기 위해 이 포스트에서는 네이버 클라우드 플랫폼의 API를 활용하여 Global DNS 레코드를 자동으로 등록하는 방법을 소개하려고 합니다.

이 방법을 통해 대량의 도메인을 훨씬 빠르고 효율적으로 관리할 수 있습니다.


Global DNS

네이버 클라우드 플랫폼의 Global DNS는 도메인 관리에 필요한 모든 기능을 제공하는 서비스입니다. 별도의 DNS 인프라 구축이나 운영 관리가 필요 없이 웹 기반 콘솔에서 도메인을 추가하여 DNS를 구축하고 관리할 수 있습니다. 여러 대의 DNS 서버를 운영하여 서비스 연속성을 보장하며 다양한 리소스 레코드 설정과 대량 데이터 업로드가 가능합니다. 또한 DNSSEC를 지원하여 보안을 강화하고 최적의 글로벌 라우팅 방식을 제공하여 빠른 응답을 가능하게 합니다. 별칭(Alias) 레코드 매핑, 존 파일 가져오기, 도메인별 질의 통계 제공 등의 기능도 포함되어 있습니다. 이를 통해 사용자는 효율적인 도메인 관리와 최적화된 서비스 접속 환경을 구축할 수 있습니다.

Global DNS는 업로드 및 다운로드 기능을 제공하는데 이 기능이 “존 파일 가져오기”를 가리키는 것으로 보입니다. 그러나 이러한 기능은 엑셀 파일 형식으로 제공되는 반면 bind를 통해 네임서버를 구축한 경우 존 파일은 아래와 같이 텍스트 형식으로 얻을 수 있어 추가적인 작업이 필요합니다.

;
; primary name server hosts file
;
$TTL 43200
@       IN      SOA     ns.test.com.   root.ns.test.com. (
                        2022041801 ; Serial
                        10800 ; Refresh
                        3800 ; Retry
                        3600000 ; Expire
                        86400 ) ; Minimum
;
        IN      NS      ns.test.com.
;
@                       IN      MX      1  ASPMX.L.GOOGLE.COM.
@                       IN      MX      5  ALT1.ASPMX.L.GOOGLE.COM.
@                       IN      A       1.1.1.1
www                     IN      A       2.2.2.2
devapi                  IN      A       2.2.2.2
dev                     IN      A       4.3.2.1

물론 템플릿 다운로드가 가능하기때문에 아래 양식에 맞게 작성하여 업로드하는 방법도 있습니다.


Automatic Record Registration

이제 Global DNS에 레코드를 자동으로 등록할 수 있도록 해봅시다.
이번 과정에서는 Cloud Functions을 사용하지 않았습니다. (zip 파일 용량이 51MB 라서…)

[참고] 위와 같이 zip 파일 용량이 38MB 이상이 넘어가는 경우에 증가 요청은 불가능하지만 런타임을 custom image로 선택하고 Container Registry 이미지를 선택하여 사용하는 방법을 고려해볼 수 있습니다. 이 방법은 추후 포스팅에서 알아보겠습니다.

# VPC, Server 생성에 대한 가이드는 생략합니다.
# Python은 3.7 버전이 사용되었습니다.

처음에는 Cloud Functions과 Object Storage를 함께 사용하려 했으나, 압축된 .zip 파일의 용량이 크다는 것을 발견하고 이를 서버로 대체하기로 결정했습니다. 따라서, Object Storage를 사용할 필요성이 줄어들었지만 다시 코드를 변경하는 작업에 시간을 버리고 싶지 않아 그대로 사용해보도록 하겠습니다.

위 이미지는 최종적으로 Zone 파일을 CSV 파일로 변환했을 때의 예시입니다.
Zone 파일을 위와 같이 정렬하기 위해서는 추가적인 작업이 필요했습니다.

import csv
import re

domain = 'newreka.co.kr'

with open( domain + '.zone', 'r') as f:
    lines = f.readlines()


csv_data = []

pattern = re.compile(r'(@|\w+)\s+IN\s+(\w+)\s+(.+)')

for line in lines:
    match = pattern.search(line)
    if match and match.group(2) != 'SOA':  # Ignore 'SOA' records
        subdomain = match.group(1)
        record_type = match.group(2)
        value = match.group(3)

        csv_data.append([domain, subdomain, record_type, value])

with open( domain + '.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    # Writing column headers
    writer.writerow(['domain', 'host', 'type', 'content'])
    # Writing data rows
    for row in csv_data:
        writer.writerow(row)

위 코드는 Zone 파일을 CSV 파일로 변환하는 예시 코드입니다.
실제 작업 환경에서는 상황에 따라 코드 수정이 필요할 수 있습니다.
예를 들어 하이픈(-)이 포함되어 있거나 여러 MX 레코드가 있는 경우 한 줄로 모아주는 등의 작업이 필요합니다.

이런 부분들은 상황에 따라 추가적인 수정이 필요하다는 것을 참고하시기 바랍니다.

생성된 CSV 파일이 위 이미지와 같이 Object Storage에 업로드되어 있다고 가정해봅시다. 이제 이 CSV 파일을 읽어와 Global DNS에 자동으로 등록하고 해당 작업에 대한 로그를 Object Storage에 저장하는 과정을 살펴보겠습니다.

pip install pandas
pip install boto3
pip install requests
import requests
import base64
import hashlib
import hmac
import time
import io
import boto3
import pandas as pd
import os
from botocore.client import Config
from datetime import datetime

ACCESS_KEY = os.getenv('NCLOUD_ACCESS_KEY')
SECRET_KEY = os.getenv('NCLOUD_SECRET_KEY')
AWS_REGION = 'kr-standard'
ENDPOINT_URL = 'https://kr.object.ncloudstorage.com'
API_SERVER = 'https://globaldns.apigw.ntruss.com'
DNS_BUCKET = 'manvscloud-globaldns'   # CSV 파일이 저장된 버킷명
LOG_BUCKET = 'manvscloud-log'    # LOG 파일을 저장할 버킷명
DOMAIN = 'newreka.co.kr'  # 대량의 레코드 변경이 필요한 도메인
CSV_FILE = DOMAIN + '.csv'


def read_csv_from_object_storage(bucket_name, file_name):
    s3 = boto3.resource('s3',
                        region_name=AWS_REGION,
                        endpoint_url=ENDPOINT_URL,
                        aws_access_key_id=ACCESS_KEY,
                        aws_secret_access_key=SECRET_KEY,
                        config=Config(signature_version='s3v4'))
    obj = s3.Object(bucket_name, file_name)
    csv_content = obj.get()['Body'].read().decode('utf-8')
    return pd.read_csv(io.StringIO(csv_content))


def ncp_request(uri, method, payload=None):
    timestamp = str(int(time.time() * 1000))
    message = method + " " + uri + "\n" + timestamp + "\n" + ACCESS_KEY
    signature = base64.b64encode(hmac.new(bytes(SECRET_KEY, 'UTF-8'), bytes(message, 'UTF-8'),
                                          digestmod=hashlib.sha256).digest())
    headers = {
        'x-ncp-apigw-signature-v2': signature,
        'x-ncp-apigw-timestamp': timestamp,
        'x-ncp-iam-access-key': ACCESS_KEY
    }
    if method == 'GET':
        response = requests.get(API_SERVER + uri, headers=headers)
    else:
        response = requests.post(API_SERVER + uri, headers=headers, json=payload)
    return response


def get_domain_id(domain):
    uri = "/dns/v1/ncpdns/domain?responseFormatType=json&page=0&size=20&domainName=" + domain
    response = ncp_request(uri, 'GET')
    return response.json()["content"][0]["id"]


def add_dns_record(record, domain_id):
    uri = f"/dns/v1/ncpdns/record/{domain_id}?responseFormatType=json"
    payload = [{
        "host": record["host"],
        "type": record["type"],
        "content": record["content"],
        "ttl": record["ttl"]
    }]
    response = ncp_request(uri, 'POST', payload)
    status_code = f"Response Status Code: {response.status_code}\n"
    response_text = f"Response Text: {response.text}\n"
    log_data = status_code + response_text
    if response.text:
        log_data += f"Response: {response.json()}\n"
    return log_data


def write_log_to_object_storage(log_data):
    timestamp = datetime.now().strftime('%Y%m%d%H%M')
    filename = f'globalDNS-{timestamp}.log'
    s3 = boto3.resource('s3',
                        region_name=AWS_REGION,
                        endpoint_url=ENDPOINT_URL,
                        aws_access_key_id=ACCESS_KEY,
                        aws_secret_access_key=SECRET_KEY,
                        config=Config(signature_version='s3v4'))
    s3.Object(LOG_BUCKET, f'dns/{filename}').put(Body=log_data)


def main():
    data = read_csv_from_object_storage(DNS_BUCKET, CSV_FILE)
    domain_id = get_domain_id(DOMAIN)
    log_data = ""
    for index, row in data.iterrows():
        log_entry = add_dns_record(row, domain_id)
        log_data += log_entry
    write_log_to_object_storage(log_data)


if __name__ == "__main__":
    main()

위 코드를 실행하면 아래와 같이 Global DNS에 자동으로 등록된 것을 확인할 수 있습니다.

레코드 변경을 적용시키는 API도 존재하지만 이 부분은 사용하지 않았습니다.
직접 Global DNS에서 등록이 잘 되었는지 검토하고 [설정 적용]을 진행하기 위함입니다.

Object Storage에서 위와 같이 로그 파일로 남겨져있어 정상적으로 레코드가 등록되었는지 어느 부분에서 실패했는지도 알 수 있습니다.

.zip 파일의 용량이 생각보다 크다는 걸 일찍 알았다면 굳이 위와 같이 번거로운 코드를 작성하지 않아도 됐었는데 아쉬움이 남습니다.


Personal Comments

지금까지 Global DNS의 API를 이용한다면 쉽고 빠르게 레코드 등록이 가능하다는 점을 알 수 있었습니다.

저 역시 현업에서 위 코드를 수정하여 Global DNS에 다수의 도메인 및 수백개의 서브도메인 짧은 시간 내에 추가한 경험이 있고 최근에도 이러한 방법으로 DNS 이전을 진행하고 있습니다.

코드를 원하는 방향으로 수정하여 사용해보시는 것을 권장합니다.

긴 글 읽어주셔서 감사합니다.