제0부 개관과 출발
0.7 ROS 2 CLI 체계와 데몬의 내부
0.7.1 이 장에서 배우는 것 / 선수 지식
ros2 명령은 ROS 2를 관찰하는 가장 기본적인 창이다. 그런데 이 창은 때때로 거짓말을 한다. 종료된 노드를 실행 중이라고 하고, 흐르는 데이터를 안 흐른다고 하고, 실제 주파수와 다른 Hz를 보여준다. 이것은 버그가 아니라 CLI가 동작하는 방식의 필연적 결과이며, 그 방식을 알면 언제 믿고 언제 의심할지 판단할 수 있다.
이 장은 CLI를 도구 목록이 아니라 메커니즘으로 다룬다. 명령 목록 자체는 부록 A.1에 정리하고, 여기서는 "왜 그렇게 동작하는가"에 집중한다.
배우는 것:
ros2명령의 구조 —ros2 <명령> <동사>가 어떻게 확장 가능한 플러그인으로 구성되는가.- 데몬(
ros2 daemon)의 정체 — 왜 존재하고, 무엇을 캐시하며, 언제 거짓말의 원인이 되는가. - CLI가 그래프에 참여한다는 사실 —
ros2 topic echo가 만드는 임시 노드와 그 부작용. - CLI가 틀리는 일곱 가지 경우와 각각의 대처.
- 진단 명령의 보고값을 대조군으로 시험한 결과(0.7.11) —
param get·node list·doctor가 각각 무엇을 답하고 무엇을 답하지 않는가. ros2 topic hz의 정확도와 한계(0.7.12) — 어디까지 믿을 수 있고, 창(window)이 왜 실제 오차원인가.- QoS와 CLI —
echo가 아무것도 못 보는 상황을 푸는 법. - 관측 명령의 실전 레시피 — 그래프·데이터·타이밍·파라미터를 빠르게 확인하는 조합.
- 운영 로봇에서 CLI를 쓸 때의 주의 — 관측 자체가 시스템을 흔드는 경우.
선수 지식: 0.4(도메인·RMW 환경변수), 0.6(노드를 실행해 본 경험). QoS는 아직 몰라도 되지만, 이 장에서 처음 마주치게 된다(정식 설명은 1.5).
정직: CLI 옵션 이름·기본값은 배포판·패키지 버전에 따라 달라질 수 있다. 아래 옵션은 Jazzy 기준으로 적었으나 실행 검증하지 않았다(목차 3).
ros2 <명령> <동사> --help가 언제나 정본이다.검증 상태(추가): 이 장의 서술 가운데 3곳은 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).
0.7.2 ros2 명령의 구조 — 하나의 진입점, 플러그인 확장
ROS 1의 rostopic·rosnode·rosservice는 각각 별도 실행 파일이었다. ROS 2는 ros2 하나의 진입점 아래 명령(command)과 동사(verb)를 둔다.
ros2 <명령> <동사> [인자]
| 위치 | 값의 예 |
|---|---|
| 명령(command) | topic · node · service · action · param · bag · launch · run · pkg · … |
| 동사(verb) | list · info · echo · pub · call · … (명령마다 다름) |
이 구조는 setuptools 엔트리포인트 기반 플러그인으로 구현된다. ros2cli 패키지가 뼈대를 제공하고, ros2topic·ros2node 같은 패키지가 각각의 명령을 등록한다.
실무적 의미 셋:
- CLI는 Python이다. 그래서 시작이 느리고(수백 밀리초), 대용량 데이터 처리에서 비효율적이다.
ros2 topic echo로 포인트클라우드를 받으면 CPU 사용률이 급증한다. - 자체 명령을 추가할 수 있다. 조직 전용 진단 명령을
ros2 mycompany diag형태로 붙일 수 있다 — 엔트리포인트를 등록하기만 하면 된다. 팀 도구를 표준 경로에 배치하는 좋은 방법이다. - 설치된 패키지에 따라 사용 가능한 명령이 다르다.
ros-base만 설치한 로봇에는 일부 명령이 없을 수 있다(0.4.3).
0.7.3 데몬 — 왜 있고 무엇을 캐시하나
ros2 node list를 실행할 때마다 새 프로세스가 DDS 디스커버리를 처음부터 수행한다면, 매번 수 초를 기다려야 한다(디스커버리는 즉시 완료되지 않는다 — 3.3). 그래서 ROS 2는 백그라운드 데몬을 둔다.
- 데몬은 도메인마다 하나씩 기동된다(
ROS_DOMAIN_ID별). 첫 CLI 실행 시 자동으로 시작된다. - 데몬은 그래프에 참가자로 계속 남아 디스커버리 정보를 축적한다. 그래서 두 번째 이후의
ros2 node list가 빠르다.
ros2 daemon status # 실행 여부
ros2 daemon stop # 종료 (캐시 초기화)
ros2 daemon start # 수동 시작
데몬이 만드는 문제 — "유령 노드":
데몬의 캐시는 즉시 정확하지 않다. 노드가 비정상 종료(SIGKILL·전원 차단)하면 DDS의 참가자 타임아웃이 지나기 전까지 데몬은 그 노드를 실행 중이라고 믿는다. 그래서 ros2 node list에 이미 종료된 노드가 남는다.
| 증상 | 대처 |
|---|---|
| 종료된 노드가 목록에 남음 | ros2 daemon stop 후 재조회 |
| 새로 기동된 노드가 안 보임 | 잠시 기다리거나 데몬 재시작 |
| 결과가 비정상적으로 일관되지 않음 | --no-daemon 옵션으로 직접 조회 |
ros2 node list --no-daemon # 데몬을 우회해 직접 디스커버리 (느리지만 정확)
견해: 디버깅 중 무언가 비정상이면 가장 먼저
ros2 daemon stop을 해 보라. 5초 걸리는 조치로 30분을 아끼는 경우가 많다. 반대로 데몬이 없으면 모든 CLI가 느려지므로, 평소에는 켜 두는 것이 맞다.
0.7.4 CLI는 그래프의 참여자다
가장 중요한 사실이자 가장 자주 잊히는 사실이다. ros2 topic echo는 관찰자가 아니라 참여자다. 실행하는 순간 임시 노드(_ros2cli_<pid> 형태)가 만들어지고, 구독자가 생기고, 디스커버리가 일어나고, 데이터가 네트워크를 거쳐 그 프로세스로 온다.
여기서 세 가지 부작용이 나온다.
- 관측이 시스템을 바꾼다. 대용량 토픽에
echo를 적용하면 발행자는 구독자가 하나 더 생긴 것으로 처리한다 — 직렬화·전송 비용이 실제로 늘어난다. 무선 링크에서는 이것만으로 대역이 포화될 수 있다.image_transport의 lazy subscriber처럼 "구독자가 있을 때만 일하는" 노드는 동작 자체가 달라진다(7.2). - CLI 자신의 QoS가 적용된다. 발행자와 호환되지 않으면 아무것도 못 받는다(0.7.5).
- 디스커버리 부하가 는다. 노드가 많은 시스템에서 CLI를 자주 실행하면 디스커버리 트래픽이 그만큼 증가한다(3.3).
숨은 엔티티는 기본적으로 목록에서 감춰진다. 필요하면 드러낼 수 있다.
ros2 topic list --include-hidden-topics
ros2 node list --all # 실재(실행 확인). --count-nodes 도 있다
0.7.5 QoS와 CLI — 무엇이 달라졌나
오랫동안 이것이 가장 흔한 CLI 함정이었다. 예전 배포판의 상황은 이랬다.
- 발행자가
BEST_EFFORT인데ros2 topic echo는 기본RELIABLE로 구독한다 → 연결되지 않음 → 아무것도 안 나옴. - 발행자가
TRANSIENT_LOCAL(래치)로 한 번만 발행했는데echo는VOLATILE로 구독한다 → 이미 지난 메시지를 못 받음 → 아무것도 안 나옴.
Jazzy에서는 이 증상이 CLI에서 대체로 사라졌다(실행 확인). ros2 topic echo의 Reliability·Durability·Liveliness 기본값이 best_available 이라, 이미 연결된 발행자에 맞춰 자동으로 낮춰 매칭되기 때문이다(1.5.3.8).
그러나 문제가 없어진 것이 아니라 자리를 옮겼다. 자동 협상은 CLI에만 적용되고 직접 작성한 구독 노드에는 적용되지 않는다 — 명시하지 않으면 기본이 RELIABLE이므로 위 첫 줄의 상황에 그대로 걸린다. 그래서 Jazzy 특유의 혼동은 이렇게 나타난다.
"
ros2 topic echo로는 잘 보이는데, 내 노드에서는 아무것도 안 들어온다."
이 증상을 보면 CLI가 아니라 내 코드의 QoS를 의심해야 한다. 아래 진단은 그대로 유효하다.
진단:
ros2 topic info -v /some_topic
이 명령은 발행자·구독자 각각의 QoS 프로파일을 전부 보여준다. 여기서 신뢰성(reliability)·내구성(durability)이 서로 다른지 확인한다.
대처 — Jazzy에서는 먼저 "아무것도 붙이지 마라"다(실행 확인).
# Jazzy의 topic echo 기본 QoS는 best_available —
# 이미 연결된 발행자에 맞춰 자동으로 낮춰 매칭된다. 대개 이걸로 보인다.
ros2 topic echo /scan
실측으로 확인했다 — BEST_EFFORT 발행자에 대해 옵션 없는 ros2 topic echo는 정상 수신하고, --qos-reliability reliable을 강제하면 오히려 비호환으로 끊긴다(Last incompatible policy: RELIABILITY). 예전 배포판에서 통하던 "안 보이면 best_effort를 붙여라"는 Jazzy에서는 불필요하며, reliable을 붙이는 것은 적극적으로 해롭다(1.5.3.8).
# 옵션이 필요한 경우 — 자동 협상 결과를 일부러 고정하고 싶을 때
ros2 topic echo /scan --qos-reliability best_effort # 명시적 고정
ros2 topic echo /map --qos-durability transient_local # 래치 토픽 과거분까지
# 발행 시에는 자동 협상이 없다 — 명시해야 한다
ros2 topic pub --once /map nav_msgs/msg/OccupancyGrid "{...}" \
--qos-durability transient_local
--qos-profile best_available도 직접 지정할 수 있다.ros2 topic echo --help가 받아들이는 프로파일 목록에best_available이 실재한다(실행 확인).
함정: "토픽이 안 나온다"를 코드 문제로 오해해 몇 시간을 쓰는 일이 흔하다.
ros2 topic info -v를 먼저 보는 습관이 이 부류의 문제를 즉시 끝낸다. QoS의 정식 설명은 1.5, 실전 설계는 1.6.
0.7.6 CLI가 틀리는 일곱 가지 경우 — 함정 카탈로그
| # | 상황 | 왜 | 대처 |
|---|---|---|---|
| 1 | 종료된 노드가 목록에 남음 | 데몬 캐시 + DDS 참가자 임대 만료 — 강제 종료일 때만(정상 종료는 0초, SIGKILL은 약 22초) | ros2 daemon stop 뒤 다시 조회하거나, 조회할 때 --no-daemon을 붙인다 — 단 --no-daemon은 실행 중인 노드를 10회 중 2회 놓친다(0.7.11.2·0.7.11.3) |
| 2 | 머신마다 topic list 결과가 다름 | 디스커버리는 비대칭·점진적 | 양쪽에서 확인, 네트워크 점검(3.9) |
| 3 | echo는 보이는데 내 노드만 침묵 | CLI는 best_available로 자동 협상, 내 노드는 기본 RELIABLE | topic info -v → 내 코드의 QoS 수정(0.7.5) |
| 4 | hz가 실제와 다름 | 대개는 창(window) 탓 — 기본 10,000개라 100 Hz면 100초를 기억한다. Python 처리 지연은 작은 메시지에서는 거의 무시할 수준(1 kHz까지 오차 ≈0) | --window를 명시, 대용량이면 1~3% 낮게 나오는 것을 감안(0.7.12) |
| 5 | echo를 적용하면 시스템이 느려짐 | 관측이 참여다(0.7.4) | --once·--field·필드 절단 옵션 사용 |
| 6 | param·service call이 응답 없음 | 대상 노드가 spin하지 않거나 콜백이 막힘(1.7·2.4) | 대상 노드의 executor 상태 점검 |
| 7 | 토픽·노드가 전혀 안 보임 | 도메인·RMW 불일치, 멀티캐스트 차단 | ROS_DOMAIN_ID·RMW_IMPLEMENTATION 확인(0.4) |
대용량 토픽을 안전하게 관찰하기:
ros2 topic echo /camera/image_raw --no-arr # 배열 내용 생략
ros2 topic echo /points --field header # 특정 필드만
ros2 topic echo /points --once # 한 번만
ros2 topic hz /camera/image_raw --window 50 # 주파수만
ros2 topic bw /camera/image_raw # 대역폭만
--no-arr·--field는 역직렬화·출력 비용을 줄이는 것이지 수신 비용을 없애지는 않는다 — 데이터는 여전히 네트워크를 거쳐 온다. 무선에서 이미지 토픽을 확인할 때는 bw·hz만 쓰고 echo는 피하는 편이 안전하다.
0.7.11 진단 명령이 보고하는 값과 실제를 대조했다 — 실측
82차에 ros2 topic info -v가 한 자리에서 사실과 다른 값을 낸다는 것이 드러났다(1.6.12). 그렇다면 다른 진단 명령은 어떤가 — 이 장이 "CLI가 틀리는 일곱 가지 경우"(0.7.6)를 이미 적어 두었으므로, 그 항목들을 대조군을 두고 실제로 시험했다(실행 확인, Jazzy · rmw_fastrtps_cpp 컨테이너).
대조군이 핵심이다 — "안 보인다"가 정말 없어서인지, 명령이 못 찾은 것인지를 가르지 못하면 아무것도 판정할 수 없다(11.7.13.6).
0.7.11.1 ros2 param get은 적용값이 아니라 저장값을 보여 준다
같은 파라미터를 캐시하는 노드와 콜백으로 반영하는 노드에 각각 걸고 값을 바꿨다.
# 흔한 형태 — 시작할 때 한 번 읽고 그 뒤로 안 본다
self.declare_parameter("rate_hz", 10.0)
self.rate = self.get_parameter("rate_hz").value # ← 여기서 끝
| 노드 | ros2 param set 결과 | ros2 param get이 말하는 값 | 노드가 실제로 쓰는 값 |
|---|---|---|---|
| 캐시형(콜백 없음) | Set parameter successful | 99.0 | 10.0 |
| 대조군(콜백으로 반영) | Set parameter successful | 99.0 | 99.0 |
두 명령이 모두 성공이라고 말하는데 노드는 옛 값으로 실행된다. 파라미터 서버에는 정말로 99.0이 들어갔으므로 param get은 거짓말을 한 것이 아니라 다른 것을 답한 것이다 — 저장값과 적용값은 별개다.
거부는 정직하게 보고된다. 콜백이 successful=False를 돌려주게 만들면 이렇게 나온다.
$ ros2 param set /rejector rate_hz 99.0
Setting parameter failed: 테스트용 거부
$ ros2 param get /rejector rate_hz
Double value is: 10.0
선이 여기 있다 — *거부는 정확히 보이고, 무시는 성공으로 보인다. 콜백이 없는 노드에게 파라미터를 바꾸는 것은 실패가 아니라 무의미이며, CLI에는 그 구분이 없다. "
param set이 성공했으니 반영됐겠지"*가 성립하지 않는 이유다(1.9.8).
0.7.11.2 종료된 노드는 어떻게 종료됐는지에 따라 목록에 남는다
0.7.6의 1번 항목("종료된 노드가 목록에 남음")을 신호별로 갈랐다.
| 종료 방식 | ros2 node list에서 사라지기까지 |
|---|---|
SIGTERM(정상 종료) | 0초 — 즉시 |
SIGKILL(강제 종료) | 약 21~22초 |
정상 종료에서는 유령이 생기지 않는다. 노드가 내려가며 DDS가 참가자 소멸을 알리기 때문이다. SIGKILL은 그럴 틈이 없어 다른 참가자가 임대(lease)가 끊길 때까지 기다린다 — 관측된 20초 남짓이 그 시간이다.
이것이 가장 불리한 때에 일어난다. 세그폴트·OOM 킬러·
kill -9로 종료된 노드는 정확히 당신이 원인을 찾고 있는 그 20초 동안 실행 중인 것처럼 보인다(14.8.12·14.8.13). "목록에 있으니 실행 중이다"로 판단하면 잘못된 곳을 조사한다.
0.7.11.3 --no-daemon은 대안이 아니라 다른 종류의 오류다
0.7.6은 대처로 --no-daemon을 제시한다. 두 경로를 나란히 측정해 봤다.
| 실행 중인 노드 탐지 | 종료된 노드(SIGKILL) 보고 | |
|---|---|---|
| 데몬 경유(기본) | 10/10 | 22초간 계속 있다고 보고 |
--no-daemon | 8/10 ← 두 번 놓쳤다 | 즉시 0건 |
바꿔치기가 아니라 오류의 종류를 바꾸는 것이다. 데몬은 거짓 양성(없는 것을 있다고), --no-daemon은 거짓 음성(있는 것을 없다고)을 낸다. --no-daemon은 매번 새로 디스커버리를 수행하는데 그 시간이 짧아 놓치는 경우가 있다.
그래서 한 번의 조회로 판정하지 마라. ① 기본(데몬)으로 두세 번 조회해 일관되게 나오는지 보고, ② 강제 종료했는데도 남아 있으면
--no-daemon으로 교차 확인하고, ③ 확실히 하려면ros2 daemon stop뒤 다시 조회한다. 두 경로가 다른 답을 주면 그 자체가 정보다 — 데몬만 보인다면 이미 종료된 노드다.
0.7.11.4 ros2 doctor는 통신이 끊긴 상태에서도 통과한다
0.3이 "통과했다고 시스템이 정상이라는 뜻은 아니다"라고 적어 둔 것을 실제로 확인했다. ROS_DOMAIN_ID=7에 노드를 실행해 기본 도메인에서는 보이지 않는 상태를 만든 뒤 진단했다.
$ ros2 topic list # 그 노드의 토픽은 없다
/parameter_events
/rosout
$ ros2 doctor
… UserWarning: geometry2 has been updated to a new version. local: 0.36.21 < latest: 0.36.22
All 5 checks passed
노드가 서로 못 보는데 5개 검사가 전부 통과했다. 게다가 유일하게 나온 경고는 패키지 버전이 상류보다 낮다는, 지금 문제와 아무 상관 없는 내용이다.
*
ros2 doctor는 환경을 보지 그래프를 보지 않는다. 배포판·플랫폼·네트워크 인터페이스·패키지 버전이 검사 대상이고, "내 노드가 상대를 보는가"는 검사 항목이 아니다. 노드가 서로 안 보일 때 칠 명령은ros2 doctor가 아니라ros2 doctor hello(11.1.15)다 — 이름이 비슷하지만 측정하는 것이 다르다.*
0.7.11.5 정리 — 무엇을 믿고 무엇을 확인할 것인가
| 명령 | 믿어도 되는 것 | 믿으면 안 되는 것 |
|---|---|---|
ros2 param get | 파라미터 서버의 저장값 | 노드가 쓰는 적용값(0.7.11.1) |
ros2 param set | 거부됐다는 사실 | 성공 = 반영(콜백 없으면 무시) |
ros2 node list(데몬) | 실행 중인 노드가 나온다 | 목록에 있다 = 실행 중이다(강제 종료 후 22초) |
ros2 node list --no-daemon | 나온 것은 실재한다 | 안 나온다 = 없다(10회 중 2회 누락) |
ros2 doctor | 환경·플랫폼·패키지 | 통과 = 통신 정상(전혀 별개) |
ros2 topic info -v | 신뢰성·타입 | system_default의 내구성(1.6.12) |
공통점이 하나 있다 — 어느 명령도 틀린 값을 낸 것이 아니라, 독자가 묻는 것과 다른 것을 답했다. param get은 저장값을, node list는 디스커버리 상태를, doctor는 환경을 답한다. 명령이 무엇을 측정하는지 알면 어긋남이 사라진다.
견해: 진단 명령은 "확인"이 아니라 "증거 하나"로 다뤄라. 이 장이 이미 관측이 참여다(0.7.4)라고 말했듯, 관측 결과도 시스템의 일부이지 시스템 밖의 진실이 아니다. 판정은 두 개 이상의 서로 다른 증거가 같은 곳을 가리킬 때 내리는 것이 안전하다.
0.7.12 ros2 topic hz를 정답을 아는 발행자로 측정해 봤다 — 실측
0.7.10의 체크포인트가 "hz의 수치를 어디까지 믿을 수 있는가"를 묻는데, 이 책은 그 답을 갖고 있지 않았다. 0.7.6의 4번 함정도 "CLI의 Python 처리 지연·유실"이라고 원인을 짐작으로 적어 두었다. 그래서 발행률을 스스로 세어 알려 주는 C++ 발행자를 만들어 대조했다(실행 확인, Jazzy · 20코어 컨테이너).
0.7.12.1 정상 상태에서는 놀랄 만큼 정확하다
| 조건 | 실제 발행률 | hz 보고 | 오차 |
|---|---|---|---|
| 10 Hz · 8 B | 10.00 | 9.999 | −0.01% |
| 100 Hz · 8 B | 100.00 | 99.998 | −0.002% |
| 1 kHz · 8 B | 999.99 | 999.995 | ≈0 |
| 5 kHz · 8 B | 4999.99 | 4997.450 | −0.05% |
| 10 kHz · 8 B | 9999.96 | 9943.288 | −0.57% |
| 100 Hz · 1 MB | 99.99 | 99.016 | −0.97% |
| 100 Hz · 4 MB | 99.99 | 97.119 | −2.9% |
| 100 Hz · 1 MB + CPU 부하 | 100.00 | 99.001 | −1.0% (부하 없을 때와 같다) |
작은 메시지라면 1 kHz까지 사실상 정확하다. "Python이라 못 믿는다"는 통념이 이 조건에서는 성립하지 않는다.
두 가지가 더 보인다.
- 오차는 항상 음의 방향이다 —
hz가 실제보다 크게 나온 경우가 없었다. 그래서hz가 목표보다 높게 나오면 그것은 CLI 탓이 아니라 실제 발행자 문제(중복 발행·다중 노드)라고 볼 수 있다. - 오차는 데이터량을 따라간다 — 초당 400 MB(4 MB × 100 Hz)에서 2.9%다. 주파수가 아니라 대역이 지배 요인이다.
0.7.6의 4번 함정을 정정했다. "부하 적은 시점에 재라"는 조언은 이 실험에서 근거가 없었다 — 20코어 중 4개를 바쁜 루프로 점유해도 결과가 그대로였다. 실제 오차원은 다음 절이다.
0.7.12.2 실제 문제는 창(window)이 메시지 단위라는 것
--help가 밝히듯 기본 창은 메시지 10,000개다(초가 아니다). 관측으로도 창이 10000에서 멈추는 것을 확인했다.
발행률이 100 Hz에서 10 Hz로 급락하는 발행자를 만들어 측정해 봤다.
급락 10초 뒤 hz 보고 | 실제 | |
|---|---|---|
| 기본 창(10,000) | 66.785 Hz | 10 Hz |
--window 10 | 9.999 Hz | 10 Hz |
기본 창은 6.7배 부풀린 값을 말한다. 옛 데이터가 창에 그대로 남아 평균을 끌어올리기 때문이다.
여기서 규칙이 하나 나온다.
$$\text{창이 기억하는 시간} = \frac{10{,}000}{\text{발행률(Hz)}}$$
| 토픽 주기 | 기본 창이 기억하는 시간 |
|---|---|
| 1 kHz | 10초 |
| 100 Hz | 100초 |
| 10 Hz | 약 17분 |
| 1 Hz | 약 3시간 |
필요와 정반대로 작동한다. 느린 토픽일수록 빨리 이상을 알고 싶은데, 창이 메시지 단위라 느릴수록 기억이 길다.
/scan(10 Hz)이 절반으로 떨어져도 기본hz는 몇 분 동안 옛 값을 섞어 보여 준다.처방은 하나다 — 진단할 때는 창을 명시하라.
ros2 topic hz /scan --window 20이면 최근 20개만 본다. 0.7.6이 적어 둔 "짧게 측정"은 결과적으로 맞는 조언이었지만(짧게 보면 창도 안 찬다) 이유가 달랐다.
0.7.12.3 발행이 완전히 멈추면 아무 말도 하지 않는다
100 Hz로 10초 발행한 뒤 완전히 정지시키고 25초를 더 지켜봤다.
average rate: 99.999
min: 0.009s max: 0.011s std dev: 0.00015s window: 906
← 여기서 출력이 아무 표시 없이 끊긴다. 25초를 기다려도 더 나오지 않는다.
0 Hz도, 경고도, 아무것도 없다. 화면에 마지막으로 남은 값은 99.999 다.
이것이 가장 위험한 형태다. 터미널을 흘깃 보면 "99.999 — 잘 동작하고 있네"로 읽힌다. 신호는 값이 아니라 "새 줄이 안 나온다"는 사실이며, 여러 창을 열어 놓고 보는 상황에서는 놓치기 쉽다.
그래서
hz는 "흐르는가"를 묻는 도구이지 "멈췄는가"를 묻는 도구가 아니다. 멈춤을 판정하려면ros2 topic echo --once에 타임아웃을 걸거나(값이 오면 실행 중이다) 진단 갱신기의Stale판정(6.9.14)을 쓰는 편이 옳다 — 후자는 "안 온다"를 적극적으로 알리도록 설계된 장치다.
0.7.12.4 정리
| 묻는 것 | hz로 답할 수 있나 |
|---|---|
| 데이터가 흐르는가 | 된다 — 줄이 나오면 흐른다 |
| 발행률이 얼마인가 | 된다 — 정상 상태에서 1 kHz까지 정확 |
| 발행률이 방금 변했는가 | 기본 창으로는 안 된다 — --window를 줘라 |
| 토픽 발행이 중단됐는가 | 안 된다 — 출력이 끊길 뿐 아무 말도 없다 |
| 발행자가 정말 그 속도로 보내는가 | 간접적으로만 — hz는 CLI에 도착한 것을 센다 |
마지막 줄이 83차의 결론과 같다(0.7.11.5) — hz도 "발행자가 얼마나 보내는가"가 아니라 "내게 얼마나 도착하는가"를 답한다. 대용량에서 나온 1~3%의 음의 오차가 바로 그 차이다.
0.7.7 관측 명령의 실전 레시피
그래프 파악(처음 보는 시스템에 들어갔을 때):
ros2 node list # 누가 있나
ros2 topic list -t # 무엇이 흐르나 (-t: 타입 함께)
ros2 node info /some_node # 이 노드의 발행·구독·서비스·액션
ros2 topic info -v /some_topic # 양쪽 끝과 QoS
데이터 확인:
ros2 topic echo /topic --once
ros2 topic hz /topic
ros2 topic bw /topic
ros2 interface show sensor_msgs/msg/Imu # 이 타입의 필드 정의
ros2 interface proto sensor_msgs/msg/Imu # 채워 넣을 YAML 뼈대
ros2 interface proto는 topic pub·service call의 인자를 직접 작성할 때 특히 유용하다.
제어·설정:
ros2 param list /some_node
ros2 param describe /some_node some_param # 타입·범위·읽기전용 여부
ros2 param get /some_node some_param
ros2 param set /some_node some_param 3.14
ros2 param dump /some_node > node_params.yaml # 현재값 백업 (1.9)
ros2 service list -t
ros2 service call /some_service std_srvs/srv/Trigger "{}"
ros2 action list -t
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose "{...}" --feedback
라이프사이클·컴포넌트(1.12·1.13):
ros2 lifecycle nodes
ros2 lifecycle get /managed_node
ros2 lifecycle set /managed_node configure
ros2 component list
ros2 component load /container my_pkg my_pkg::MyComponent
환경·네트워크 점검:
ros2 doctor --report # 배포판·플랫폼·네트워크 요약
ros2 multicast receive # 다른 터미널·다른 머신에서
ros2 multicast send # 멀티캐스트 도달성 확인 (3.9)
ros2 multicast send·receive 쌍은 "디스커버리가 안 되는 이유가 멀티캐스트 차단인가" 를 가르는 가장 빠른 시험이다. 두 머신에서 이 쌍이 통하지 않으면 ROS 2 이전에 네트워크 문제다.
0.7.8 자동완성과 도움말 탐색
# 자동완성 — /opt/ros/jazzy/share/ros2cli/environment/ros2-argcomplete.{bash,zsh} (실행 확인)
source /opt/ros/jazzy/setup.bash # 완성 스크립트 포함
ros2 --help
ros2 topic --help
ros2 topic echo --help # 동사 단위 도움말이 가장 정확하다
CLI 옵션은 배포판마다 바뀌므로, 책이나 블로그의 옵션보다 --help가 정본이다. 특히 QoS 관련 옵션 이름은 배포판에 따라 표기가 달라진 이력이 있다.
0.7.9 운영 로봇에서의 CLI — 관측의 비용
개발 환경에서는 CLI를 마음껏 써도 되지만, 필드에서 운용되는 로봇에 SSH로 들어가 CLI를 쓸 때는 다르다.
| 위험 | 설명 | 완화 |
|---|---|---|
| 대역 포화 | echo가 대용량 토픽을 끌어와 무선을 막는다 | bw·hz만 쓰기, 로컬에서만 echo |
| 디스커버리 부하 | CLI 프로세스마다 참가자가 늘어난다 | 조회를 최소화, 데몬 재사용 |
| lazy 노드 활성화 | 구독자가 생겨 발행이 시작된다 | 영향 있는 토픽 파악 후 접근 |
| 실시간 경로 방해 | Python CLI가 CPU를 점유 | CPU 격리·우선순위 분리(12.2) |
| 의도치 않은 상태 변경 | param set·service call·lifecycle set은 시스템을 바꾼다 | 읽기 명령과 쓰기 명령을 의식적으로 구분 |
대안(권장): 필드 진단은 CLI 즉석 조회보다 상시 관측 체계로 푸는 편이 안전하다 — 진단 토픽(6.9), 상시 기록(11.3), 메트릭·트레이싱(11.6). CLI는 그 위에서 "확인 사살"용으로 쓴다. 이 원칙은 11.1(현장 디버깅 플레이북)에서 절차로 정리한다.
0.7.10 체크포인트
ros2 <명령> <동사>구조가 어떻게 확장되는지(엔트리포인트) 설명하고, 팀 전용 명령을 추가하는 방법을 말할 수 있는가.- 데몬이 존재하는 이유와, 데몬 때문에 생기는 대표적 오류(유령 노드)를 설명할 수 있는가.
--no-daemon은 언제 쓰나. - "
ros2 topic echo는 관찰자가 아니라 참여자"라는 문장의 실무적 함의 세 가지를 말할 수 있는가. ros2 topic echo가 아무것도 출력하지 않을 때, 가장 먼저 실행할 한 줄은 무엇인가.ros2 topic hz의 수치를 어디까지 믿을 수 있는가. 더 신뢰할 만한 측정 수단은 무엇인가(11.6·11.7·0.7.12).- 100 Hz 토픽이 방금 10 Hz로 떨어졌다. 기본 창의
hz는 왜 66 Hz라고 말하는가(0.7.12.2). - 토픽 발행이 중단됐는지
hz로 판정할 수 있는가. 못 한다면 무엇을 쓰는가(0.7.12.3). - 필드 로봇에서
echo대신bw·hz를 쓰라는 권고의 근거를 설명할 수 있는가. - 읽기 명령과 시스템을 바꾸는 명령을 구분해 나열할 수 있는가.
ros2 param set이successful이라고 답했는데 노드 동작이 안 바뀐다. 버그인가 아닌가, 무엇으로 확인하는가(0.7.11.1).ros2 node list에 노드가 보인다. 실행 중이라고 단정할 수 있는가. 어떤 경우에 못 하는가(0.7.11.2).ros2 doctor가 전부 통과했는데 노드가 서로 안 보인다. 모순인가(0.7.11.4).
여기까지가 제0부다. 설치·워크스페이스·패키지·CLI라는 작업대가 갖춰졌다. 다음 장 1.1부터 제1부에서는 이 위에서 실행되는 계산 그래프의 코어 — 노드·이름·리매핑부터 시작해 토픽·QoS·서비스·액션·파라미터·라이프사이클까지 — 를 하나씩 원리 수준에서 연다.