Unboundで内部ネットワーク用DNSを構築し、PrometheusとGrafanaでDNSクエリをモニタリングしよう

# 追記
AdGuardHomeのほうが簡単そうなので、こちらの記事をおすすめします。

https://qiita.com/m4549071758/items/02aef27024b848b160a4

# はじめに
自宅内ネットワークでいろいろする際、インターネットに公開しているドメインを使ってアクセスしたい時があると思います。

しかし大半の家庭用ルーターはヘアピンNAT機能を搭載していないので、ローカルからインターネットで公開しているドメイン名と同じホスト名でアクセスするとルーターに飛ばされてしまいます。

そこで自宅ネットワーク内部にDNSキャッシュサーバーを設置して、内部/外部両方で同じホスト名を使えるようにします。

### ネットワーク構成
`192.168.1.0/24`のネットワーク上に`192.168.1.200/24`としてDNSサーバーを設置し、ルーターのデフォルトDNSとして指定します。

またUnbindの再帰問い合わせ用のDNSサーバーとして、CloudflareのパブリックDNSを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/3edf3502-5eba-e9c8-17a3-a5407f2da16d.png)

# 前提ソフトウェアインストール
## Unbound
`bind`よりも新しく、設定がシンプルなDNSキャッシュサーバーです。
権威サーバーとして動作しないので、`bind`で必要なサブドメインごとのゾーン設定が不要です。

### インストール
標準リポジトリにいるのでコマンド一発です。
```
$ sudo dnf -y install unbound
```

## unbound_exporter
`Unbound`のTLSソケットに接続してメトリクスを送信させるソフトウェアです。
Goで出来ていて、`Unbound`のメトリクス群を`Prometheus`のラベルに変換してくれます。

https://github.com/letsencrypt/unbound_exporter

### Goのインストール
```
$ wget https://storage.googleapis.com/golang/go1.21.6.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz

# PATHを通す
$ vim ./bash_profile

# 追記
PATH=$PATH:$HOME/bin:/usr/local/go/bin

$ source ~/.bash_profile
```
### 本体のインストール
```
$ go install github.com/letsencrypt/unbound_exporter@latest
$ sudo mv ./go/bin/unbound_exporter /usr/local/bin/
```

## Prometheus
リソース監視ソフトウェア。
`Unbound`からのデータを受け取り、`Grafana`のデータソースとして使用します。

https://prometheus.io/

### インストール
```
$ wget https://github.com/prometheus/prometheus/releases/download/v2.49.1/prometheus-2.49.1.linux-amd64.tar.gz
$ tar zxvf prometheus-2.49.1.linux-amd64.tar.gz
$ sudo mv ./prometheus-2.49.1.linux-amd64 /usr/local/prometheus
```

### デーモン登録
``` /usr/lib/systemd/system/prometheus.service
[Unit]
Description=Prometheus - Monitoring system and time series database
Documentation=https://prometheus.io/docs/introduction/overview/
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml

[Install]
WantedBy=multi-user.target
```
### 起動
```
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now prometheus
```
`http://SERVER_ADDR:9090`にアクセスし、`Prometheus`のWebGUIが表示されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/b5635e5b-fd45-662f-93e4-a31b193d678c.png)

## Grafana

https://grafana.com/

グラフツール。
めんどくさいのでdocker使います。

``` docker-compose.yml
services:
  grafana:
    image: grafana/grafana
    container_name: grafana
    user: "root"
    restart: always
    volumes:
      - ./grafana:/var/lib/grafana    
    depends_on:
      - influxdb
    ports:
      - 3000:3000
```
`http://SERVER_ADDR:3000`にアクセスすると次の画面が表示されます。
初期IDと初期パスワードはどちらも`admin`です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/276d5d5b-1095-33cd-f040-b916418d8597.png)

# 設定
## Unbound
### 基本設定

``` /etc/unbound/conf.d/server.conf
server:
    interface: 0.0.0.0
    interface: ::
    access-control: 127.0.0.1/32 allow # 自分自身からの問い合わせを許可
    access-control: 192.168.1.0/24 allow # ローカルからの問い合わせを許可
    access-control: fe80::/10 allow

    use-syslog: yes
    log-queries: yes

    extended-statistics: yes # 詳細メトリクス設定

    num-threads: 6 # サーバーのCPUコア数と同じにする

    rrset-cache-size: 100m
    msg-cache-size: 50m

    msg-cache-slabs: 4
    rrset-cache-slabs: 4
    infra-cache-slabs: 4
    key-cache-slabs: 4

    outgoing-range: 120 # 1024/numthreads - 50の値を入れる

    so-rcvbuf: 4m

forward-zone:
    name: "."
    forward-addr: 1.1.1.1
    forward-addr: 1.0.0.1
    forward-addr: 2606:4700:4700::1111
    forward-addr: 2606:4700:4700::1001

```

### ゾーン設定
``` /etc/unbound/conf.d/local-data.conf
# 各自環境に合わせて設定変更

server:
    local-data: "hoge.example.com.    IN A 192.168.1.100"
    local-data: "fuga.example.com.    IN A 192.168.1.101"
    local-data: "hogehoge.example.com.     IN A 192.168.1.102"
```

### テレメトリ設定
``` /etc/unbound/conf.d/remote-control.conf
remote-control:
    control-enable: yes
    control-interface: /run/unbound.ctl
```

### 再起動
`Unbound`を再起動します。
```
$ sudo systemctl restart unbound
```

## 動作テスト
LAN内の別PCから`dig`なり`nslookup`なりします。
```
$ dig fuga.example.com @192.168.1.20
; <<>> DiG 9.18.19-1~deb12u1-Debian <<>> fuga.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56481
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;fuga.example.com.                    IN      A

;; ANSWER SECTION:
fuga.example.com.             3600    IN      A       192.168.1.101

;; Query time: 0 msec
;; SERVER: 192.168.1.200#53(192.168.1.200) (UDP)
;; WHEN: Thu Feb 01 22:06:59 JST 2024
;; MSG SIZE  rcvd: 55
```

# クエリ可視化
## unbound_exporterの起動
デーモン化します。

``` /usr/lib/systemd/system/unbound_exporter.service
[Unit]
Description=Unbound_Exporter
Documentation=https://github.com/letsencrypt/unbound_exporter
After=network-online.target

[Service]
Type=simple
ExecStart=unbound_exporter -unbound.ca "" -unbound.cert "" -unbound.host "unix:///run/unbound.ctl"

[Install]
WantedBy=multi-user.target
```
```
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now unbound_exporter
```
PATHは通っていると思いますが、`command not found`の場合は`/usr/local/bin`にPATHを通してあげてください。

## Prometheusの設定
設定ファイルのメトリクスターゲットを変更します。

``` /usr/local/prometheus/prometheus.yml
# my global config
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9167"] # ポート番号を9090から9167に
```

Prometheusを再起動します。
```
$ sudo systemctl restart prometheus
```

## Grafanaで読み込み
### データソース追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/47134c99-bded-6da9-d9b3-b8e5873a1416.png)

`Home > Connections > Data sources`からPrometheusのデータソースを追加します。

`Connection`の`Prometheus Server URL`を`http://127.0.0.1:9090`に設定し、`Save & Test`をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/912b173f-4e92-8368-943b-24f87f946c5a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/2391b71d-5a6c-c403-69c7-35fa67d13218.png)

### ダッシュボードのインポート

https://grafana.com/grafana/dashboards/11705-unbound/?tab=reviews

このダッシュボードを使用します。

`Copy ID to clipboard`でIDをコピーし、`Home > Dashboards`の`New`をクリックして新しくダッシュボードを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/c61f3d3d-9cec-a58f-5fc5-8c761a4dd6a0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/b24aa7fe-3f51-965b-442f-e94dec8b6120.png)

ダッシュボードのインポート画面が開いたら、先ほど設定したPrometheusのデータソースを指定して`Import`をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/4cba83d2-0ad5-41b9-d806-dcf1669b2a86.png)

### 表示
このようなダッシュボードが表示されればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532025/d064f160-abcf-5588-0bd3-24f707b51dc6.png)


# おわり
bindと違い面倒なゾーン設定はありませんし、PowerDNSのようにリッチなGUIこそ無いもののコンフィグファイルに1行追加するだけで簡単にレコードを追加できます。