クラウドMacでBonjourサービス探索を自動検証する

クラウドMacでBonjourサービス探索を自動検証する

リモート環境でアプリケーションが明示的なホスト名とポートを使って接続できても、Bonjourのサービス探索まで正常に機能しているとは限りません。印刷、画面共有、デバッグプロキシ、LAN内のコラボレーションツールは、多くの場合mDNSに依存しています。サービス種別、TXTレコード、ネットワークインターフェイスのいずれかが変わると、UI上では単に「近くのデバイスが消えた」ように見えます。この種の問題は、クラウドMac上でプロトコルレベルの回帰テストを行うのに適しています。実機で断続的な障害が起きるのを待つのではなく、一時サービスを登録し、ブラウズ、名前解決、終了時のクリーンアップまで検証します。

まずテスト境界を明確にする

Bonjourは通常、UDP 5353を使用してリンクローカルのmDNSマルチキャストを送信します。TCPポートに到達できるからといって、ルーター、通常のSSHトンネル、リモートデスクトップ接続を自動的に越えるわけではありません。そのため、クラウド上のテストは次の2層に分ける必要があります。

  1. 同一のmacOSノード上で、登録、ブラウズ、SRVレコードの解決、TXTレコードを検証する。
  2. 対象LAN内で実機との相互接続を別途テストし、スイッチ、無線ネットワーク、マルチキャストポリシーを検証する。

第1層はコミットごとのCIに組み込むのに適しており、サービス種別のタイプミス、誤ったポート、メタデータの欠落、プロセスの終了漏れを検出できます。第2層はネットワーク受け入れテストであり、第1層の結果で代替することはできません。

Bonjourテストの成功が証明するのは、現在のテスト境界内でサービス探索の契約が成立していることだけです。マルチキャストがリモートネットワークの境界を越えたことまでは証明できません。

サービス種別には、たとえば _myapp-test._tcp のような製品専用の値を割り当てることを推奨します。_http._tcp を流用すると、テストノード上の別のサービスが結果に混入します。インスタンス名も固定せず、実行中のジョブを一意に識別できる値を含めます。

dns-sdで最小限の検証ループを構築する

macOSには dns-sd が標準搭載されているため、追加の依存関係をインストールする必要はありません。以下のスクリプトは一時HTTPサービスを起動し、先にブラウズを開始してからBonjourインスタンスを登録し、最後にインスタンスを解決してTXTレコードの内容を確認します。リポジトリ内の ci/test-bonjour.sh として利用できます。

#!/bin/bash
set -euo pipefail

RUN_ID="${CI_RUN_ID:-$(date +%s)}"
INSTANCE="vmcache-${RUN_ID}"
TYPE="_myapp-test._tcp"
PORT="${BONJOUR_TEST_PORT:-8088}"
WORK_DIR="$(mktemp -d)"
BROWSE_LOG="$WORK_DIR/browse.log"
RESOLVE_LOG="$WORK_DIR/resolve.log"

HTTP_PID=""
BROWSE_PID=""
REGISTER_PID=""
RESOLVE_PID=""

cleanup() {
  for pid in "$RESOLVE_PID" "$REGISTER_PID" "$BROWSE_PID" "$HTTP_PID"; do
    if [[ -n "$pid" ]]; then
      kill "$pid" 2>/dev/null || true
      wait "$pid" 2>/dev/null || true
    fi
  done
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM

python3 -m http.server "$PORT" --bind 0.0.0.0 \
  >"$WORK_DIR/http.log" 2>&1 &
HTTP_PID=$!

dns-sd -B "$TYPE" local. >"$BROWSE_LOG" 2>&1 &
BROWSE_PID=$!

dns-sd -R "$INSTANCE" "$TYPE" local. "$PORT" \
  "path=/" "run=$RUN_ID" >"$WORK_DIR/register.log" 2>&1 &
REGISTER_PID=$!

sleep 4
grep -F "$INSTANCE" "$BROWSE_LOG"

dns-sd -L "$INSTANCE" "$TYPE" local. >"$RESOLVE_LOG" 2>&1 &
RESOLVE_PID=$!
sleep 3
kill "$RESOLVE_PID" 2>/dev/null || true
wait "$RESOLVE_PID" 2>/dev/null || true
RESOLVE_PID=""

grep -E "path=/|run=$RUN_ID" "$RESOLVE_LOG"
grep -E "$PORT" "$RESOLVE_LOG"
curl --fail --silent --show-error "http://127.0.0.1:$PORT/" >/dev/null

ブラウズは必ず登録より先に開始する必要があります。そうしないと、短時間で終了するジョブでは追加イベントを取りこぼす可能性があります。ここでは dns-sd をフォアグラウンドで実行していません。ブラウズと名前解決のコマンドは待機し続けるため、CI側で監視時間を明示的に設定し、プロセスを終了させる必要があります。

アサーションをサービス契約に合わせる

インスタンス名が出現したことを確認するだけでは不十分です。安定した回帰テストでは、少なくともサービス種別、インスタンス名、ポート、TXTレコードの4項目を検証する必要があります。TXTレコードには、プロトコルバージョン、機能フラグ、ヘルスチェック用パスなど、探索時に本当に必要となる機密性の低いメタデータを格納します。トークン、パスワード、内部認証情報を含めてはいけません。

プロトコルバージョンは api=2、機能は features=sync,preview として記録することを推奨します。テスト側では出力行全体を比較せず、各項目を個別にアサートします。dns-sd の出力には時刻やインターフェイス情報が含まれるため、行全体のスナップショットはOSのバージョンによって変化しやすくなります。

アプリケーションが古いプロトコルを拒否する仕様なら、さらに api=1 のインスタンスを登録し、クライアントが実際にそれを無視することを検証できます。これにより、単に「名前が見える」ことだけでなく、選択ロジックまでテストできます。名前解決に成功した後は、実際のポートへ最小限のリクエストも送信し、「アドバタイズでは8088を指定しているのに、サービスは8089で待ち受けている」といった偽陽性を防ぐ必要があります。

並列実行、残留プロセス、公開範囲を管理する

同一ノード上で複数のジョブを並列実行する場合、インスタンス名とポートの両方を分離する必要があります。インスタンス名にジョブ番号を追加すれば探索結果の混同は防げますが、2つのHTTPサービスが同じポートを取り合う問題は解決できません。CIスケジューラーであらかじめポートを割り当てるか、起動前に lsof -nP -iTCP:$PORT -sTCP:LISTEN を実行して使用状況を確認します。

すべてのバックグラウンドプロセスを trap の対象にする必要があります。アサーションの失敗後も登録プロセスが動き続けると、次回のテストで古いインスタンスが検出され、再現しにくい偽陽性が発生する可能性があります。クリーンアップでは、最初に名前解決と登録を停止し、その後にブラウズプロセスとテストサービスを停止します。

この例ではエンドツーエンドの検証を行うため、すべてのインターフェイスで待ち受けています。専有クラウドMacでも、公開範囲を最小限にする原則に従う必要があります。テストポートは管理されたネットワーク内で短時間だけ開き、ネットワークリクエストが不要ならHTTPサービスを省略し、登録と名前解決だけを検証できます。実行前後にそれぞれ lsof を実行し、ポートを待ち受けるプロセスが残っていないことも確認できます。

失敗時の出力から原因を特定する

「ブラウズできない」場合は、まず先頭のアンダースコアと _tcp サフィックスを含め、サービス種別が完全に一致しているかを確認します。次に、登録プロセスが途中で終了していないかを確認します。ブラウズはできても名前解決できない場合は、インスタンス名のエスケープ、ドメイン引数、ローカルホスト名の解決を重点的に調べます。名前解決できてもポートへ接続できない場合は、待ち受けアドレス、ポートの競合、ファイアウォールポリシー、サービスの起動順序を確認します。

CIでは browse.logresolve.log、登録プロセスの出力を保存する必要がありますが、アップロード前に機密情報を除去してください。失敗時には scutil --get LocalHostNamenetworksetup -listallhardwareports、該当ポートに対する lsof の結果も記録すると、通常は命名、インターフェイス、待ち受けのどこに問題があるかを切り分けられます。

VMCacheの専有物理ノードでこの種のテストを実行する場合も、リモート接続経路がmDNSを転送すると仮定してはいけません。プロトコルレベルの回帰テストはノード内部で完結させ、実機の探索は対象LANでの受け入れテストとして実施します。2種類の結果を別々に記録することで、障害の境界が明確になり、再現性も確保できます。

よくある質問

SSHトンネルでBonjour探索も自動的に転送されますか?

いいえ。Bonjourは通常、リンクローカルのUDP 5353 mDNSマルチキャストを使います。一般的なSSHポート転送では、このマルチキャスト探索を別ネットワークへ自動転送できません。

並列CIでBonjourサービス名の衝突を防ぐにはどうしますか?

ジョブごとに固有のインスタンス名と別ポートを割り当てます。終了トラップを設定し、失敗時にも登録、検索、解決、テストサービスの全プロセスを停止してください。

専用物理ノード

構築・テスト・MLX推論にクラウドMacを選ぶ

実際のタスクに合わせて、M4、メモリ、ストレージ、ノード、契約期間を選択できます。各レンタルには専用物理マシンが割り当てられ、仮想マシンではありません。

プランを選ぶ