Kea DDNS 구축. DHCP가 주소를 빌려줄 때 호스트 이름의 A 레코드와 IP의 PTR 레코드를 BIND 9에 자동으로 등록하는 구성입니다. Kea DHCPv4가 DNS 존 파일을 직접 수정하는 것이 아니라, Name Change Request를 Kea D2에 보내고 D2가 RFC 2136 동적 업데이트를 TSIG로 서명해 BIND 9 primary에 전달합니다.

이 글은 10.20.30.0/24 네트워크와 lab.example.internal 내부 도메인을 예로 듭니다. 기존 ISC DHCP의 ddns-update-style 예제를 Kea에 그대로 옮기면 동작하지 않습니다. 역할 분리, 키 보호, 정방향·역방향 존의 일치, DHCID 충돌 처리와 서비스 시작 순서를 함께 맞춰야 합니다. 모든 명령과 설정은 코드 블록으로 구분했습니다.

Kea DDNS 구축에서 DHCP 임대가 BIND 9 정방향과 역방향 DNS로 갱신되는 흐름
DHCPv4가 임대 변경을 D2에 전달하고, D2가 TSIG로 서명한 업데이트를 BIND 9의 정방향·역방향 존에 반영합니다.

Kea DDNS 구축 구성 요소와 데이터 흐름

구성 요소 예시 주소 역할
Kea DHCPv4 10.20.30.10 주소 임대와 호스트 이름 정책, D2로 NCR 전송
Kea D2 127.0.0.1:53001 NCR을 DNS UPDATE 메시지로 변환하고 TSIG 서명
BIND 9 primary 10.20.30.53:53 정방향·역방향 dynamic zone의 권한 서버
클라이언트 10.20.30.100~200 DHCPREQUEST에서 이름 또는 FQDN 제공
  • 정방향 존 lab.example.internal.에는 호스트 이름에서 IPv4 주소로 찾는 A 레코드가 생성됩니다.
  • 역방향 존 30.20.10.in-addr.arpa.에는 IPv4 주소에서 FQDN으로 찾는 PTR 레코드가 생성됩니다.
  • DHCID 레코드는 다른 DHCP 클라이언트가 같은 이름을 덮어쓰는 충돌을 줄이는 데 사용됩니다.
  • TSIG는 업데이트 메시지의 인증과 무결성을 제공하지만 DNS 조회 응답 자체를 암호화하지는 않습니다.

Kea DDNS 구축 1단계: 사전 점검과 패키지 확인

Kea와 BIND 9는 지원되는 배포판 패키지 또는 승인된 ISC 저장소에서 설치합니다. 저장소를 무작정 추가하지 말고 후보 버전과 서명을 먼저 확인하십시오. 아래는 EL9 계열의 패키지 이름 예시입니다.

sudo dnf repolist
sudo dnf --showduplicates list kea kea-dhcp-ddns bind bind-utils
sudo dnf install -y kea kea-dhcp-ddns bind bind-utils policycoreutils-python-utils
rpm -q kea kea-dhcp-ddns bind bind-utils
kea-dhcp4 -V
named -V

호스트 이름, 시간, 인터페이스, 기존 DHCP·DNS 리스너 충돌을 먼저 확인합니다.

hostnamectl
chronyc tracking
ip -br address
sudo ss -luntp | grep -E ':(53|67|53001)\b' || true
getent passwd kea named

Kea DDNS 구축 2단계: TSIG 비밀키 생성과 권한 분리

BIND 9과 Kea D2가 같은 TSIG secret을 사용해야 합니다. 키를 화면이나 셸 기록에 출력하지 말고 root만 생성한 뒤, BIND용 key 블록과 Kea용 secret 파일을 각각 최소 권한으로 배치합니다. 키 이름의 끝점까지 양쪽 설정에서 정확히 일치시킵니다.

sudo install -d -o root -g named -m 0750 /etc/named/keys
sudo sh -c 'umask 077; tsig-keygen -a hmac-sha256 kea-ddns.lab.example.internal. > /etc/named/keys/kea-ddns.key'
sudo chown root:named /etc/named/keys/kea-ddns.key
sudo chmod 0640 /etc/named/keys/kea-ddns.key

Kea 2.5.8 이상에서는 설정 파일에 secret을 직접 넣는 대신 secret-file을 사용할 수 있습니다.

sudo install -d -o root -g kea -m 0750 /etc/kea/secrets
sudo awk -F'"' '/secret/{print $2}' /etc/named/keys/kea-ddns.key \
  | sudo tee /etc/kea/secrets/kea-ddns.secret >/dev/null
sudo chown root:kea /etc/kea/secrets/kea-ddns.secret
sudo chmod 0640 /etc/kea/secrets/kea-ddns.secret
sudo stat -c '%U:%G %a %n' /etc/named/keys/kea-ddns.key /etc/kea/secrets/kea-ddns.secret
TSIG secret을 게시물, 티켓, Git 저장소, 명령 출력에 복사하지 마십시오. 유출이 의심되면 새 키를 만들고 BIND와 D2를 계획된 순서로 교체한 뒤 이전 키를 폐기합니다.

Kea DDNS 구축 3단계: BIND 9 dynamic zone 설정

/etc/named.conf에 TSIG key 파일을 포함하고, 정방향·역방향 primary zone을 정의합니다.

include "/etc/named/keys/kea-ddns.key";

zone "lab.example.internal" IN {
    type primary;
    file "dynamic/db.lab.example.internal";
    allow-update { key "kea-ddns.lab.example.internal."; };
};

zone "30.20.10.in-addr.arpa" IN {
    type primary;
    file "dynamic/db.10.20.30";
    allow-update { key "kea-ddns.lab.example.internal."; };
};

allow-update는 해당 키에 존 전체 업데이트 권한을 줍니다. 더 세밀한 이름·레코드 제한이 필요하면 BIND의 update-policy를 설계하되 두 옵션을 같은 존에 동시에 사용하지 마십시오.

정방향 존 초기 파일

$TTL 300
@   IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
        2026072101  ; serial
        3600        ; refresh
        900         ; retry
        604800      ; expire
        300 )       ; minimum
    IN NS  dns01.lab.example.internal.
dns01 IN A 10.20.30.53

역방향 존 초기 파일

$TTL 300
@   IN SOA dns01.lab.example.internal. hostmaster.lab.example.internal. (
        2026072101  ; serial
        3600        ; refresh
        900         ; retry
        604800      ; expire
        300 )       ; minimum
    IN NS  dns01.lab.example.internal.
53  IN PTR dns01.lab.example.internal.
sudo install -d -o named -g named -m 0770 /var/named/dynamic
sudo install -o named -g named -m 0660 db.lab.example.internal /var/named/dynamic/
sudo install -o named -g named -m 0660 db.10.20.30 /var/named/dynamic/
sudo restorecon -Rv /etc/named/keys /var/named/dynamic
sudo named-checkconf /etc/named.conf
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo named-checkzone 30.20.10.in-addr.arpa /var/named/dynamic/db.10.20.30

dynamic zone의 저널 파일은 named가 생성합니다. SELinux를 끄거나 /var/named 전체를 0777로 바꾸지 말고, 패키지 기본 컨텍스트와 dynamic 디렉터리만 쓰기 가능하게 유지합니다.

sudo systemctl enable --now named
sudo systemctl --no-pager --full status named
sudo journalctl -u named -b --no-pager | tail -n 100
dig @127.0.0.1 SOA lab.example.internal +norecurse
dig @127.0.0.1 SOA 30.20.10.in-addr.arpa +norecurse

Kea DDNS 구축 4단계: BIND 동적 업데이트 단독 시험

Kea를 연결하기 전에 nsupdate로 BIND와 TSIG만 시험하면 장애 구간을 분리할 수 있습니다. 이 시험은 임시 A·PTR 레코드를 추가하고 확인한 뒤 삭제합니다.

sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
zone lab.example.internal.
update add ddns-test.lab.example.internal. 300 A 10.20.30.250
send
zone 30.20.10.in-addr.arpa.
update add 250.30.20.10.in-addr.arpa. 300 PTR ddns-test.lab.example.internal.
send
EOF
dig @127.0.0.1 ddns-test.lab.example.internal A +short
dig @127.0.0.1 -x 10.20.30.250 +short
sudo journalctl -u named --since '-5 minutes' --no-pager
sudo nsupdate -k /etc/named/keys/kea-ddns.key <<'EOF'
server 127.0.0.1
update delete ddns-test.lab.example.internal. A
send
update delete 250.30.20.10.in-addr.arpa. PTR
send
EOF

Kea DDNS 구축 5단계: Kea D2 설정

/etc/kea/kea-dhcp-ddns.conf를 다음과 같이 구성합니다. DNS 서버 주소는 BIND 9 primary를 가리켜야 합니다.

{
  "DhcpDdns": {
    "ip-address": "127.0.0.1",
    "port": 53001,
    "dns-server-timeout": 1000,
    "ncr-protocol": "UDP",
    "ncr-format": "JSON",
    "tsig-keys": [
      {
        "name": "kea-ddns.lab.example.internal.",
        "algorithm": "HMAC-SHA256",
        "secret-file": "/etc/kea/secrets/kea-ddns.secret"
      }
    ],
    "forward-ddns": {
      "ddns-domains": [
        {
          "name": "lab.example.internal.",
          "key-name": "kea-ddns.lab.example.internal.",
          "dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
        }
      ]
    },
    "reverse-ddns": {
      "ddns-domains": [
        {
          "name": "30.20.10.in-addr.arpa.",
          "key-name": "kea-ddns.lab.example.internal.",
          "dns-servers": [ { "ip-address": "10.20.30.53", "port": 53 } ]
        }
      ]
    },
    "loggers": [
      { "name": "kea-dhcp-ddns", "severity": "INFO" }
    ]
  }
}
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo systemctl enable --now kea-dhcp-ddns
sudo systemctl --no-pager --full status kea-dhcp-ddns
sudo ss -lunp | grep ':53001'
sudo journalctl -u kea-dhcp-ddns -b --no-pager | tail -n 100

Kea DDNS 구축 6단계: DHCPv4에서 D2 연결

다음 예시는 /etc/kea/kea-dhcp4.conf의 핵심 부분입니다. 인터페이스, 라우터, DNS 주소와 풀은 실제 네트워크에 맞게 바꿉니다. dhcp-ddns.enable-updates와 ddns-send-updates가 모두 활성화되어야 D2로 요청이 전송됩니다.

{
  "Dhcp4": {
    "interfaces-config": { "interfaces": [ "ens192" ] },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/lib/kea/kea-leases4.csv"
    },
    "renew-timer": 900,
    "rebind-timer": 1800,
    "valid-lifetime": 3600,
    "dhcp-ddns": {
      "enable-updates": true,
      "server-ip": "127.0.0.1",
      "server-port": 53001,
      "ncr-protocol": "UDP",
      "ncr-format": "JSON"
    },
    "ddns-send-updates": true,
    "ddns-override-no-update": false,
    "ddns-override-client-update": false,
    "ddns-replace-client-name": "when-not-present",
    "ddns-generated-prefix": "host",
    "ddns-qualifying-suffix": "lab.example.internal",
    "ddns-update-on-renew": false,
    "ddns-conflict-resolution-mode": "check-with-dhcid",
    "subnet4": [
      {
        "subnet": "10.20.30.0/24",
        "pools": [ { "pool": "10.20.30.100 - 10.20.30.200" } ],
        "option-data": [
          { "name": "routers", "data": "10.20.30.1" },
          { "name": "domain-name-servers", "data": "10.20.30.53" },
          { "name": "domain-name", "data": "lab.example.internal" }
        ]
      }
    ],
    "loggers": [ { "name": "kea-dhcp4", "severity": "INFO" } ]
  }
}

ddns-replace-client-name은 이름이 없을 때만 생성하도록 설정했습니다. 관리 정책상 서버가 모든 이름을 강제로 정해야 한다면 always를 검토할 수 있지만, 기존 클라이언트 이름과 충돌하지 않는 예약·명명 규칙을 먼저 설계하십시오.

sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo systemctl enable --now kea-dhcp4
sudo systemctl --no-pager --full status kea-dhcp4
sudo ss -lunp | grep -E ':(67|53001)\b'
sudo journalctl -u kea-dhcp4 -b --no-pager | tail -n 100

Kea DDNS 구축 7단계: 실제 A·PTR·DHCID 검증

테스트 클라이언트에서 DHCP 임대를 새로 받은 뒤, 실제 할당 주소와 FQDN을 기준으로 정방향·역방향을 모두 조회합니다. 운영 인터페이스의 임대 갱신은 연결을 끊을 수 있으므로 콘솔 또는 별도 테스트 단말에서 수행하십시오.

# 테스트 클라이언트에서 실제 환경에 맞는 방법으로 임대를 갱신
sudo dhclient -r ens192
sudo dhclient -v ens192
ip -4 address show dev ens192
dig @10.20.30.53 client01.lab.example.internal A +noall +answer
dig @10.20.30.53 -x 10.20.30.101 +noall +answer
dig @10.20.30.53 client01.lab.example.internal DHCID +noall +answer
sudo journalctl -u kea-dhcp4 -u kea-dhcp-ddns -u named \
  --since '-10 minutes' --no-pager
증상 먼저 확인할 항목 대표 원인
A와 PTR 모두 없음 DHCPv4 로그와 53001 UDP D2 연결 비활성화, 이름 정책, D2 중지
A만 생성 reverse-ddns 도메인과 역방향 존 역방향 존 이름 또는 DNS 서버 주소 불일치
NOTAUTH BIND primary와 zone 선언 secondary로 전송, 존 이름 불일치
NOTAUTH 또는 REFUSED TSIG key 이름·secret·algorithm 키 불일치, allow-update 미허용
YXDOMAIN·충돌 기존 A·DHCID와 클라이언트 식별자 다른 클라이언트가 같은 이름을 소유

Kea DDNS 운영: dynamic zone 파일을 직접 수정하지 않기

named가 실행 중인 dynamic zone은 .jnl 저널과 함께 관리됩니다. 존 파일을 직접 편집하면 변경 내용이 덮어써지거나 저널과 불일치할 수 있습니다. 수동 변경이 꼭 필요하면 먼저 동기화하고 해당 존을 freeze한 뒤 검증 후 thaw합니다.

sudo rndc sync -clean lab.example.internal
sudo rndc freeze lab.example.internal
sudoedit /var/named/dynamic/db.lab.example.internal
sudo named-checkzone lab.example.internal /var/named/dynamic/db.lab.example.internal
sudo rndc thaw lab.example.internal
sudo rndc zonestatus lab.example.internal
오류를 빨리 없애려고 .jnl 파일, A·PTR·DHCID 레코드를 일괄 삭제하지 마십시오. 먼저 DHCP 임대 소유자, 정방향·역방향 레코드와 DHCID를 함께 확인하고 서비스 중지·백업·복구 순서를 정합니다.

방화벽·SELinux·권한 점검

클라이언트와 권한 DNS 서버 사이에는 DNS 53/TCP·UDP가, DHCP 서버와 클라이언트 사이에는 DHCP 포트가 필요합니다. D2의 53001/UDP는 같은 호스트의 loopback에만 바인딩했으므로 외부에 열지 않습니다.

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --zone=internal --add-service=dns
sudo firewall-cmd --permanent --zone=internal --add-service=dhcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=internal --list-all
sudo ss -luntp | grep -E ':(53|67|53001)\b'
sudo ausearch -m AVC -ts recent | tail -n 50
firewalld나 SELinux를 끄는 방식은 해결책이 아닙니다. named는 dynamic 디렉터리에만 쓰게 하고, D2의 secret 파일은 kea 사용자만 읽게 하며, TSIG 권한은 필요한 존에만 부여합니다.

고가용성·백업·모니터링 체크리스트

  1. Kea HA의 두 DHCP 서버가 D2와 authoritative DNS에 도달하는지 failover 시험으로 검증합니다.
  2. DNS secondary는 primary의 NOTIFY·IXFR/AXFR로 동기화하고 D2 업데이트 대상은 primary로 명확히 지정합니다.
  3. Kea 설정, lease 데이터베이스, BIND 설정·zone·journal, TSIG 키를 서로 다른 보안 등급으로 백업합니다.
  4. 키 복원 권한을 제한하고 정기적으로 키 교체와 서비스 재시작 순서를 연습합니다.
  5. DHCP 임대 성공뿐 아니라 D2 queue, DNS UPDATE 결과, A·PTR 불일치와 존 serial을 관측합니다.
sudo systemctl is-active kea-dhcp4 kea-dhcp-ddns named
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
sudo kea-dhcp-ddns -t /etc/kea/kea-dhcp-ddns.conf
sudo named-checkconf -z /etc/named.conf
sudo rndc status
sudo rndc zonestatus lab.example.internal

함께 읽을 글

정리

안전한 Kea DDNS 구축. DHCPv4, D2, BIND 9을 한 번에 시작하는 것이 아니라 BIND 존 검증, TSIG 단독 시험, D2 연결, 실제 DHCP 임대 시험 순으로 경계를 나눠 확인해야 합니다. 정방향 A와 역방향 PTR, DHCID를 함께 관찰하고 secret 권한·dynamic zone 저널·백업과 키 교체 절차까지 운영 기준에 포함해야 자동화가 데이터 정합성을 해치지 않습니다.