Skip to document
pieroot log
Markdown Preview

OpenStack 환경에서 Designate를 활용한 내부 DNS 서버 구축 가이드

Frontmatter

OpenStack 환경에 Designate를 활용해 내부 DNS 서비스를 구축하는 전체 과정을 안내한다. Keystone에 사용자·서비스를 등록하고 API 엔드포인트를 추가한 뒤, Designate와 BIND9 패키지를 설치하고 데이터베이스·RabbitMQ·Keystone 연동 설정을 진행한다. BIND9에 rndc 키와 AppArmor 권한을 설정하고, named.conf.options와 designate.conf를 구성한다. pools.yaml 파일로 DNS 서버 풀을 정의하고 업데이트한 후, Designate 서비스(API, Central, Worker, Producer, Mini DNS)를 시작한다. 마지막으로 designate‑dashboard 플러그인을 설치·설정해 Horizon에 DNS 관리 UI를 추가하고, zone·recordset 생성으로 동작을 검증한다.

OpenStack 환경에서 Designate를 활용한 내부 DNS 서버 구축 가이드
OpenStack 내부에 GitLab 서버와 GitLab Runner가 함께 돌아가고 있는 상황이다. 문제는 Runner가 이미지를 빌드할 때 public network를 거쳐서 데이터를 받아오면, 가뜩이나 느린 네트워크에서 더 말도 안 되게 느린 속도를 자랑한다는 것이다.
그래서 내부에 자체 DNS 서버를 구성하려고 했는데, SSL 인증이 시원찮은 관계로 다른 방법을 찾던 중 — OpenStack 서비스들 중에 DNS를 제공하는 놈이 있다는 소식을 듣게 되었다. 바로 Designate다.
이번 글에서는 OpenStack 환경에 Designate(DNSaaS)를 설치하고, Horizon 대시보드까지 연동하는 전체 과정을 다룬다.
📌
이 글에서 다루는 내용
  • Designate란 무엇인가, 왜 필요한가
  • Keystone에 서비스 등록 및 엔드포인트 생성
  • BIND9 설치 및 연동 설정
  • designate.conf 주요 설정 항목 해설
  • pools.yaml 구성과 DNS 풀 업데이트
  • Horizon 대시보드 플러그인 설치
  • 트러블슈팅 (rndc.key 권한 문제 등)

🤔 Designate란?

⚠️
주의
Designate는 Keystone, RabbitMQ, MySQL(또는 MariaDB)에 의존한다. 이 서비스들이 불안정한 상태에서 Designate를 설치하면 디버깅 지옥에 빠질 수 있다. 경험담이다
notion image
Designate는 OpenStack의 DNSaaS(DNS as a Service) 프로젝트다. 위 링크의 제목처럼 DNS를 위한 서비스인데, 요즘 뭐든 ...as a Service를 붙이면 좀 더 그럴듯해 보이는지 여기저기 다 붙는다. LBaaS, DNSaaS, 다음은 뭐가 나올까
쉽게 말하면, REST API로 DNS 레코드(zone, record)를 관리할 수 있고, 뒤에서는 BIND9이나 PowerDNS 같은 실제 DNS 서버를 조종하는 구조다.
💡
Designate의 핵심 구성 요소
  • API: 외부 요청을 받는 REST API 서버 (포트 9001)
  • Central: 비즈니스 로직을 처리하고 DB에 데이터를 저장하는 핵심 서비스
  • Worker: 실제 DNS 서버(BIND9 등)에 zone 변경 사항을 전파하는 역할
  • Producer: 주기적인 작업(zone 만료, 갱신 등)을 스케줄링
  • Mini DNS (mdns): zone transfer(AXFR)를 담당하는 경량 DNS 서버
  • Message Queue: 컴포넌트 간 통신을 위한 RabbitMQ
위 그림처럼 여러 API와 컴포넌트들이 유기적으로 연결되어 동작한다. Designate API로 요청이 들어오면 Central이 처리하고, Worker가 BIND9에 반영하는 흐름이다.
📝
Neutron/Nova 연동도 가능하다
Designate는 Neutron과 Nova의 notification을 받아서 floating IP가 할당되거나 인스턴스가 생성될 때 자동으로 DNS 레코드를 생성할 수 있다. 다만 이번 글에서는 기본 설치에 집중하고, Neutron 연동은 다루지 않는다.

🔧 사전 준비

우선 OpenStack에서 기본 패키지(Nova, Neutron, Keystone, Glance, Placement)는 모두 설치가 되어있다는 가정 하에 진행된다. 모든 기능이 정상 동작하고 안정화된 상태에서 진행하기를 권장한다.
우리가 해야 할 건 크게 두 가지다:
  1. Designate 서비스 설치 (Keystone 등록 → BIND9 설정 → Designate 설정 → 패키지 기동)
  1. Horizon에서 사용 가능하도록 대시보드 플러그인 설치

⚙️ 서비스 생성

기존에 만들어두었던 openrc 파일을 통해 환경변수를 설정한다.
bash
source admin-openrc

Keystone에 사용자 및 서비스 등록

Designate 전용 사용자를 생성하고, 서비스 프로젝트에 admin 역할을 부여한다.
bash
# designate 사용자 생성
openstack user create --domain default --password-prompt designate

# service 프로젝트에 admin 역할 할당
openstack role add --project service --user designate admin

# DNS 서비스 등록
openstack service create --name designate --description "DNS" dns
📝
각 명령어 해설
  • openstack user create: Keystone에 designate 사용자를 생성한다. --password-prompt로 비밀번호를 직접 입력
  • openstack role add: designate 사용자에게 service 프로젝트의 admin 권한을 부여한다. 이게 있어야 다른 OpenStack 서비스들과 통신할 수 있다
  • openstack service create: Keystone 서비스 카탈로그에 DNS 타입으로 등록한다

API 엔드포인트 추가

DNS 서비스를 API 엔드포인트에 추가한다.
bash
openstack endpoint create --region RegionOne \
  dns public http://dms-controller:9001

openstack endpoint create --region RegionOne \
  dns internal http://dms-controller:9001

openstack endpoint create --region RegionOne \
  dns admin http://dms-controller:9001
레퍼런스에는 public만 추가하면 된다고 나와있지만 그냥 다 추가하면 된다. 그리고 이런 엔드포인트는 가급적이면 VIP나, VIP가 연결되어있는 hostname으로 설정하는 게 좋다.
💡
왜 9001 포트인가?
Designate API의 기본 리슨 포트가 9001이다. 이 포트는 나중에 designate.conf의 [service:api] 섹션에서 변경할 수 있지만, 공식 문서 기준으로 9001을 사용한다.

📦 패키지 설치

Designate 패키지

bash
apt-get install designate

데이터베이스 생성

designate 전용 데이터베이스와 사용자를 생성한다.
sql
-- MySQL에 접속하여 실행
CREATE DATABASE designate CHARACTER SET utf8 COLLATE utf8_general_ci;

GRANT ALL PRIVILEGES ON designate.* TO 'designate'@'localhost' \
IDENTIFIED BY 'DESIGNATE_DBPASS';

GRANT ALL PRIVILEGES ON designate.* TO 'designate'@'%' \
IDENTIFIED BY 'DESIGNATE_DBPASS';
⚠️
비밀번호 보안
DESIGNATE_DBPASS는 반드시 강력한 비밀번호로 변경해야 한다. 복잡한 것보다 길이가 긴 게 훨씬 중요하다. 최소 20자 이상을 권장한다.

BIND9 설치

Linux에서 DNS 설정을 하기 위해 주로 사용되는 BIND9 패키지를 설치한다.
bash
apt-get install bind9 bind9utils bind9-doc
BIND9이 무엇을 하는지 깊게 서술하지는 않겠지만, 간단하게 말하면 실제 DNS 레코드를 관리하고 질의에 응답하는 서비스를 이 BIND9이 수행한다. Designate는 BIND9의 "관리자" 역할이고, BIND9이 실제 "일꾼"인 셈이다.
💡
BIND9 말고 다른 선택지는?
Designate는 플러그인 방식으로 다양한 DNS 백엔드를 지원한다:
  • BIND9: 가장 널리 사용되는 오픈소스 DNS 서버. 안정적이고 문서가 풍부하다
  • PowerDNS: 고성능 DNS 서버. SQL 기반 레코드 관리가 가능하여 대규모 환경에 적합하다
  • Akamai, Infoblox: 상용 DNS 서비스와의 연동도 가능
이 글에서는 가장 범용적인 BIND9을 사용한다.

🔑 BIND9 설정

rndc 키 생성

BIND9에 명령을 전달하기 위한 rndc(Remote Name Daemon Control) 인증 키를 생성한다.
bash
rndc-confgen -a -k designate -c /etc/designate/rndc.key
📝
rndc란?
rndc는 BIND9을 원격으로 제어하기 위한 도구다. Designate의 Worker가 zone을 추가하거나 삭제할 때 이 rndc를 통해 BIND9에 명령을 전달한다. 인증 키가 없으면 아무나 DNS 서버를 조작할 수 있으니, 반드시 키 기반 인증을 사용해야 한다.

AppArmor 설정

Ubuntu에서는 AppArmor가 BIND9의 파일 접근을 제한하고 있다. rndc.key 파일에 접근할 수 있도록 설정을 추가해야 한다.
/etc/apparmor.d/usr.sbin.named 파일에 아래 한 줄을 추가한다:
bash
/etc/designate/rndc.key r,
이 설정은 BIND9(named) 프로세스가 designate의 rndc.key 파일을 읽을 수 있도록 허용하는 것이다. 추가한 뒤 AppArmor를 재시작한다:
bash
sudo systemctl reload apparmor.service

named.conf.options 수정

BIND9의 핵심 설정 파일인 /etc/bind/named.conf.options를 수정한다. ...으로 표시된 부분은 기존 내용을 유지하라는 의미다.
bash
...
include "/etc/designate/rndc.key";

options {
    ...
    allow-new-zones yes;
    request-ixfr no;
    listen-on port 53 { 127.0.0.1; };
    recursion no;
    allow-query { 127.0.0.1; };
};

controls {
  inet 127.0.0.1 port 953
    allow { 127.0.0.1; } keys { "designate"; };
};
📝
설정값 해설
  • include "/etc/designate/rndc.key": 아까 생성한 rndc 키 파일을 로드한다
  • allow-new-zones yes: Designate가 동적으로 새 zone을 추가할 수 있도록 허용
  • request-ixfr no: 증분 zone transfer(IXFR) 대신 전체 transfer(AXFR)를 사용. Designate의 mdns와의 호환성을 위해 끄는 게 안전하다
  • listen-on port 53 { 127.0.0.1; }: DNS 서비스를 로컬에서만 리슨. 외부 직접 질의를 차단한다
  • recursion no: 재귀 질의를 비활성화. 이 서버는 authoritative DNS 전용이다
  • allow-query { 127.0.0.1; }: 로컬에서만 질의를 허용
  • controls: rndc 명령을 127.0.0.1:953에서만 수신하고, designate 키로 인증한다
notion image
설정값들을 넣으면 위 사진처럼 될 것이다.
DNS 서비스를 재시작한다:
bash
systemctl restart bind9.service

⚙️ Designate 설정

이제 본격적으로 Designate의 설정 파일인 /etc/designate/designate.conf를 수정한다.
⚠️
주의!
이 파일을 열어본 사람이라면 당황스러울 수 있다. 아래에서 설명하는 섹션들 중 상당수가 기본 파일에는 존재하지 않는다. 없는 섹션은 파일 맨 아래에 그대로 추가해주면 된다.

API 서비스 설정

toml
[service:api]
listen = 0.0.0.0:9001
auth_strategy = keystone
enable_api_v2 = True
enable_api_admin = True
enable_host_header = True
enabled_extensions_admin = quotas, reports
📝
설정 해설
  • listen = 0.0.0.0:9001: 모든 인터페이스의 9001 포트에서 API 요청을 수신한다
  • auth_strategy = keystone: Keystone을 통해 인증을 수행한다
  • enable_api_v2 = True: Designate API v2를 활성화. 현재 표준 API 버전이다
  • enable_api_admin = True: 관리자용 API를 활성화. quota 관리 등에 필요하다
  • enable_host_header = True: HTTP Host 헤더를 사용한 라우팅을 허용한다
  • enabled_extensions_admin: 관리자 API에서 사용할 확장 기능(quota, report)을 지정

Keystone 인증 설정

toml
[keystone_authtoken]
auth_type = password
username = designate
password = DESIGNATE_PASS
project_name = service
project_domain_name = Default
user_domain_name = Default
www_authenticate_uri = http://dms-controller:5000/
auth_url = http://dms-controller:5000/
memcached_servers = dms-controller1:11211,dms-controller2:11211
여느 OpenStack 서비스와 마찬가지로 Keystone 인증 토큰 설정이다. DESIGNATE_PASS는 아까 openstack user create에서 입력한 비밀번호와 동일해야 한다.

RabbitMQ 설정

toml
[DEFAULT]
# ...
transport_url = rabbit://openstack:RABBIT_PASS@dms-controller1:5672,openstack:RABBIT_PASS@dms-controller2:5672
💡
RabbitMQ 클러스터 팁
RabbitMQ 서버가 클러스터로 여러 개 있다면, memcached 서버처럼 여러 개의 URL을 나열하는 게 좋다. 하나가 죽어도 나머지에서 메시지를 처리할 수 있어 가용성이 올라간다. 물론 단일 서버라면 하나만 적어도 된다.

데이터베이스 연결 설정

toml
[storage:sqlalchemy]
connection = mysql+pymysql://designate:DESIGNATE_DBPASS@dms-controller/designate
아까 MySQL에서 생성한 designate 데이터베이스에 연결하는 설정이다. 이 섹션도 기본 파일에는 없으니 그대로 파일 맨 아래에 추가하면 된다.

데이터베이스 동기화

설정 파일이 완료되었다면 데이터베이스 스키마를 동기화한다:
bash
su -s /bin/sh -c "designate-manage database sync" designate
📝
su -s /bin/sh -c "..." designate가 뭔지 궁금하다면?
designate 사용자 권한으로 명령어를 실행하는 것이다. OpenStack 서비스들은 보안을 위해 각자 전용 시스템 사용자를 가지고 있고, DB 마이그레이션도 해당 사용자 권한으로 수행하는 것이 원칙이다.

핵심 서비스 시작

bash
# API와 Central 서비스 시작
systemctl start designate-central designate-api

# 부팅 시 자동 시작 설정
systemctl enable designate-central designate-api

🏊 Pools 설정

Designate에서 Pool은 DNS 서버들의 그룹이다. 어떤 네임서버를 사용할지, zone transfer는 어디서 받을지 등을 정의한다.
/etc/designate/pools.yaml 파일을 생성하고 아래 내용을 넣는다:
yaml
- name: default
  # Pool 이름은 한번 생성하면 변경 불가. 삭제 후 재생성만 가능하다.
  description: Default Pool

  attributes: {}

  # 이 Pool에서 호스팅하는 zone의 NS 레코드 목록
  # Designate 외부에서 생성된 레코드여야 하며,
  # 컨트롤러 노드의 public IP를 가리켜야 한다.
  ns_records:
    - hostname: ns1.dmslab.xyz.
      priority: 1

  # 실제 BIND 서버 목록. zone 변경이 전파되었는지 확인하는 데 사용한다.
  nameservers:
    - host: 127.0.0.1
      port: 53

  # BIND 서버 타겟 목록. 각 BIND 서버마다 하나의 엔트리가 필요하다.
  targets:
    - type: bind9
      description: BIND9 Server 1

      # zone transfer(AXFR)를 요청할 designate-mdns 서버 목록
      # 컨트롤러 노드의 IP를 지정한다.
      masters:
        - host: 127.0.0.1
          port: 5354

      # BIND9 연결 옵션
      options:
        host: 127.0.0.1
        port: 53
        rndc_host: 127.0.0.1
        rndc_port: 953
        rndc_key_file: /etc/bind/designate-rndc.key
📝
pools.yaml 동작 원리
간단히 정리하면 이런 흐름이다:
  1. Designate API로 zone 생성 요청이 들어온다
  1. Central이 요청을 처리하고 DB에 저장한다
  1. Worker가 pool 설정을 참고하여 targets에 정의된 BIND9 서버에 zone을 생성한다
  1. Worker는 rndc(953 포트)를 통해 BIND9에 명령을 전달한다
  1. mdns(5354 포트)가 BIND9의 zone transfer 요청에 응답한다
  1. nameservers에 정의된 서버에서 zone이 정상 전파되었는지 확인한다
즉, Designate가 "이 zone 추가해줘"라고 rndc로 BIND9에 알려주고, BIND9이 mdns한테 "zone 데이터 줘"라고 AXFR 요청을 보내는 구조다.
⚠️
ns_records의 hostname에 주의!
ns1.dmslab.xyz. 끝에 점(.)이 붙어있는 것에 주목하자. DNS에서 FQDN(Fully Qualified Domain Name)은 반드시 마침표로 끝나야 한다. 이걸 빠뜨리면 상대 도메인으로 해석되어 이상한 결과가 나올 수 있다.
Pool 정보를 갱신한다:
bash
su -s /bin/sh -c "designate-manage pool update" designate

나머지 패키지 설치 및 시작

bash
# Worker, Producer, Mini DNS 설치
apt install designate-worker designate-producer designate-mdns

# 서비스 시작
systemctl start designate-worker designate-producer designate-mdns

# 부팅 시 자동 시작 설정
systemctl enable designate-worker designate-producer designate-mdns

설치 확인

아래 명령어로 DNS 서비스가 정상적으로 올라왔는지 확인한다:
bash
openstack dns service list
notion image
위와 같이 모든 서비스가 UP 상태로 표시되면 Designate 설치는 마무리다. 🎉

🖥️ Horizon 대시보드 연동

Horizon은 가장 기본이 되는 패키지들만 기본 지원하기 때문에, 추가 설치되는 서비스들은 별도로 플러그인을 설치해서 넣어주어야 한다. 위 링크는 designate-dashboard 플러그인의 Git 저장소다.

소스 받기

bash
git clone https://opendev.org/openstack/designate-dashboard.git
cd designate-dashboard/

패키지 설치

공식 가이드에서는 python3 setup.py sdist로 tarball을 만든 뒤 설치하라고 하지만, 나는 그냥 pip로 설치했다:
bash
pip install designate_dashboard
삽질을 줄이는 가장 좋은 방법은 복잡한 걸 단순화하는 거다

패널 플러그인 및 설정 파일 복사

bash
# 패널 파일 복사 (Horizon이 Designate 메뉴를 표시할 수 있도록)
cp designatedashboard/enabled/*.py \
  /usr/share/openstack-dashboard/openstack_dashboard/local/enable/

# 기본 구성 파일 복사
mkdir -p /etc/openstack-dashboard/local_settings.d/
cp designatedashboard/local_settings.d/* \
  /etc/openstack-dashboard/local_settings.d/
📝
파일 복사 위치 해설
  • enabled/: Horizon의 패널 등록 파일이 들어가는 곳. 여기에 파일을 넣으면 Horizon이 자동으로 Designate 관련 메뉴를 로드한다
  • local_settings.d/: Horizon의 설정 파일 디렉토리. 기존 local_settings.py를 덮어쓰면 안 되기 때문에 별도 디렉토리에 넣어준다. Horizon은 이 디렉토리의 설정 파일들을 자동으로 읽어들인다
⚠️
local_settings.py를 절대 덮어쓰지 말 것!
Horizon의 핵심 설정 파일인 local_settings.py를 designate 설정으로 덮어쓰면 Horizon 전체가 먹통이 될 수 있다. 반드시 local_settings.d/ 디렉토리를 사용하자.

정적 파일 갱신 및 재시작

bash
cd /usr/share/openstack-dashboard/

# 정적 파일(CSS, JS 등) 압축 및 갱신
python3 manage.py compress

# Horizon 재시작
systemctl restart apache2.service
플러그인을 설치한 뒤에는 정적 파일을 갱신해야 새로운 CSS와 JavaScript가 반영된다. manage.py compress가 이 작업을 수행하고, apache2를 재시작하면 Horizon이 새로고침된다.
notion image
notion image
위와 같이 Horizon에 DNS 메뉴가 나타나고, Zone 관리 화면이 정상적으로 표시된다면 모두 끝이다. ✅

🔧 트러블슈팅

rndc.key 파일 접근 오류

만약 BIND9 설정 단계에서 정상적으로 동작하지 않고, rndc.key 파일을 연결할 수 없다는 오류가 발생한다면 아래를 따라해보자.
notion image
위와 같은 오류는 named.conf.options에서 rndc.key 파일을 읽지 못하는 문제다. 파일이 존재하더라도 권한 문제로 접근이 안 될 수 있다.

1. 디렉토리 및 파일 권한 확인

Ubuntu 기준으로 BIND9 관련 패키지는 bind 계정과 그룹을 사용한다. 하지만 /etc/designate/ 디렉토리는 root와 designate 사용자/그룹이 소유하고 있다.
notion image
rndc.key 파일뿐만 아니라 디렉토리 자체가 외부 사용자의 접근을 차단하고 있기 때문에, 파일의 경로를 알더라도 읽을 수 없다.
notion image

2. 읽기 권한 추가

디렉토리와 파일에 other 사용자의 읽기 권한을 추가해야 한다:
bash
# 디렉토리에 읽기+실행 권한 추가 (디렉토리 진입을 위해 실행 권한도 필요)
sudo chmod o+rx /etc/designate/

# rndc.key 파일에 읽기 권한 추가
sudo chmod o+r /etc/designate/rndc.key
💡
더 안전한 방법
chmod o+r은 모든 사용자에게 읽기 권한을 주는 것이라 보안상 최선은 아니다. 좀 더 안전하게 하려면 bind 사용자를 designate 그룹에 추가하는 방법도 있다:
bash
sudo usermod -aG designate bind
sudo chmod g+r /etc/designate/rndc.key
이렇게 하면 bind 사용자만 designate 그룹 권한으로 파일을 읽을 수 있다.
권한을 변경한 뒤 BIND9을 재시작하면 정상적으로 rndc.key를 로드할 수 있다:
bash
sudo systemctl restart bind9.service

그래도 안 된다면?

rndc.key 파일의 경로 자체를 BIND9이 접근하기 쉬운 곳으로 변경하는 방법도 있다:
bash
# rndc.key를 BIND9 디렉토리로 복사
sudo cp /etc/designate/rndc.key /etc/bind/designate-rndc.key
sudo chown bind:bind /etc/bind/designate-rndc.key

# named.conf.options의 include 경로도 변경
# include "/etc/designate/rndc.key" → include "/etc/bind/designate-rndc.key"
이 경우 pools.yaml의 rndc_key_file 경로도 함께 변경해야 한다는 점을 잊지 말자.

✅ 동작 확인

모든 설치가 완료되었으면 간단한 zone을 생성하여 테스트해보자.
bash
# zone 생성
openstack zone create --email admin@dmslab.xyz dmslab.xyz.

# zone 목록 확인
openstack zone list

# zone에 A 레코드 추가
openstack recordset create --record '192.168.1.100' --type A dmslab.xyz. test

# 레코드 확인
openstack recordset list dmslab.xyz.
📝
zone 생성 시 주의사항
  • zone 이름 끝에 점(.)을 반드시 붙여야 한다. dmslab.xyz가 아니라 dmslab.xyz.이다
  • --email 옵션은 SOA 레코드의 관리자 이메일로 사용된다. 실제 메일이 전송되지는 않지만, 유효한 형식이어야 한다
  • zone 생성 후 상태가 ACTIVE가 되어야 정상이다. ERROR라면 Worker 로그를 확인하자
bash
# 로컬 DNS에 직접 질의하여 확인
dig @127.0.0.1 test.dmslab.xyz A
응답이 정상적으로 돌아온다면 Designate가 완벽하게 동작하는 것이다. 🚀

핵심 정리

✅ Designate: OpenStack의 DNSaaS 서비스. REST API로 DNS 레코드를 관리하고, 뒤에서 BIND9이 실제 DNS를 서빙한다
✅ 구성 요소: API(9001) → Central → Worker → BIND9(53), 그리고 mdns(5354)가 zone transfer를 담당
✅ BIND9 연동: rndc(953)를 통해 Designate가 BIND9에 명령을 전달하는 구조
✅ pools.yaml: DNS 서버 풀을 정의. nameservers, targets, masters 설정이 핵심
✅ Horizon 플러그인: designate-dashboard를 설치하고 패널 파일을 복사한 뒤 정적 파일 갱신
✅ AppArmor/권한: Ubuntu에서는 AppArmor와 파일 권한 설정을 반드시 확인해야 한다

주의사항

⚠️ designate.conf의 빈 섹션: [service:api], [storage:sqlalchemy] 등은 기본 파일에 없다. 직접 추가해야 한다
⚠️ rndc.key 권한: BIND9(bind 사용자)이 /etc/designate/rndc.key를 읽을 수 있어야 한다. 권한 문제가 가장 흔한 트러블 원인
⚠️ FQDN 마침표: zone 이름과 NS 레코드의 hostname은 반드시 마침표(.)로 끝나야 한다
⚠️ Pool 업데이트: pools.yaml을 수정한 후에는 반드시 designate-manage pool update를 실행해야 한다
⚠️ Horizon local_settings.py: 기존 설정 파일을 덮어쓰지 말고, local_settings.d/ 디렉토리를 사용하자

참고 자료