第5章:DNS (BIND) の一括移行と自動化

← 目次に戻る

移行の背景:
ISPConfig 3.3.xのRemote APIにおけるバリデーション問題を回避し、TXTレコードの文字切れを防ぐため、内部ライブラリを利用した「生SQLインポート」方式を採用。43ドメイン中41ドメインの一括処理に成功した実戦の記録である。

5.1 実管理ドメインリストの作成 (CentOS 6)

標準ゾーン(localhost等)や RBL ゾーンを除外し、必要なドメインだけを抽出せよ。

# [CentOS 6 側] 再帰検索とノイズ除去
grep -r "^zone" /etc/named* | awk -F\" '{print $2}' | \
grep -vE "^\.|\.\.|localhost|localdomain|^0\.|^127\.|^255\.|rbl\.dazoo" | \
sort -u > /root/all_domains.txt

# [Ubuntu(227) 側] 転送先でviを使い不要なドメインを最終清掃
vi /root/all_domains.txt
    

5.2 全レコードの物理吸い出し (Ubuntu 側)

リストを元に 226番(1st DNS) から dig axfr を実行し、全データを保存せよ。

# [Ubuntu(227) 側]
mkdir -p /root/zones
while read dom; do
    echo "Fetching: $dom"
    dig axfr $dom @192.168.200.226 > /root/zones/$dom.zone
done < /root/all_domains.txt
    

5.3 バルク・インポート・スクリプトの実行

以下の PHP スクリプトを /root/bulk_dns_import_final.php として作成・実行せよ。変数の初期化、TXTレコードの完全取得、SQLエスケープ処理を実装済みである。

#!/bin/bash
# 依存パッケージの導入
sudo apt install -y php-cli php-mysql
# スクリプトの実行
sudo php /root/bulk_dns_import_final.php
    

付録:bulk_dns_import_final.php (Verified Edition)

<?php
require_once '/usr/local/ispconfig/interface/lib/config.inc.php';
require_once '/usr/local/ispconfig/interface/lib/app.inc.php';

if (posix_getuid() !== 0) die("Error: Run as root.\n");

function format_dns_name($name, $origin) {
    $name = trim($name);
    if ($name == '@' || $name == '') return $origin;
    if (substr($name, -1) != '.') $name .= '.';
    return $name;
}

$zone_dir = '/root/zones/';
$server_id = 1; 
$sys_userid = 1; $sys_groupid = 1;
$app->uses('functions');

// 1. 重複防止のための物理初期化
$app->db->query("TRUNCATE TABLE dns_soa");
$app->db->query("TRUNCATE TABLE dns_rr");
$app->db->query("TRUNCATE TABLE sys_datalog");

$files = glob($zone_dir . "*.zone");
foreach ($files as $file) {
    $domain = basename($file, ".zone");
    $origin = $domain . '.';
    $dns_soa_id = 0; // ループごとにリセット
    $dns_rr_list = array();

    echo "Importing $domain: ";
    $lines = file($file);

    foreach($lines as $line) {
        $line = trim($line); if ($line == '' || $line[0] == ';') continue;
        $parts = preg_split('/\s+/', $line);
        $type_idx = -1;
        foreach($parts as $idx => $p) {
            $u = strtoupper($p);
            if (in_array($u, ['SOA','A','AAAA','MX','NS','CNAME','TXT','PTR'])) {
                $type_idx = $idx; $resource_type = $u; break;
            }
        }
        if ($type_idx == -1) continue;

        if ($resource_type == 'SOA') {
            $ns = $app->db->escape(trim($parts[$type_idx+1]));
            $mbox = $app->db->escape(trim($parts[$type_idx+2]));
            $sql = "INSERT INTO dns_soa SET " .
                   "sys_userid=$sys_userid, sys_groupid=$sys_groupid, sys_perm_user='riud', sys_perm_group='riud', " .
                   "server_id=$server_id, origin='$origin', ns='$ns', mbox='$mbox', " .
                   "serial=".intval($parts[$type_idx+3]).", refresh=".intval($parts[$type_idx+4]).", " .
                   "retry=".intval($parts[$type_idx+5]).", expire=".intval($parts[$type_idx+6]).", " .
                   "minimum=".intval($parts[$type_idx+7]).", ttl=3600, active='Y'";
            if($app->db->query($sql)) { $dns_soa_id = $app->db->insert_id(); }
        } else {
            $data_all = implode(' ', array_slice($parts, $type_idx + 1));
            $aux = 0; $data = $data_all;
            if ($resource_type == 'MX') { $aux = intval($parts[$type_idx+1]); $data = trim($parts[$type_idx+2]); }
            elseif ($resource_type == 'TXT') { $data = preg_replace('/^"|"$/', '', trim($data_all)); }
            $dns_rr_list[] = ["name" => format_dns_name($parts[0], $origin), "type" => $resource_type, "data" => $data, "aux" => $aux];
        }
    }

    if ($dns_soa_id > 0) {
        $rr_count = 0;
        foreach($dns_rr_list as $rr) {
            $name_esc = $app->db->escape($rr['name']);
            $data_esc = $app->db->escape($rr['data']);
            $sql = "INSERT INTO dns_rr SET " .
                   "sys_userid=$sys_userid, sys_groupid=$sys_groupid, sys_perm_user='riud', sys_perm_group='riud', " .
                   "server_id=$server_id, zone=$dns_soa_id, name='$name_esc', type='{$rr['type']}', " .
                   "data='$data_esc', aux={$rr['aux']}, ttl=3600, active='Y'";
            if($app->db->query($sql)) $rr_count++;
        }
        echo "SUCCESS (ID: $dns_soa_id, Records: $rr_count)\n";
    } else { echo "FAILED (SOA parse error)\n"; }
}
?>
    

5.4 仕上げと連携

UIの Tools > Resync 実行後、BIND設定が /etc/bind/pri.* に反映されているか確認せよ。

ls -l /etc/bind/pri.*