ROS 2 Jazzy Jalisco(2024-05-30にリリースが発表された)は、DDSを基盤に「マスターなし」で分散システムを組めることが特徴である。複数台ロボットの協調では、この特性が強みになる一方、DDSの発見(Discovery)トラフィック、名前衝突、障害時の切替を設計しないと、台数が増えた瞬間に不安定化しやすい。

本稿では、ROS 2 Jazzyを前提に、(1) DDS設計、(2) 名前空間分離、(3) 障害時フェイルオーバーの3点から、マルチロボット協調アーキテクチャの実装パターンを整理する。対象は、同一拠点LANまたはVPNで接続された小規模〜中規模フリート(数台〜数十台)を想定する。

協調アーキテクチャの分解: 「ロボット内」「ロボット間」「管制」

マルチロボット協調は、通信の話に見えて、実際は境界(責務)を切る話である。最初に、システムを3層に分解しておくと設計の誤差が小さくなる。

  • ロボット内(オンボード): センサ、推定、制御、Safetyなど。ここは単体で閉じる(ネットワーク断でも走れる)ことが重要である。
  • ロボット間: 相対位置共有、衝突回避、タスクの譲り合い等。遅延・欠損を前提にし、QoSと同期戦略で品質を決める。
  • 管制(Fleet/Orchestrator): タスク割当、地図・ルール配布、監視、ログ収集。ここは冗長化しやすい形で作る。

この分解に沿って、「ロボット内は最小限の依存」「ロボット間は疎結合」「管制は交換可能」を満たすと、後述のフェイルオーバーが現実的になる。

DDS設計: ドメイン分割とDiscovery制御でスケールさせる

ROS 2はDDSを基盤に、ノード起動時に同一ドメイン上の他ノードを自動発見し、互換なQoSのエンドポイント同士で接続を確立する。台数が増えると「発見そのもの」が負荷になり、無線区間やVPN越しで顕在化しやすい。

1) ROS_DOMAIN_IDで「物理ネットワーク上の論理分割」を作る

複数チーム・複数フリートが同じL2/L3上に混在する場合、まずはROS_DOMAIN_IDで論理的に分離する。これはDDSのDomain IDに対応し、異なるDomain ID同士は発見も通信も行わない。

# 例: フリートAは 10、フリートBは 20
export ROS_DOMAIN_ID=10

実務では「現場ネットワークに乗る全ROS 2プロセスがDomain ID 0のまま」になりがちで、試験環境が本番を壊す事故が起きる。まずDomain IDを運用ルールに昇格させるべきである。

2) JazzyのDiscovery制御: 範囲制限とDiscovery Serverの活用

JazzyではDiscovery範囲を制御する環境変数(例: ROS_AUTOMATIC_DISCOVERY_RANGE)が整備されている。台数が増えるほど「誰が誰を発見するべきか」を限定し、無駄な発見を減らす価値が増す。

加えて、Fast DDS系(RMWがFast DDSの場合)ではDiscovery Serverを使い、マルチキャスト頼みの分散発見を、サーバ型に寄せて安定化できる。特に、VPN越しやマルチキャストが制限されるネットワークでは有効である。

# Discovery Server(例: 2台冗長)
fastdds discovery --server-id 0 --udp-address 10.0.0.10 --udp-port 11811
fastdds discovery --server-id 1 --udp-address 10.0.0.11 --udp-port 11888

# クライアント側(複数指定で冗長化)
export ROS_DISCOVERY_SERVER="10.0.0.10:11811;10.0.0.11:11888"

Discovery Serverは「サーバが単一障害点になるのでは」という懸念が出るが、冗長構成が公式に提示されている。現実的には、ロボット内はP2Pのままロボット間と管制に関わる通信だけDiscovery Server経由に寄せると、運用が壊れにくい。

3) QoSを「協調品質の契約」として設計する

協調制御では、QoSは単なる性能チューニングではなく「欠損・遅延が起きたときに何が正しいか」という契約である。代表的には以下で事故が起きる。

  • Reliable過信: 無線や混雑区間で再送が増え、遅延が悪化して制御が破綻する。
  • Best Effortの乱用: 重要イベント(状態遷移、タスク確定)が落ち、整合性が崩れる。
  • History/Depthの不整合: バースト時に重要メッセージが押し出され、後段が誤動作する。

センサ系はBest Effortで許容できることが多い一方、タスク割当やロック(予約)系はReliableかつイベント設計(冪等・再試行・タイムアウト)まで含めて作る必要がある。

名前空間分離: 衝突を「起こさない」のではなく「起きても隔離する」

マルチロボットで最初に踏む地雷は、トピック名やノード名の衝突である。衝突は完全には防げないため、隔離(isolation)を設計に埋め込む。

1) ロボット単位のトップレベル名前空間を固定する

各ロボットを /r1, /r2 のようなトップレベル名前空間に閉じ込め、ロボット内ノードは原則その配下で動かす。Launch側で強制すると、開発者のミスが減る。

# 実行例(単体起動でも名前空間を明示)
ros2 run my_pkg my_node --ros-args -r __ns:=/r1 -r __node:=controller

このとき、共有すべきものだけを例外としてルート名前空間に出す(例: 管制への状態報告トピック、フリート全体の指令Actionなど)。「例外は少数」が運用上の正義である。

2) TFの扱いは方針を決めて徹底する

TFは「トピック名の名前空間」と「frame_idの一意性」が混ざりやすい。実務上は、(A) ロボットごとに /<robot>/tf を持つ、(B) frame名にプレフィックスを入れて単一 /tf に集約する、の2系統がある。Nav2では前者(ロボット別TFツリー)を採る例がある。

どちらを採っても良いが、重要なのは可視化・解析・相互参照をどう成立させるかである。複数ロボットの統合可視化が必要なら、集約ブリッジ(robot별TFを収集してグローバルに再配信する)を用意し、アプリ側に曖昧さを持ち込まない。

3) パラメータ、ノード名、サービス名も衝突対象である

トピックだけ分離しても、/parameter_events やサービス名、ノード名衝突で運用が壊れることがある。命名規約(例: __node:=<robot>_<role>)と、Launchでの強制をセットで行うべきである。

協調制御の実装パターン: 「共有状態」を最小化する

協調制御で難しいのは、実はアルゴリズムではなく「共有状態の管理」である。DDS/ROS 2は通信を提供するが、整合性はアプリで担保する必要がある。

1) 中央集権(Orchestrator): まずはここから始める

最初の現実解は、管制(Orchestrator)がタスク割当を握り、ロボットは命令を受けて実行し、結果を報告する形である。ロボット間の直接協調は最小にする。理由は、障害時に「責任の所在」が明確になり、フェイルオーバー設計がしやすいからである。

実装上は、ロボットごとの状態(位置、バッテリ、故障フラグ)を集約し、タスクをActionで配布する。Actionはタイムアウトとキャンセルが扱えるため、現場運用に向く。

2) 分散協調: 共有資源だけをロックし、他は推定で動かす

ロボット間で直接協調する場合、共有資源(交差点、狭路、充電器)のような衝突点だけを明示し、ロック/予約を設ける。ロックは以下を満たす必要がある。

  • 冪等: 同じ要求が重複しても状態が壊れない。
  • 期限付き: ハートビートが止まれば自動解放される。
  • 観測可能: 誰が保持しているかを監視できる。

この部分は通信がReliableでも壊れるため、アプリ・プロトコルとして設計し、監視と運用手順まで作って初めて成立する。

障害時フェイルオーバー: ノード、管制、Discoveryを別々に冗長化する

フェイルオーバーは「全部を二重化する」話ではない。故障モードごとに、切替単位と切替条件を決める話である。

1) ノード単位: Lifecycleで「停止しても安全」を作る

ROS 2のmanaged lifecycle(LifecycleNode)を使うと、ノードを状態機械で管理し、起動・停止・再起動を手順化できる。Nav2ではLifecycle対応ノードをLifecycle managerがまとめて制御する設計が採られている。

協調制御では、障害時に「半死にで動き続けるノード」が最も危険である。Lifecycleで、(a) 入力が途絶えたら自動でDeactivate、(b) 復帰時はConfigureからやり直す、といった安全側の遷移を設計に入れるべきである。

2) 管制(Orchestrator): Active/Standbyとリーダ選出

管制は単一障害点になりやすい。基本はActive/Standbyで、状態(タスク台帳、ロック台帳、ロボットの最新ハートビート)を外部ストアに置く。切替条件は「プロセス死活」ではなく、観測した時間(例: 直近N秒のハートビート欠損)で決める。

ここで重要なのは、切替中に二重に命令が出ないことである。命令には一意IDを付け、ロボット側は「最後に適用したID」を記録して冪等に処理する。これにより、二重化の現実的な運用が可能になる。

3) Discovery: Discovery Server冗長と「復旧の速さ」

Discoveryの障害は、アプリが生きていても通信が張れず、現場から見ると「沈黙」に見える。Fast DDS Discovery Serverには冗長構成が提示され、サーバ状態の復元(SQLite/JSONバックアップ)も含めて復旧を高速化する仕組みがある。協調の基盤をDiscoveryに依存するなら、サーバ冗長復旧手順をセットで用意すべきである。

FAQ

ROS_DOMAIN_IDはロボットごとに変えるべきか?

原則は「フリート(協調させたい集合)ごと」に揃えるべきである。ロボットごとにDomain IDを変えると発見も通信もできず、協調の前提が崩れる。分離したいのは、別フリート、別チーム、別環境(検証/本番)である。

名前空間分離だけでマルチロボットは成立するか?

成立はするが十分ではない。名前空間分離は衝突を避ける手段であり、スケールのボトルネック(Discovery負荷、QoS不整合、障害時切替)を解決しない。実運用ではDDS設計とフェイルオーバーが必須である。

Discovery Serverは必須か?

必須ではない。小規模で同一LAN、マルチキャスト制限がなく、ノード数が少なければ分散Discoveryでも動く。一方、VPN越し、無線、台数増加、監視ツールの常時接続などがあると、Discovery Serverが安定化に効く場面が増える。

QoSはどこから設計すべきか?

「落ちると危険な情報(イベント)」と「落ちても良い情報(ストリーム)」を分けるところから始めるべきである。前者は冪等・再試行・タイムアウトまで含めて設計し、後者はBest Effortを許容して遅延増を避ける、という整理が実務的である。

フェイルオーバーの最小実装は?

まずは管制をActive/Standbyにし、ロボット側は命令を冪等に処理する(命令IDを持つ)ところが最小である。その上で、Discovery制御(範囲制限やDiscovery Server冗長)と、重要ノードのLifecycle運用を追加していくと崩れにくい。

参考文献