サーバー環境

  • HP ProDesk 400 G6 DM
  • CPU: Core i5-10500T
  • メモリ: 16GB
  • ストレージ: M.2 SSD 256GB
  • OS: Ubuntu Server 26.04 LTS

このサーバーでは仮想通貨自動売買Botを常時稼働させているが、CPU・メモリともに余力があったため、家中の広告ブロック用にPi-holeを追加導入することにした。

Docker導入とポート53の競合

Ubuntu 24.04以降はデフォルトでsystemd-resolvedがローカルDNSキャッシュとして動いており、127.0.0.53/127.0.0.54をリッスンしている。Pi-holeをDockerで-p 53:53公開しようとすると、ここが競合しやすい。

対処として、/etc/systemd/resolved.confDNSStubListenerを無効化し、ホスト自体の名前解決先を/run/systemd/resolve/resolv.conf(スタブを経由しない実際のアップストリーム)に向け直した。

sudo sed -i 's/#\?DNSStubListener=.*/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo rm -f /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved

これでポート53が解放され、Pi-holeのコンテナは無事起動できた。管理画面(http://<サーバーIP>:8080/admin)にもアクセスでき、ここまでは順調だった。

ルーターのDNSを向けた瞬間、全端末で名前解決が死んだ

ルーターのDHCP設定でプライマリ/セカンダリDNSを両方ともPi-holeのIPに変更したところ、PCもスマホも検索すらできなくなった。DNSサーバーのアドレス自体はipconfig /allで正しく切り替わっているのに、何も引けない。

コンテナ内部からのテストは問題なかった。

docker exec pihole dig google.com @127.0.0.1
# → 正常にAレコードが返る

つまりPi-hole自体、そしてアップストリームDNSへの転送は正常に機能している。おかしいのは「LANの外部端末からの問い合わせ」だけだった。

原因:Pi-holeが自分のLANを認識していなかった

コンテナのログを見て原因が判明した。

WARNING: dnsmasq: ignoring query from non-local network 192.168.0.64

Pi-holeの内部(dnsmasq/FTL)には「ローカルネットワークとみなす範囲」を判定するロジックがあり、デフォルトのLOCALモードでは、コンテナ自身のネットワークインターフェース情報から自動判定する。ところがDockerのブリッジネットワーク経由で動かしていたため、Pi-holeが認識する「自分のネットワーク」はDockerの内部サブネットになってしまい、実際のLAN(192.168.0.0/24)から届く問い合わせを「よそのネットワークからの怪しい問い合わせ」として黙って無視していた。

これはPi-hole+Docker構成で比較的よく起きる既知の挙動らしい。

解決策

docker-compose.ymlの環境変数で、DNSのリスニングモードをALLに変更する。

services:
  pihole:
    image: pihole/pihole:latest
    environment:
      TZ: "Asia/Tokyo"
      FTLCONF_webserver_api_password: "${PIHOLE_PASSWORD}"
      FTLCONF_dns_listeningMode: "all"
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    cap_add:
      - NET_ADMIN

コンテナを再作成して再度LANから問い合わせると、正常に名前解決できるようになった。

docker compose up -d

教訓

  • Dockerでネットワークサービス(特にDNSのようにクライアントのソースIPを見て挙動を変えるもの)を動かす場合、ホストの物理ネットワークとコンテナのブリッジネットワークが別物であることを常に意識する
  • 「内部からは動くのに外部からだけ失敗する」ときは、まずログを見る。今回もdocker logsの一行で一発で原因が特定できた
  • ルーターのDNS設定を切り替える前に、一度LAN内の別端末(別のPCやスマホ)からnslookupで疑似的に検証してから本番切り替えするとやり直しが少なくて済む