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-cAPI인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_EFFORT와Drop_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_zenoh는z_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 라는 로봇 주행 액션을 부른다고 치자.
ros2/rq/navigate_to_pose/_action/send_goalRequest-> (Zenoh Queryable): 야, 저기 좌표로 가! (수락/거부응답)ros2/rt/navigate_to_pose/_action/feedback-> (Zenoh Publisher): 지금 10% 갔어, 20% 갔어… (나를 부른 놈에게 일방적 쏘기)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) 스피드를 얻게 된 것이다.