3.7 Zenoh 모니터링 인프라 구축

3.7 Zenoh 모니터링 인프라 구축

네트워크를 혈관에 비유한다면 데이터는 혈액이고, 미들웨어는 심장이다. 아무리 코드를 견고하게 작성했다고 할지라도 실제 상용 네트워크 환경(공용 Wi-Fi, 5G 셀룰러 망, 불안정한 위성 통신 등)에 배포되는 순간 패킷 드롭(Packet Drop)과 예기치 않은 지연(Latency) 현상은 피할 수 없는 숙명과 같다.

만약 수십 대의 드론 편대를 제어하는 Zenoh 네트워크에서 어느 한 구간에 병목(Bottleneck)이 발생하여 제어 신호가 500ms(밀리초) 이상 지연된다면, 그것은 단순한 소프트웨어 버그를 넘어 치명적인 물리적 충돌로 이어질 수 있다. 따라서 분산 시스템을 구축할 때 모니터링은 선택이 아니라 생존을 위한 가장 강력한 관제 시스템이다.

이 장에서는 블랙박스처럼 동작할 수 있는 Zenoh 라우터와 피어(Peer) 노드들의 내부 동작을 투명하게 들여다보고, 실시간 데이터 흐름을 계량화하여 모니터링할 수 있는 텔레메트리(Telemetry) 인프라 구축의 정석을 심도 있게 다룬다. 플러그인을 활용한 지표(Metrics) 추출부터 시계열 데이터 가공, 시스템 내부의 신경망을 훑어내는 Admin Space 활용까지, Zenoh 네트워크를 완벽하게 통제하기 위한 파이프라인을 구축해 보자.

1. 상태 모니터링을 위한 zenoh-plugin-metrics 컴파일 및 로드

Zenoh 라우터는 통신 성능을 극한으로 끌어올리기 위해 기본적으로 부가적인 모니터링 기능을 비활성화한 채 배포된다. 라우터가 초당 수백만 개의 패킷을 어떻게 처리하고 있는지, 메모리 단편화(Fragmentation)는 없는지, 어느 세션(Session)에서 백프레셔(Backpressure)가 발생하는지 파악하려면 **zenoh-plugin-metrics**라는 전용 플러그인을 라우터의 심장부에 로드(Load)해야 한다.

1.1 Rust 툴체인 및 빌드 도구 준비

Ubuntu 기반 리눅스 시스템에서 소스 빌드를 위해 앞서 3.3절에서 구성한 Rust 환경과 cmake 도구가 필요하다.

## C++ 빌드 필수 패키지 설치
sudo apt update
sudo apt install build-essential cmake

## Cargo 패키지 매니저로 플러그인 바이너리 소스 컴파일
cargo install zenoh-plugin-metrics

명령어가 성공적으로 실행되면 ~/.cargo/bin 디렉터리에 zenoh-plugin-metrics 동적 라이브러리(리눅스의 경우 .so, macOS는 .dylib) 파일이 생성된다.

1.2 라우터에 플러그인 로드 및 설정 파일 구성

단순히 빌드만 해서는 라우터가 플러그인을 인식할 수 없다. 라우터 실행 시 해당 플러그인의 경로를 파라미터로 명시하거나, JSON5/YAML 포맷의 환경 설정 파일 안에 플러그인 활성화를 지정해 주어야 한다.

명령행 파라미터(CLI)를 이용한 즉석 플러그인 로드 예시:

## 기본 라우터를 띄우면서 metrics 플러그인을 강제 체결
zenohd --plugin metrics

보다 체계적인 인프라 관리를 위해서라면, 라우터 루트 설정 파일인 zenoh.json5 파일 내의 plugins 배열에 명시하는 방식을 권장한다.

설정 파일(zenoh.json5)을 통한 선언적 플러그인 로드:

{
  plugins: {
    metrics: { // 상태 수집 플러그인 활성화 블록
      enabled: true,
      // 메트릭 데이터를 외부로 노출할 HTTP 포트 설정 (예: 8000)
      server_config: {
        bind: "0.0.0.0:8000" 
      }
    }
  }
}

이 설정 파일을 적용하여 라우터를 다시 시작하면, 플러그인이 로드되어 라우터 내부의 I/O 패킷량, 연결된 노드 수, 메모리 사용량 등의 내부 지표를 실시간으로 집계하기 시작한다.

1.3 플러그인 정상 구동 테스트 (단순 cURL 호출)

플러그인이 정상적으로 숨을 쉬고 있는지 검증해 보자. 다른 창을 열어, 앞서 포트를 개방한 8000번 포트로 HTTP GET 요청을 가볍게 날려본다.

curl http://localhost:8000/metrics

성공적으로 구축되었다면 아래와 같은 **프로메테우스 리포팅 포맷(Prometheus Exporting Format)**의 순수 텍스트(Plain Text) 데이터가 폭포수처럼 쏟아져 내릴 것이다.

## HELP zenoh_router_rx_bytes_total Total bytes received by the router
## TYPE zenoh_router_rx_bytes_total counter
zenoh_router_rx_bytes_total 1234567

## HELP zenoh_router_sessions_active Number of active sessions
## TYPE zenoh_router_sessions_active gauge
zenoh_router_sessions_active 4
...

이제 블랙박스였던 라우터를 측정 가능한 유리 상자로 탈바꿈시켰다. 이 조잡한 원시 시계열(Time-series) 데이터 더미들을 화려하게 시각화하기 위해, 다음 절에서 업계 표준인 프로메테우스(Prometheus) 서버를 호출하여 결합해 볼 명분과 징검다리가 마련되었다.

2. 시계열 데이터 수집을 위한 Prometheus 서버 연동 설정

앞선 3.7.1절에서 zenoh-plugin-metrics를 로드하여 라우터의 텔레메트리(Telemetry) 데이터를 HTTP /metrics 엔드포인트로 노출하는 데 성공했다. 하지만 이 원시 데이터는 실시간으로 화면에 텍스트 형태로 흘러갈 뿐, 1시간 전이나 어제 발생했던 트래픽 폭증(Spike)의 원인을 분석하는 데는 아무런 쓸모가 없다.

과거의 상태를 저장하고 지표의 변화 잉여분을 수학적으로 계산(Rate, Derivative 등)하기 위해서는, 이 데이터를 주기적으로 긁어모아(Scraping) 고성능 데이터베이스에 적재하는 중추 신경계가 필요하다. 이 역할을 완벽하게 수행하는 클라우드 네이티브의 사실상 표준(De-facto Standard)이 바로 **Prometheus(프로메테우스)**이다.

이 절에서는 노출된 Zenoh 메트릭을 Prometheus 서버와 연동하여 영구적인 시계열(Time-series) 데이터베이스로 구축하는 과정을 설명한다.

2.1 Prometheus 서버 설치

가장 깔끔하고 운영체제 의존성이 적은 배포 방식인 Docker 분리(Isolation) 기법을 사용하여 Prometheus를 구동하자.

## Prometheus 설정 및 데이터를 저장할 로컬 디렉터리 준비
mkdir -p ~/zenoh_monitor/prometheus
cd ~/zenoh_monitor/prometheus

## 기본 설정 파일을 담을 빈 파일 생성
touch prometheus.yml

2.2 Scraping(스크래핑) 파이프라인 설정

가장 중요한 단계이다. 생성이 완료된 prometheus.yml 파일을 열고, Prometheus 서버에게 “어디로 찾아가서(Target), 얼마의 간격으로(Interval) Zenoh 데이터를 긁어올지“를 명확하게 지시해야 한다.

## prometheus.yml 내용 구성
global:
  scrape_interval: 5s     # 5초 간격으로 매우 조밀하게 Zenoh 네트워크 생체 신호 수집
  evaluation_interval: 5s # 알럿(Alert) 룰 평가 간격

scrape_configs:
  # 스크랩할 작업 단위의 고유 이름
  - job_name: 'zenoh_router_main'
    
    static_configs:
      # 앞서 zenoh.json5에서 개방했던 라우터의 IP와 포트를 명시
      - targets: ['192.168.1.100:8000']
        labels:
          environment: 'production'
          node_type: 'backbone_router'

(주의: targets의 IP 주소는 실제 라우터가 구동 중인 호스트 머신의 IP로 치환해야 한다. Docker 내부 네트워크 격리에 주의하라.)

2.3 Prometheus 서버 컨테이너 구동 및 검증

작성한 백엔드 설정 파일을 마운트(Mount)하여 최신 버전의 Prometheus 서버를 데몬(Daemon)으로 띄운다.

docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

구동이 완료되면, 웹 브라우저를 열고 http://localhost:9090 (Prometheus 자체 Web UI)으로 접속한다.
상단 메뉴에서 Status -> Targets 탭으로 이동했을 때, 우리가 설정한 zenoh_router_main의 State 칸이 영롱한 파란색 글씨로 UP이라고 표시되어 있다면 연동은 백퍼센트 성공한 것이다. (만약 DOWN이 뜬다면, 방화벽 문제거나 targets IP 매핑이 잘못된 것이다.)

이제 라우터 내부의 I/O 처리량부터 세션 유지 상태까지, 모든 텔레메트리 데이터가 5초마다 Prometheus 데이터베이스 안에 차곡차곡 쌓이고 있다. 다음 단계에서는 이 방대한 데이터 더미 속에서 의미 있는 통찰력을 얻기 위한 분석 도구를 결합할 차례이다.

3. 네트워크 레이턴시 및 패킷 드롭률 측정 도구 구성

로보틱스나 미션 크리티컬(Mission Critical) IoT 환경에서 대역폭(Bandwidth) 부족 이상으로 시스템을 치명적인 상태로 몰고 가는 주범은 파도처럼 요동치는 **네트워크 레이턴시(Latency, 지연 시간)**와 흔적도 없이 사라지는 **패킷 드롭(Packet Drop)**이다.

Prometheus를 통해 수집하는 라우터의 I/O 통계만으로는 종단 간(End-to-End) 체감 지연 속도를 파악할 수 없다. 이를 위해 Zenoh 커뮤니티에서 제공하는 공식 성능 측정 도구인 정밀 핑(Ping) 유틸리티를 활용하여 분산 네트워크의 진짜 맨얼굴을 진단하는 방법을 서술한다.

3.1 정밀 네트워크 진단 도구 빌드

공식 Zenoh 핵심 리포지터리(zenoh-cliexamples) 내에는 레이턴시와 드롭률을 수학적으로 검증할 수 있는 통계형 테스트 코드가 포함되어 있다. 이를 Rust 환경에서 타설(Build)하여 즉시 실행 가능한 바이너리로 뽑아내자.

## Zenoh 공식 소스코드 배포판 획득
git clone https://github.com/eclipse-zenoh/zenoh.git
cd zenoh

## 최적화를 극대화한(Release) 핑(Ping) 유틸리티 컴파일
cargo build --release --example z_ping
cargo build --release --example z_pong

컴파일이 끝나면 target/release/examples 하위에 두 개의 검증용 바이너리가 준비된다.

3.2 양방향 핑퐁(Ping-Pong) 교차 검증

네트워크 레이턴시 측정의 대원칙은 반드시 목적지(Target) 노드에서 패킷을 반사해 주는 수동적 엔티티(Pong)가 선행 구동되어야 한다는 점이다.

1단계: 대상 노드(수신단) 설정
로봇 본체나 외부 클라우드 인스턴스에 접속하여 수신 대기 상태를 만든다.

## /test/network/ping 토픽으로 들어오는 패킷을 즉시 반사
./target/release/examples/z_pong -k /test/network/ping

2단계: 관제 노드(송신단)에서의 포격 및 측정
이제 나의 로컬 환경에서 로봇을 향해 일정한 주기로 핑(Ping) 패킷을 퍼붓기 시작한다. 기본적으로 1초 단위로 핑을 발사하지만, 드론의 텔레메트리 환경을 모사하고 싶다면 송신 간격을 극단적으로 조일 수 있다.

## 100바이트 짜리 페이로드를 초당 10번(100ms 간격)씩 발사하여 정밀도 향상
./target/release/examples/z_ping -k /test/network/ping -s 100

3.3 진단 결과 분석 및 병목 대응

송신단 터미널에는 ICMP 핑과 유사하지만, Zenoh의 미들웨어 계층(프레이밍, 라우팅, 스케줄링) 오버헤드까지 모두 포함된 진짜 애플리케이션 레벨의 통계가 출력된다.

64 bytes from N/A: seq=1 time=2.45 ms
64 bytes from N/A: seq=2 time=3.10 ms
64 bytes from N/A: seq=3 time=124.50 ms ⚠️ (지연 스파이크 발생)
...
--- /test/network/ping ping statistics ---
100 packets transmitted, 98 received, 2% packet loss, time 9980ms
rtt min/avg/max/mdev = 1.82/6.45/124.50/8.20 ms

분석 가이드라인:

  1. Packet Loss(패킷 드롭률): 0.1%라도 발생한다면 무선 간섭(Wi-Fi 채널 충돌)이나 라우터 혼잡 제어(Congestion) 큐가 꽉 찬 상태이다. Zenoh의 릴라이어블(Reliable) 모드 설정 여부를 점검해야 한다.
  2. mdev (표준 편차, Jitter): 평균값(avg)은 낮더라도 mdev 값이 비정상적으로 높다면(예: > 30ms), 네트워크 라우팅이 불안정하게 요동치고 있음을 의미한다. 자율주행에서는 패킷 드롭보다 이 예측 불가능한 지터(Jitter)가 더 큰 위험 요소이다.

이러한 레이턴시 데이터는 단기간의 검증용으로 훌륭하다. 향후 상용 환경에서는 이 데이터를 별도의 커스텀 스크립트로 파싱(Parsing)하여 다시 Prometheus 스크래핑 포맷으로 변환해 적재하면, 지연율의 변화 추이를 장기 트렌드 그래프로 관찰할 수 있게 된다.

4. Admin Space 기반의 런타임 리소스 상태 모니터링

Prometheus를 활용한 메트릭 스크래핑이 서버 사이드의 무거운 중앙 집중형 모니터링 방식이라면, Admin Space(@) 메커니즘은 Zenoh 네트워크에 참여한 그 어떤 가벼운 클라이언트(Client) 노드라도 네트워크 전체의 심부름꾼이 되어 토폴로지와 노드 상태를 즉각적으로 꿰뚫어 볼 수 있게 해주는 분산형 런타임 진단 기능이다.

이 절에서는 로보틱스 애플리케이션 안에서 별도의 모니터링 인프라 없이, 순수 Zenoh API만을 호출하여 시스템의 전반적인 상태를 동적으로 추적하는 기법을 구현한다.

4.1 예약된 키 경로: Admin Space(@)의 이해

Zenoh의 모든 라우터와 피어는 구동되는 순간 자신만의 고유한 UUID(예: eb42a...)를 발급받으며, 시스템 상태와 관련된 모든 민감성 정보들을 예약된 특별한 키 접두사(Prefix)인 @ 하위에 계층적으로 관리한다. 이를 Admin Space라고 부른다.

## Admin Space의 기본 계층 구조 예시
@/router/eb42a.../config    # 해당 라우터의 현재 런타임 설정값
@/router/eb42a.../sessions  # 현재 연결된 모든 클라이언트 세션 목록
@/router/eb42a.../link      # 송수신 네트워크 링크별 바이트/패킷 통계

모든 관리는 이 @ 공간을 향해 일반적인 Zenoh session.get() 쿼리를 날리는 것에서 출발한다.

4.2 CLI 툴을 활용한 네트워크 상태 스캔

코드 레벨의 구현에 앞서, Zenoh CLI 도구를 이용해 내가 속한 네트워크망에 어떤 라우터들이 살아 숨 쉬고 있는지 스캔해 보자.

## 네트워크 상의 모든 라우터 식별자(UUID)를 쿼리
zenoh get "@/*"

## 특정 라우터(예: uuid가 1234인 라우터)에 연결된 모든 세션 상세 정보 쿼리
zenoh get "@/router/1234/sessions/*"

출력된 JSON 페이로드를 살펴보면, 연결된 엔드포인트의 IP 주소는 물론이고, 각 세션이 Dropping 정책을 쓰고 있는지, 대역폭 한계(Bandwidth Limit)는 얼마로 설정되어 있는지 등 런타임에 동적으로 변하는 파라미터들이 적나라하게 드러난다.

4.3 프로그램 코드 기반의 동적 상태 모니터 노드 구현

자율주행 시스템 관제 대시보드를 만든다고 가정하고, Rust 코드를 이용해 Admin Space에서 현재 네트워크 토폴로지를 스냅샷 형태로 떠오는 프로그램을 작성해 보자.

use zenoh::prelude::r#async::*;

#[tokio::main]
async fn main() {
    // 1. 관제 모니터링을 위한 Zenoh 세션 수립
    let session = zenoh::open(config::default()).res().await.unwrap();

    // 2. Admin Space의 모든 라우터와 피어에 대한 글로벌 쿼리 발송
    // 셀렉터: @/* 
    let replies = session.get("@/*").res().await.unwrap();

    println!("--- 런타임 노드 토폴로지 스캔 ---");
    // 3. 비동기 스트림에서 쿼리 응답(각 라우터의 상태 JSON)을 수신
    while let Ok(reply) = replies.recv_async().await {
        matches!(reply.sample {
            Ok(sample) => {
                let key = sample.key_expr.as_str();
                let payload = String::from_utf8_lossy(&sample.payload.contiguous());
                
                // 예: @/router/eb42a -> {"version": "0.10.1", ...}
                println!("노드 감지: {}\n상태 데이터: {}\n", key, payload);
            },
            Err(e) => eprintln!("쿼리 조회 실패: {}", e),
        })
    }
}

이 코드가 실행되면, 관제 시스템은 별도로 플러그인을 깐다거나 방대한 로그 파일을 뒤질 필요 없이, 함수 호출 한 번만으로 전체 네트워크의 뼈대를 즉시 파악할 수 있다.

Admin Space는 단순한 조회가 아니라 런타임에 라우터의 설정을 즉시 변경(Put)할 수 있는 막강한 기능(예: 끊어진 링크 강제 재연결 시도 등)까지 내포하고 있으므로, 향후 15장의 ‘보안 및 권한 제어’ 단원에서 이 공간에 대한 읽기/쓰기 권한(ACL)을 철저하게 방어하는 전략과 반드시 맞물려 설계되어야 한다.