클라우드 Mac에서 Bonjour 서비스 검색을 자동 검증하는 방법

클라우드 Mac에서 Bonjour 서비스 검색을 자동 검증하는 방법

클라우드 Mac에서 Bonjour 서비스 검색을 자동 검증하는 방법

원격으로 작업할 때 애플리케이션이 명확한 호스트 이름과 포트로 연결된다고 해서 Bonjour 서비스 검색까지 정상이라는 뜻은 아니다. 프린트, 화면 전송, 디버깅 프록시, 로컬 네트워크 협업 도구는 mDNS에 의존하는 경우가 많다. 서비스 유형이나 TXT 레코드, 네트워크 인터페이스가 바뀌면 사용자 화면에는 대개 “주변 기기가 사라진” 것처럼만 보인다. 이런 문제는 실제 기기에서 간헐적인 오류가 발생하기를 기다리기보다 클라우드 Mac에서 프로토콜 수준의 회귀 테스트를 수행하는 편이 적합하다. 임시 서비스를 등록한 뒤 검색과 확인을 검증하고, 마지막으로 관련 프로세스가 정상적으로 정리되는지 확인하면 된다.

먼저 테스트 범위를 명확히 정의하기

Bonjour는 일반적으로 UDP 5353을 통해 링크 로컬 범위에 mDNS 멀티캐스트를 전송한다. TCP 포트에 접속할 수 있더라도 mDNS가 라우터나 일반 SSH 터널, 원격 데스크톱 연결을 자동으로 통과하는 것은 아니다. 따라서 클라우드 테스트는 다음 두 단계로 나눠야 한다.

  1. 동일한 macOS 노드에서 등록, 검색, SRV 확인, TXT 레코드를 검증한다.
  2. 대상 로컬 네트워크에서 실제 기기 간 통신을 별도로 테스트해 스위치, 무선 네트워크, 멀티캐스트 정책을 검증한다.

첫 번째 단계는 커밋마다 실행되는 CI에 넣기 적합하며, 서비스 유형 오타, 잘못된 포트, 누락된 메타데이터, 종료되지 않은 프로세스를 찾아낼 수 있다. 두 번째 단계는 네트워크 인수 테스트에 해당하므로 첫 번째 단계의 결과로 대체할 수 없다.

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 레코드의 네 가지 항목을 검증해야 한다. TXT 레코드에는 프로토콜 버전, 기능 플래그, 상태 확인 경로처럼 검색 단계에서 실제로 필요한 저민감도 메타데이터만 저장해야 한다. 토큰, 비밀번호, 내부 자격 증명을 넣어서는 안 된다.

프로토콜 버전은 api=2, 기능은 features=sync,preview로 기록하는 것이 좋다. 테스트에서는 출력 전체 행을 비교하지 말고 각 항목을 개별적으로 검증한다. dns-sd 출력에는 시간과 인터페이스 정보가 포함되므로 전체 행 스냅샷은 시스템 버전에 따라 쉽게 달라질 수 있다.

애플리케이션이 이전 프로토콜을 거부해야 한다면 api=1 인스턴스를 추가로 등록하고 클라이언트가 실제로 이를 무시하는지 검증할 수 있다. 이렇게 해야 단순히 “이름 하나를 발견했다”는 사실이 아니라 선택 로직까지 테스트할 수 있다. 확인에 성공한 뒤에는 실제 포트로 최소 요청을 한 번 보내야 한다. 그래야 “브로드캐스트에는 8088이라고 적혀 있지만 서비스는 8089에서 수신하는” 식의 잘못된 통과를 방지할 수 있다.

병렬 작업과 잔여 프로세스, 노출 범위 관리하기

동일한 노드에서 여러 작업을 병렬로 실행할 때는 인스턴스 이름과 포트를 모두 격리해야 한다. 인스턴스 이름에 작업 번호를 추가하면 검색 결과가 뒤섞이는 문제는 해결할 수 있지만, 두 HTTP 서비스가 동일한 포트를 차지하려는 충돌까지 막지는 못한다. CI 스케줄러가 포트를 미리 할당하거나 시작 전에 lsof -nP -iTCP:$PORT -sTCP:LISTEN으로 사용 여부를 확인해야 한다.

모든 백그라운드 프로세스는 trap으로 정리해야 한다. 검증 실패 후에도 등록 프로세스가 살아 있으면 다음 테스트가 이전 인스턴스를 발견해 재현하기 어려운 거짓 양성을 만들 수 있다. 정리할 때는 확인 및 등록 프로세스를 먼저 중지하고, 그다음 검색 프로세스와 테스트 서비스를 종료해야 한다.

이 예제는 엔드투엔드 검사를 위해 모든 인터페이스에서 수신한다. 전용 클라우드 Mac에서도 최소 노출 원칙을 지켜야 한다. 테스트 포트는 통제된 네트워크에서 짧은 시간 동안만 열어야 한다. 네트워크 요청이 필요하지 않다면 HTTP 서비스를 생략하고 등록과 확인만 검증할 수 있다. 실행 전후에 각각 lsof를 사용하면 포트를 계속 수신 중인 잔여 프로세스가 없는지 확인할 수 있다.

실패 출력으로 문제 진단하기

서비스가 검색되지 않으면 먼저 앞쪽 밑줄과 _tcp 접미사를 포함해 서비스 유형이 완전히 일치하는지 확인하고, 등록 프로세스가 일찍 종료되지 않았는지도 점검한다. 검색은 되지만 확인되지 않는다면 인스턴스 이름 이스케이프, 도메인 인자, 로컬 호스트 이름 확인을 중점적으로 살펴본다. 확인은 되지만 포트에 연결할 수 없다면 수신 주소, 포트 점유, 방화벽 정책, 서비스 시작 순서를 검사한다.

CI는 browse.log, resolve.log, 등록 프로세스 출력을 저장하되 업로드 전에 민감 정보를 제거해야 한다. 실패 시 scutil --get LocalHostName, networksetup -listallhardwareports, 관련 포트의 lsof 결과도 함께 기록하면 이름 문제, 인터페이스 문제, 수신 문제를 대부분 구분할 수 있다.

VMCache의 전용 물리 노드에서 이런 테스트를 실행하더라도 원격 연결 경로가 mDNS를 전달할 것이라고 가정해서는 안 된다. 프로토콜 수준의 회귀 테스트는 노드 내부에서 실행하고, 실제 기기 검색은 대상 로컬 네트워크 인수 테스트에서 별도로 검증해야 한다. 두 결과를 따로 기록해야 실패 범위를 명확히 파악하고 같은 조건에서 재현할 수 있다.

자주 묻는 질문

SSH 터널을 열면 로컬 컴퓨터에서 Bonjour 서비스가 자동으로 검색되나요?

아닙니다. Bonjour는 일반적으로 링크 로컬 UDP 5353 mDNS 멀티캐스트를 사용하며, 일반적인 SSH 포트 전달은 이 멀티캐스트 트래픽을 자동 전달하지 않습니다.

병렬 CI 작업의 Bonjour 이름 충돌은 어떻게 방지하나요?

작업마다 고유한 인스턴스 이름과 별도 포트를 사용해야 합니다. 종료 트랩으로 등록 프로세스, 검색 프로세스와 테스트 서버를 오류 발생 시에도 모두 정리해야 합니다.

독점 물리 노드

빌드, 테스트와 MLX 추론을 위한 클라우드 Mac 선택

실제 작업에 맞춰 M4, 메모리, 스토리지, 노드와 사용 기간을 선택하세요. 모든 대여에는 가상 머신이 아닌 독점 물리 장비가 제공됩니다.

대여 플랜 선택