11.4 차세대 통신 계층: rmw_zenoh 심층 분석

11.4 차세대 통신 계층: rmw_zenoh 심층 분석

브릿지(Bridge)는 임시방편일 뿐이다. 진정한 의미의 “차세대 로봇 아키텍처” 를 논하려면 ROS2 의 심장인 rmw_zenoh 를 메인 엔진으로 채택해야 한다.

Open Robotics (ROS2 코어 개발팀)와 Eclipse 재단이 직접 합작하여 만든 이 RMW(ROS Middleware)는, 기존 C++ 코드를 단 1자도 고치지 않고 시스템의 통신 메커니즘을 DDS 에서 순수 Zenoh 로 100% 치환해 버리는 마법 같은 브레인 스왑(Brain-swap) 기술이다.
이 챕터에서는 rmw_zenoh 빌드부터 시작하여, 어떻게 Publisher 가 Zenoh 패킷으로 변이되는지 그 하부 구조를 해부하는 하드코어 런북을 전개한다.

1. rmw_zenoh 프로젝트의 내부 아키텍처 이해

ROS2 는 자신만의 통신 계층인 RMW (ROS Middleware) 라는 표준 인터페이스 규격을 갖추고 있다. rmw_fastrtps_cpp, rmw_cyclonedds_cpp 가 이 자리를 꿰차고 있었듯, rmw_zenoh_cpp 도 동일한 인터페이스(C API)를 구현한 플러그인 엔진이다.

1.0.1 [인스펙션] RMW -> Zenoh 하향 번역 구조

  • RMW API: rclcpp (당신의 C++ 코드) 에서 Publisher 를 생성하면, 내부적으로 rmw_create_publisher() 라는 표준 가상 함수가 호출된다.
  • RMW_Zenoh 레이어 (브릿징): rmw_zenoh 코어는 이 요청을 낚아챈 뒤, ROS2 의 Topic 이름(/cmd_vel)을 알아듣고 내부적으로 Rust 로 짜여진 zenoh-c API인 z_declare_publisher() 를 호출해 버린다.
  • Zenoh Core 레이어: 최종적으로 데이터는 DDS 엔진이 아니라, TCP/UDP 기반의 Zenoh 비동기 소켓망으로 발사된다.

가장 충격적인 차이점:
기존 DDS 구조에서는 프로세스(Node)마다 통신 스레드와 거대한 셰어드 메모리 데몬(Daemon)이 떴다.
하지만 rmw_zenoh 환경에서는 로봇 내부에 오직 단 1대의 zenoh-router 만을 띄워놓고, 각 ROS2 노드들이 이 라우터에 Client(포트 7447) 로 붙는 구조를 강제한다. 즉, “로컬 공유기(Local Router)” 스파이더 웹 패턴을 로봇 운영체제 자체에 내재화시켜 버린 것이다.

2. rmw_zenoh 소스코드 빌드 및 ROS2 워크스페이스 연동

(ROS2 Jazzy 버전 이상부터는 apt-get 으로 설치 가능하지만, 구버전인 Humble 이나 특수 임베디드 보드에서는 소스 빌드가 필수다.)

2.0.1 [Runbook] 심장 적출 및 RMW_ZENOH 컴파일 전술

rmw_zenoh 는 C++ 저장소이면서 동시에 Rust 라이브러리(zenoh-c)를 심하게 참조한다. colcon 빌드 환경에 Rust 툴체인을 인젝션해야 한다.

1. 워크스페이스 준비 및 클론

mkdir -p ~/ros2_zenoh_ws/src
cd ~/ros2_zenoh_ws/src
## Humble 분기점 등 자신의 버전에 맞춰 Clone!
git clone https://github.com/ros2/rmw_zenoh.git -b humble

2. 의존성 폭격 (rosdep)

cd ~/ros2_zenoh_ws
## Cargo(Rust) 와 기본 빌드 의존성을 일괄 설치
rosdep install --from-paths src --ignore-src --rosdistro humble -y

3. 대규모 컴파일 격발
가장 위험한 순간이다. colcon 이 내부적으로 Cargo built 를 호출하여 zenoh-c 를 데리고 오느라 수 분의 CPU Full load 가 걸린다.

## -Ccmake-args 설정으로 Release 풀 최적화를 돌려라.
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release

성공적으로 빌드가 멈췄다면, 당신의 워크스페이스 install 폴더 안에는 librmw_zenoh_cpp.so 라는, DDS 를 대체할 파괴적인 공유 라이브러리가 탄생해 있을 것이다.

3. RMW_IMPLEMENTATION 환경 변수 설정과 동작 검증

만들어진 칼을 장착할 시간이다. ROS2 는 환경 변수 단 하나로 자신의 심장을 바꿔 끼우는(Hot-swapping) 미친 기능(Dynamic Loading)을 지원한다.

3.0.1 [Runbook] 엔진 핫스왑(Hot-swap) 전술

1. 새로운 심장 인식시키기
방금 빌드한 워크스페이스를 로드하여 터미널 환경에 rmw_zenoh 의 존재를 알린다.

source /opt/ros/humble/setup.bash
source ~/ros2_zenoh_ws/install/setup.bash

2. 환경 변수 강제 주입 (The Swap)
이 선언 이후 띄워지는 이 터미널 안의 모든 ROS2 노드들은 DDS 를 버린다.

export RMW_IMPLEMENTATION=rmw_zenoh_cpp

3. 로컬 Zenoh 라우터 기동 (필수 사항!!!)
rmw_zenoh 는 켜지자마자 자신을 받아줄 로컬 라우터(localhost:7447)를 필사적으로 찾는다. 다른 창을 열고 zenohd 를 띄워놔야 한다.

## 백그라운드나 다른 셸에서 기동
zenohd

4. 톡커(Talker) 대 리스너(Listener) 통신 개통 검증
두 개의 셸에서 RMW_IMPLEMENTATION=rmw_zenoh_cpp 를 주고 공식 예제를 띄운다.

  • 셸 A: ros2 run demo_nodes_cpp talker
  • 셸 B: ros2 run demo_nodes_cpp listener

Hello World 가 무사히 찍힌다면, 당신은 역사상 처음으로 DDS 멀티캐스트를 쓰지 않고 순수 Zenoh TCP/UDP 프로토콜망 위에서 로봇 통신을 성공시킨 것이다. 만약 이 두 컴퓨터망이 AWS VPC 로 뚫려있다면, local 이 아닌 지구 반대편의 노드와도 Hello World 가 핑(Ping) 수준으로 터질 것이다.

4. ROS2 Publisher-Subscriber 모델과 Zenoh Pub-Sub의 매핑 원리

ROS2 시스템 프로그래머라면, 대체 내 rclcpp::Publisher 코드가 저 밑단의 Zenoh 에서 무슨 기괴한 문자열로 바뀌는지 알아야 디버깅(wireshark 패킷 확인)이 가능하다.

4.0.1 [인스펙션] 토픽(Topic) 규격의 번역 공식

ROS2 의 네임스페이스와 토픽 이름은 rmw_zenoh 를 거치면 Zenoh 의 Key Expression URI 스킴으로 1:1 강제 매핑된다.

1. 기본 Topic 매핑 규칙
만약 ROS2 에서 /robot1/sensors/lidar 라는 토픽을 발행(Publish)하면, Zenoh 망에는 다음과 같은 Key 로 던져진다.
ros2/rt/robot1/sensors/lidar

여기서 rt 는 ROS Topic 의 약자다. 즉, 모든 ROS2 데이터 앞에는 ros2/rt/ 접두사가 훈장처럼 붙어서 Zenoh 망을 부유하게 된다.

2. Serialization (직렬화 가로채기)
원래 DDS 는 CDR (Common Data Representation)이라는 악명 높은 빅엔디안/리틀엔디안 이진 변환 포맷을 쓴다.
rmw_zenoh 는 영리하게도 이 CDR 스펙을 그대로 가져와 쓴다! 즉 바이트 덩어리 모양은 DDS 시절과 통계적으로 완전히 똑같은데, 배달부 탑차만 Zenoh 트럭으로 바꾼 것이다.

3. QoS 의 번역 (BEST_EFFORT vs RELIABILITY)

  • rclcpp::QoS(10).best_effort() -> Zenoh 의 Z_RELIABILITY_BEST_EFFORTDrop_oldest (혼용 버퍼) 구조로 번역된다.
  • 이 완벽한 매핑 덕분에, 기존 ROS2 C++ 개발자는 패킷이 유실되는지 보관되는지 전혀 신경 안 쓰고 그냥 publish() 만 치면 끝난다.

5. ROS2 Service-Client 모델과 Zenoh Queryable-Query의 매핑 원리

로봇에게 “이 경로를 계산해 줘” 라고 부탁(Request)하고 응답(Response)을 기다리는 동기식 RPC 통신인 ‘Service’.
Zenoh 책을 주의 깊게 읽었다면, 이 구조가 Zenoh 의 Get()Queryable 구조 와 영혼까지 일치한다는 것을 깨달았을 것이다.

5.0.1 [인스펙션] RPC 매핑과 블로킹(Blocking) 브릿징 전술

1. Service 토픽의 분할 구조
ROS2 에서 /calculate_path 라는 서비스를 만들면, rmw_zenoh 는 밑단의 Zenoh 에게 다음과 같이 변조된 키를 2개 부여한다.

  • 요청(Request)을 받는 서버의 Key: ros2/rq/calculate_pathRequest
  • 대답(Reply)을 돌려주는 Key: ros2/rr/calculate_pathReply (rq: Request / rr: Reply 의 약자)

2. Client 의 Get 호출 작동 원리

  • ROS2 노드 A가 클라이언트로서 이 서비스를 부르면,
  • rmw_zenohz_get("ros2/rq/calculate_path", Request데이터) 를 쏘고 스레드를 블로킹(Blocking) 상태로 멈춘 채 기다린다.
  • 저 멀리 있는 ROS2 서비스 서버 (노드 B) 는 Queryable 로 대기하고 있다가, 이 질문 파티클을 낚아채 정답을 계산한 후(Callback 실행)
  • z_reply() 로 응답 데이터를 역류시킨다.

이 완벽한 1:1 대칭(Symmetry) 구조 덕분에, ROS2 의 Service 는 그 기반이 DDS 에서 Zenoh 로 바뀌었는지 스스로 절대 눈치채지 못한다.

6. ROS2 Action 통신(Goal, Result, Feedback)의 Zenoh 처리 구조

ROS2 의 액션(Action)은 단일 RPC(서비스) 가 아니라, “목표(Goal) 전송 -> 무한 피드백(Feedback) -> 최종 결과(Result)” 로 이어지는 정신병적인 3-way 통신이다.

6.0.1 [인스펙션] 액션 지옥(Action Hell)의 멀티채널 매핑

DDS 에서는 이 액션을 구현하기 위해 무려 내부적으로 5~7개의 잡동사니 토픽과 서비스를 겹겹이 쳐버린다.
rmw_zenoh 는 이 끔찍한 구조를 완벽하게 분해해서 5개의 서로 다른 Zenoh API 로 찢어서 매핑(Mapping)한다.

만약 /navigate_to_pose 라는 로봇 주행 액션을 부른다고 치자.

  1. ros2/rq/navigate_to_pose/_action/send_goalRequest -> (Zenoh Queryable): 야, 저기 좌표로 가! (수락/거부응답)
  2. ros2/rt/navigate_to_pose/_action/feedback -> (Zenoh Publisher): 지금 10% 갔어, 20% 갔어… (나를 부른 놈에게 일방적 쏘기)
  3. ros2/rq/navigate_to_pose/_action/get_resultRequest -> (Zenoh Queryable): 최종 결과 알려줘! (성공/충돌보고)

이처럼 ROS2 액션의 복잡성을 순수하게 하단의 Zenoh Pub/Sub 와 Queryable 객체 5개로 잘라서(Sharding) 관리하기 때문에, 통신이 잠시 끊기더라도 로봇 주행(Action Server) 코어는 자신의 위치를 계속 Zenoh 네트워크망 허공에 던지며 질주할 수 있는 독립성을 부여받게 되었다.

7. 노드 디스커버리(Discovery) 메커니즘: DDS 멀티캐스트와 Zenoh 방식 비교

이 섹션은 왜 구글 구글 딥마인드와 토요타 로보틱스 연구소가 rmw_zenoh 에 열광했는지에 대한 핵심 답안지다.

7.0.1 [인스펙션] O(N^2) 폭동의 종말

[기존 DDS 의 무질서도: 멀티캐스트의 저주]
로봇 노드(Node)가 켜지면 무조건 서브넷 망 전체(239.255.0.1 등)를 향해 "나 여기 있어! 나랑 통신할 사람!" 이라는 XML 명판을 초당 수차례 씩 뿜어낸다. 노드가 100개면 서로 간에 곱연산(O(N^2)) 의 트래픽이 발생하여 스위치 장비의 CPU 가 터져버린다. (Discovery Storm).

[Zenoh 의 완벽한 통제: Single Gateway 랑데부]
rmw_zenoh 로 무장한 노드 통신에서는 이런 시끄러운 멀티캐스트 인사가 아예 전파 단위에서 사라진다.

  • 각 노드는 켜지자마자 자신에게 할당된 가까운 Zenoh 라우터 단 한 대(localhost 또는 지정 PEER) 에게 조용히 귓속말을 남긴다. "나 /cmd_vel 토픽 발행(Publish)할 권한이 생겼어."
  • 이게 끝이다!
  • 라우터(Router)는 알아서 전 세계망의 다른 라우터들과 글로벌 라우팅 테이블(Routing Table)을 동기화하여, 저 멀리 런던에 있는 클라이언트가 이 토픽을 원할 때만 점대점(P2P) TCP 소켓으로 길을 뚫어준다.

런북 지침:
rmw_zenoh 에서는 디스커버리 지연 시간(수 초~수십 초) 자체가 0 에 수렴한다. 당신이 AWS 관제 서버에서 구독(Subscription) 을 누르는 즉시 경로가 개통되는 진정한 마이크로서비스(Microservices) 스피드를 얻게 된 것이다.