Windowsネットワーク負荷分散でActive Directoryと同居するDNSサーバをロードバランス&冗長化&死活監視をする

# はじめに
:::note warn
本記事で実施している内容はMicrosoftも推奨していません。
特にActive Directoryのドメインコントローラに複数のNICを取り付けたり、IPアドレスを設定するマルチホーム構成もダメです。
どうしてもWindows ServerのDNSサーバでNLBしたいんだ！という方はどうぞ読み進めてください。
:::
今回のテスト環境は下記の構成です。
| PC名 | MACHINE1 | MACHINE2 |
|:-:|:-:|:-:|
| OS | WS2022 Datacenter | WS2022 Datacenter |
| IPAddr | 172.16.32.1/24 | 172.16.32.2/24 |
| DNS | 127.0.0.1<br>172.16.32.2 | 127.0.0.1<br>172.16.32.1 |
| Gateway | 172.16.32.254 | 172.16.32.254 |


# 作業
> すでにADの構築が完了し、ドメインコントローラが最低2台以上で動作していることを前提とします。

## (前準備)追加されるIPアドレスをDNSにレジストされないようにする
NLBを使用する場合に一番問題となるのが**複数のNICのIPやマルチキャスト設定のIPアドレスがDNSにレジストされてしまう**事です。

特にActive DirectoryのドメインコントローラーにNLBを設定しようとするとIPアドレスが重複し、認証や同期に障害が発生してしまいます。

その為下記のレジストリを設定することでDNSレコードの意図しない変更を抑制することが必要です。

| 場所 | 値名<br>データ型 | 内容 | 補足 |
|:-:|:-:|:-:|:-:|
|`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\Parameters`|`PublishAddresses`<br>`REG_SZ`|IPアドレス|スペースを開けて複数登録できます。要求を待ち受けるインターフェースのうち、どのIPアドレスをレジストするかを示します。|
|`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters`|`RegisterDnsARecords`<br>`REG_DWORD`|`0`|多分各NICのIPアドレスのレジストを止めます。念の為に各NICの`この接続のアドレスをDNSに登録する`のチェックを外しておいて下さい。|

以上を行うことで、本作業を実施したマシンのIPアドレスが動的にDNSレコードに登録されなくなります。
設定後はNETLOGONとDNSのサービスを再起動して設定変更を反映して下さい。

## ①ロードバランスしたいDNSサーバに`ネットワーク負荷分散`をインストールする
`サーバー マネージャー`より`管理`→`役割と機能の追加`、`機能`で`ネットワーク負荷分散`にチェックを入れてインストールして下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/6f5068ad-7d08-75d1-eab8-cbc53757c66b.png)
インストールが終わったら`ツール`→`ネットワーク負荷分散マネージャー`を開く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/9bc00299-ecff-4f04-ffde-5f279278c98d.png)

## ②NLBマネージャーでクラスタを作る
`クラスター(C)`→`新規(N)`を押してNLBノードとして使用するホストを入力して登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/5c651324-de36-bcd7-93df-ec8cccbb8892.png)

特に複数NICやIPがなければそのまま`次へ(N)`を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/3a8b36d4-3aab-504a-795a-14faf0294190.png)

`クラスターIPアドレス`は実際にNLBの通信を受け入れるためのIPアドレスを指定します。
重複しなければ特に何でも良いと思います。
今回はDNSサーバをNLBで運用する予定なのでプライマリとセカンダリ用で2つ予約しておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/8e254fa9-6b44-fc33-d7ee-63f4137496f5.png)

`IPアドレス`はどれでもOKです。
`フルインターネット名`も何を付けても問題ないです。
`クラスター操作モード`は基本的に`マルチキャスト`か`IGMPマルチキャスト`のどちらかを推奨します。
違いはまだ良くわかっていません、分かり次第追記します。ユニキャストの場合はもう1つNICが要ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314743/01f4264d-ba6a-8164-4ea6-558e15d6f13f.png)

ポートの規則についてはTCP/UDPの53番を対象にすればOKです。
アフィニティについては
- なし:ランダムなホストで処理します
- 単一:同じ送信元だった場合は以前対応したホストで処理します
- ネットワーク:クラスCのセグメント単位の以前対応したホストで処理します(多分第3オクテット毎に区切ってると思われる)

といった具合です。タイムアウトについては単一またはネットワークで指定時間リクエストがなかった場合、アフィニティをリセットするまでの期間を指します。
DNSの場合はほぼ完全にステートレスなので基本的には`なし`で良いと思います。

この時点ではまだクラスタにホストは1つしかいないので、クラスタを右クリックして追加して下さい。
最大で合計32ホストまで追加できます。

:::note alert
追加が終わったらDNSレコードを確認し、間違ってNLBのIPアドレスが追加されていないかを確認して下さい。
ドメイン名/フォレスト名の前方参照ゾーンを開き、`(親フォルダーと同じ)`及びNLBを設定したドメインコントローラ、`DomainDNSZones`、`ForestDNSZones`の中にもAレコードが生成される可能性があります。
間違って追加されたまま放置すると障害が発生してしまいます！
:::
## ③DNSサービスの死活監視
ここまで正しく設定できていれば、作成したNLBのアドレスに対してnslookupをすればクラスタホストのいずれかが応答してくれるはずです。(マルチキャストのルーティング問題については言及しません)
また、ホストがシャットダウンしたり完全に応答不能に陥ってハングしたりした場合は自動で生き残ったホストだけに切り替えてくれます。

しかしこの状態では仮にホストのネットワークは切断されない状態でDNSサービスに障害が発生した場合、自動でホストがダウンしてくれるわけではありません。(DNSサービスがクラッシュした等…)

なので下記のPowerShellを常時実行すれば自動でDNS応答できなくなった場合にホストをダウンさせ、応答できるようになったら自動で復旧してくれます。
<details><summary>コードはこちら</summary>

```powershell
# 設定
$maxCount = 3                                # 最大成功・失敗回数
$dnsRequestTarget = "google.com"             # DNSリクエスト名、内部名でも可
$checkIntervalSeconds = 1                    # チェック間隔（秒）

# 変数初期化
$failureCount = 0
$successCount = 0
$currentStatus = $true
function Check-DnsRequest {
    param (
        [string]$target
    )

    try {
        $result = Resolve-DnsName -Name $target -Server 127.0.0.1 -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

while ($true) {
    $isSuccess = Check-DnsRequest -target $dnsRequestTarget 

    if ($isSuccess) {
        Write-Host "DNSリクエストは成功しました。"
        $failureCount = 0
		$successCount++
    } else {
        Write-Host "DNSリクエストは失敗しました。"
		$successCount = 0
        $failureCount++
    }

    if ($currentStatus -eq $true -and $failureCount -ge $maxCount) {
        Write-Host "ノードをオフラインにします"

        try {
            Stop-NlbClusterNode
            Write-Host "オフラインになりました。"
            $currentStatus = $false
        } catch {
            Write-Host "オフラインにできませんでした。理由: $_"
        }
    } elseif($currentStatus -eq $false -and $successCount -ge $maxCount) {
		Write-Host "ノードをオンラインにします"

		try {
			Start-NlbClusterNode
			Write-Host "オンラインになりました。"
            $currentStatus = $true
		} catch {
			Write-Host "オンラインにできませんでした。理由: $_"
		}
	}

    Start-Sleep -Seconds $checkIntervalSeconds
}

```

</details>


上記をサービス化するなりタスクスケジューラーでシステムとして常時実行すれば、DNSだけ死んだときもちゃんと落ちてくれます。