제11부 도구·관측성
11.1 현장 디버깅 플레이북
11.1.1 이 장에서 배우는 것 / 선수 지식
이 장은 제11부의 결론이자 이 책 앞부분 전체의 실무적 소환이다. 100개 장에 걸쳐 각 계층의 함정을 다뤘는데, 현장에서는 그것들이 증상 하나로 뭉쳐서 나타난다. "로봇이 안 움직여요"라는 한 문장 뒤에 스무 가지 원인이 있다.
플레이북이 필요한 이유는 명확하다. 현장에서는 생각이 잘 안 된다. 시간 압박이 있고, 관계자가 지켜보고, 도구가 제한되고, 재현이 어렵다. 그런 상황에서는 미리 정해 둔 순서가 즉흥적 판단보다 낫다.
배우는 것:
- 분류 원칙 — 계층을 먼저 좁힌다.
- 최초 대응 체크리스트 — 5분 안에 하는 것.
- 증상별 경로 — 이 책 전체로의 색인.
- 현장 제약 — 원격·재현 불가·시간 압박.
- 데이터 확보 — 못 고쳐도 남길 것.
- 에스컬레이션 — 멈춰야 할 때.
- 사후 분석 — 재발 방지.
- 진단 가능한 시스템 — 사전 설계.
- 플레이북 검증(11.1.14) — 이 절차를 실제 문제에 실행해 본 결과.
- 층을 가르는 한 명령(11.1.15) —
ros2 doctor hello의 두 표를 읽는 법.
선수 지식: 이 책 전반. 특히 2.1.8(계층 식별), 3.3(디스커버리), 1.6(QoS), 5.2(tf), 6.9(진단), 11.2~11.7(도구).
정직: 이 장은 절차와 판단 원칙이며, 모든 문제에 대한 해답 목록이 아니다. 실제 현장은 예상 밖의 조합을 내놓는다. 원칙은 "계층을 좁히고, 데이터를 남기고, 무리하지 않는다" 이다. 이 환경에서 검증하지 않았다(목차 3).
11.1.2 분류 원칙 — 계층 먼저
2.1.8에서 세운 원칙이 플레이북의 뼈대다.
아래에서 위로 확인하는 것이 원칙이다.
| 이유 | 설명 |
|---|---|
| 아래가 깨지면 위가 모두 비정상이 된다 | 원인을 위에서 찾으면 무익한 작업 |
| 아래일수록 확인이 빠르다 | 전원·케이블·ros2 node list |
| 아래일수록 흔하다 | 케이블·전원·QoS가 다수 |
견해: "알고리즘이 잘못됐나 봐요"로 시작하는 진단이 가장 오래 걸린다. 실제로는 케이블이 빠졌거나, USB 권한이 없거나,
ROS_DOMAIN_ID가 다르거나, QoS가 안 맞는 경우가 훨씬 많다. 아래부터 5분만 확인하면 대부분 여기서 끝난다.
11.1.3 최초 대응 체크리스트
증상이 무엇이든 먼저 하는 것. 5분.
# --- ① 하드웨어·전원 ---
# 전원 LED · 케이블 · E-stop 상태 · 배터리 전압
# --- ② OS·네트워크 ---
uptime # 재부팅 흔적
df -h # ★ 디스크 가득? (11.3.7)
free -h # 메모리
dmesg | tail -50 # USB 재연결 · OOM · 커널 오류
ip a # 네트워크 인터페이스
timedatectl # ★ 시계 동기 (7.7.6)
# --- ③ 미들웨어 ---
echo $ROS_DOMAIN_ID # ★ 다르면 아무것도 안 보임 (1.5.8)
echo $RMW_IMPLEMENTATION # 노드 간 불일치 (3.5.6)
ros2 daemon status # 실재(실행 확인) — 실행 중이 아니면 "The daemon is not running"
# --- ④ ROS 그래프 ---
ros2 node list # 기대한 노드가 다 있나
ros2 topic list # 기대한 토픽이 있나
# --- ⑤ 데이터 ---
ros2 topic hz /scan --window 20 # 데이터가 흐르나 (창을 명시해야 최근 상태를 본다 — 0.7.12.2)
ros2 run tf2_tools view_frames # tf 트리 (5.2.5)
# --- ⑥ 진단·로그 ---
ros2 topic echo /diagnostics_agg --once # ★ 시스템이 스스로 말한다
ros2 topic echo /rosout | grep -i -E "error|warn"
| 확인 | 자주 걸리는 것 |
|---|---|
df -h | 디스크 가득 → 온갖 이상 증상 |
dmesg | USB 재연결·OOM 킬 |
$ROS_DOMAIN_ID | 완전 무통신 |
ros2 node list | 종료된 노드 |
/diagnostics_agg | 시스템이 이미 답을 알고 있음 |
견해:
/diagnostics_agg를 가장 먼저 보라(6.9.8). 진단을 제대로 구현했다면(6.9), 시스템이 무엇이 잘못됐는지 이미 말하고 있다. 그것을 안 보고 시행착오를 겪는 것이 흔한 낭비다.
견해:
df -h와dmesg가 의외로 자주 원인이다. 디스크가 가득 차면 로그·기록이 실패하고, 노드가 비정상적으로 종료되고, 원인 추적이 어려워진다(1.11.3·11.3.7). USB 장치 재연결은 센서가 조용히 사라지는 원인이다(6.1.8).
11.1.4 증상별 경로
이 책 전체로의 색인이다.
11.1.4.1 아무 데이터도 없다
| 확인 | 장 |
|---|---|
ROS_DOMAIN_ID·RMW·방화벽 | 1.5.8·3.5.6·3.9 |
| 이름·네임스페이스·리매핑 | 1.1.5·1.1.6 |
| QoS 불일치 | 1.5.4·1.6.3.1 |
| 멀티캐스트 차단 | 3.3.5·3.9.4 |
여러 대 사이라면 먼저 ros2 doctor hello | A.1.14.1 — 멀티캐스트와 DDS를 한 화면에서 가른다 |
11.1.4.2 데이터는 오는데 내용이 비정상이다
| 증상 | 원인 후보 | 장 |
|---|---|---|
| 값이 불규칙하게 변한다 | 센서 노이즈·전원 | 7.1 |
| 프레임이 이상 | tf·optical frame | 5.1.8·7.1.4 |
| 시각이 이상 | 스탬프·시계 | 7.7·1.10 |
| 단위가 이상 | 규약 위반 | 7.1.6 |
| 오래된 값 | 큐·지연 | 1.4.4·11.7.4 |
| 공분산이 이상 | 0을 "모름"으로 발행 | 7.1.12.2 — 소비자는 "절대 신뢰"로 읽는다 |
/compressed가 없음 | 플러그인 미설치 | 7.2.12 — ros-base에는 없다 |
11.1.4.3 가끔·간헐적
| 증상 | 원인 후보 | 장 |
|---|---|---|
| 가끔 끊김 | 무선·큐·QoS | 3.9.3·1.6 |
| 주기가 튐 | 실행기·부하 | 2.5.8·11.6 |
| 재부팅 후 다름 | 시작 순서·경합 | 1.12.6·4.9 |
| 장시간 실행하면 이상 | 누수·누적 | 11.7.6 |
| 특정 위치에서 | 환경·무선·측위 | 3.9·8.4 |
견해: 간헐적 문제가 가장 어렵고, 여기서 기록이 결정적이다(11.3.3). 재현을 기다리지 말고 순환 버퍼 기록을 켜 두고 사건을 포착한다. 이것이 이 책이 기록을 강조한 실무적 이유다.
11.1.4.4 로봇이 안 움직인다
| 확인 | 장 |
|---|---|
| E-stop·전원·안전 상태 | 6.1·8.7 |
/cmd_vel이 발행되나 | 8.8.8 |
| 컨트롤러 활성 상태 | 6.3.6 |
| 하드웨어 인터페이스 상태 | 6.2 |
| 항법 상태·목표 거부 | 8.7.6 |
| 코스트맵에 갇힘 | 8.3.8 |
| 속도 제한·collision_monitor | 8.7.5 |
11.1.4.5 항법·조작이 실패한다
| 영역 | 장 |
|---|---|
| 측위 흔들림 | 8.4.8 |
| 코스트맵 이상 | 8.3.8 |
| 경로 실패 | 8.5.8 |
| 복구 반복 | 8.7.6 |
| 계획 실패 | 9.2.8 |
| 파지 실패 | 9.5.8 |
11.1.4.6 빌드·실행 문제
| 증상 | 장 |
|---|---|
| 빌드 실패 | 4.5 |
| 옛 코드가 실행됨 | 4.5.8(오버레이) |
| 패키지를 못 찾음 | 4.1.8 |
| launch 오류 | 14.3.8 |
| 파라미터 미적용 | 1.9.7 |
견해: "고쳤는데 안 바뀐다"의 최다 원인은 오버레이·소싱 문제다(4.5.8).
ros2 pkg prefix로 실제로 어느 경로가 쓰이는지 확인하는 것이 확실하다.
11.1.5 현장 제약
현장은 개발실이 아니다.
| 제약 | 결과 | 대응 |
|---|---|---|
| GUI 불가 | RViz2·rqt 못 씀 | CLI 숙련(11.5.8) |
| SSH만 가능 | 화면 없음 | tmux로 세션 유지 |
| 재현 어려움 | 한 번뿐 | 즉시 기록(11.3.3) |
| 시간 압박 | 조급함 | 체크리스트 |
| 관계자 참관 | 압박 | 절차대로 |
| 재부팅 유혹 | 증거 소실 | 데이터 먼저 |
| 네트워크 제한 | 원격 접속 불가 | 로컬 수집 |
| 안전 요구 | 함부로 못 움직임 | 정지 상태 진단 |
견해: "일단 재부팅"이 현장에서 가장 아까운 행동이다. 문제가 사라지지만 원인도 사라진다. 재부팅 전에 최소한의 데이터를 확보하라 — 로그,
dmesg, 프로세스 상태, 실행 중인 bag. 5분 투자로 다음 재발을 막을 수 있다.
재부팅 전 확보:
# 한 번에 수집하는 스크립트를 미리 만들어 두라
mkdir -p /tmp/incident_$(date +%s) && cd $_
ros2 node list > nodes.txt 2>&1
ros2 topic list -v > topics.txt 2>&1
ros2 param dump /some_node > params.yaml 2>&1
ros2 topic echo /diagnostics_agg --once > diag.txt 2>&1
dmesg > dmesg.txt
journalctl -n 5000 > journal.txt
cp -r ~/.ros/log/latest ros_log
df -h > disk.txt; free -h > mem.txt; ps auxf > ps.txt
ip a > net.txt; timedatectl > time.txt
tar czf ../incident.tgz .
견해: 이 수집 스크립트를 로봇에 미리 넣어 두라. 현장에서 명령을 하나씩 치는 것과 스크립트 하나를 실행하는 것은 다르다. 압박 상황에서 빠뜨리지 않는 것이 목적이다.
견해:
tmux(또는screen)를 습관화하라. SSH가 끊겨도 세션이 유지되고, 기록·모니터링을 여러 창에서 동시에 볼 수 있다. 무선이 불안정한 현장에서 특히 값을 한다.
11.1.6 데이터 확보
못 고쳐도 이것만은 남긴다.
| 우선순위 | 데이터 |
|---|---|
| 1 | bag(가능하면 사건 전후) |
| 2 | /rosout·노드 로그 |
| 3 | /diagnostics 이력 |
| 4 | dmesg·journalctl |
| 5 | 시스템 상태(디스크·메모리·프로세스) |
| 6 | 관찰 기록(사람이 본 것·시각) |
| 7 | 설정·커밋 해시 |
견해: "사람이 본 것"을 적는 것을 빠뜨리지 말라. "14:32경 로봇이 문 앞에서 좌우로 흔들리다 정지, 소리 없음" 같은 서술이 로그와 맞춰 볼 때 결정적 단서가 된다. 로그에는 없는 정보다.
견해: 시각을 정확히 적으라. 로그와 대조하려면 시각이 필요하다. 로봇 시계와 사람의 시계가 다를 수 있으니(7.7.6), 로봇에서
date를 한 번 출력해 두면 대조가 쉬워진다.
분석 순서:
11.1.7 에스컬레이션
멈춰야 할 때가 있다.
| 신호 | 판단 |
|---|---|
| 안전이 걸림 | 즉시 중단·전문가 |
| 30분 이상 진전 없음 | 데이터 확보 후 후퇴 |
| 추측으로 설정을 바꾸고 있음 | 위험 — 되돌리기 |
| 여러 변경을 동시에 | 인과 불명 |
| 원인 모른 채 "고쳐짐" | 재발 가능 |
| 하드웨어 손상 위험 | 중단 |
견해: "추측으로 설정을 계속 바꾸는 상태"를 자각하는 것이 중요하다. 이 상태에서는 문제를 더 복잡하게 만들 뿐이고, 우연히 고쳐져도 원인을 모른다. 변경을 되돌리고, 데이터를 모으고, 차분히 분석하는 것이 결국 빠르다.
견해: 한 번에 하나만 바꾸라. 현장 압박에서 세 가지를 동시에 바꾸면 무엇이 효과였는지 모르고, 부작용이 섞인다. 바꾼 것을 기록하면 되돌리기도 쉽다.
견해: 안전 관련 이상은 다르게 다뤄야 한다(8.7.1·9.6.1의 반복). 로봇이 예상 밖으로 움직이거나 안전 기능이 의심되면, 디버깅보다 정지와 격리가 먼저다. 이 책은 안전 인증에 관한 조언을 제공하지 않으며, 해당 판단은 안전 담당자·표준·전문가를 따라야 한다.
11.1.8 사후 분석
같은 문제를 두 번 겪지 않기 위한 절차다.
| 항목 | 질문 |
|---|---|
| 무슨 일이 | 사실만 |
| 직접 원인 | 기술적 원인 |
| 왜 못 막았나 | 시험·감지의 공백 |
| 왜 못 찾았나 | 진단의 공백 |
| 조치 | 수정·감지 추가 |
| 재발 방지 | 회귀 시험 |
견해: "왜 못 막았나"와 "왜 못 찾았나"가 가장 가치 있는 질문이다. 원인을 고치는 것은 그 사건만 해결하지만, 감지 공백을 메우면 유사 문제 전체를 다음번엔 빨리 찾는다. 진단 항목 하나 추가(6.9)가 수 시간의 디버깅을 절약한다.
견해: 사람을 탓하는 사후 분석은 정보를 소실시킨다. 실수가 가능했던 구조를 묻는 것이 유용하다 — 왜 잘못된 설정이 배포될 수 있었나, 왜 시험에서 안 걸렸나. 이것이 CI(4.10)·배포 절차(4.7)를 개선하는 입력이 된다.
산출물:
| 산출 | 예 |
|---|---|
| 코드·설정 수정 | 직접 원인 |
| 진단 항목 추가 | 다음엔 즉시 감지 |
| 회귀 시험 추가 | 재발 차단 |
| bag 데이터셋 편입 | 11.3.8 |
| 문서·체크리스트 갱신 | 절차 |
| 함정 목록 추가 | 팀 지식 |
11.1.13 프로세스를 찾는 명령 — -f가 자기 자신을 매칭한다
이 책의 진단 예시는 $(pgrep -f <이름>) 형태를 자주 쓴다(1.13·2.2·2.4·2.6·2.7·3.2·3.5·3.6·6.6·7.8). 대개 잘 동작하지만 실패 방식이 까다로워서 한 번 짚어 둔다.
-f는 프로세스 이름이 아니라 명령줄 전체를 매칭한다. 그래서 다음이 함께 매칭된다:
| 함께 매칭되는 것 | 결과 |
|---|---|
| 그 명령을 실행 중인 셸 자신 | 명령줄에 그 문자열이 들어 있다 |
같은 이름을 연 편집기·less·grep | 잘못된 PID |
ros2 run 래퍼와 실제 실행 파일 | PID가 둘 이상 나와 -p가 실패한다 |
pgrep이면 잘못된 결과에 그치지만, pkill -f면 자기 셸을 강제 종료한다. 스크립트가 그 줄에서 즉시 종료되며, 원인을 찾기 어렵다(종료 코드만 137/143으로 남는다).
안전한 형태 셋:
# ① 프로세스 이름이 정확하면 -x (권장)
PID=$(pgrep -x talker)
# ② 패턴이 꼭 필요하면 대괄호로 자기 매칭을 피한다
ps -eo pid,cmd | grep "[t]alker" | awk '{print $1}'
# ③ 내가 실행한 프로세스면 PID를 변수에 담아 둔다 — 검색 자체를 없앤다
/opt/ros/jazzy/lib/demo_nodes_cpp/talker & PID=$!
견해: ③이 가장 확실하다. 진단 스크립트를 쓸 때 "찾아서 강제 종료한다"는 구조 자체가 위험하다. 실행한 쪽이 PID를 기억하면 검색이 필요 없고, systemd로 관리한다면
systemctl kill이 프로세스 그룹을 정확히 다룬다(14.8).
함정: 여러 PID가 나오는 것도 흔한 실패다.
ros2 run pkg node는 파이썬 런처와 실제 노드가 각각 프로세스라pgrep -f node가 둘을 준다.ps -p $(pgrep -f node)는 인자 하나를 기대하므로 그대로 깨진다.pgrep -x로 실행 파일 이름을 정확히 지목하거나,pgrep -n(가장 최근)·head -1로 하나만 고른다.
11.1.9 진단 가능한 시스템
디버깅의 난이도는 대부분 사전 설계로 정해진다. 이 책이 여러 장에서 반복한 것들을 여기 모은다.
| 설계 | 장 | 효과 |
|---|---|---|
진단 발행(/diagnostics) | 6.9 | 시스템이 스스로 말함 |
| 의미 있는 로그(구조화·레벨) | 1.11 | 사후 추적 |
| 상시 기록(작은 토픽) | 11.3.2 | 사건 전 상황 |
| 순환 버퍼·이벤트 기록 | 11.3.3 | 간헐 문제 포착 |
| 성능 지표 진단 | 11.7.9 | 열화 감지 |
| 명확한 이름·네임스페이스 | 1.1 | 그래프 이해 |
| tf 트리 단순·단일 부모 | 5.1 | 프레임 문제 감소 |
| 규약 준수(REP·단위·프레임) | 7.1 | 오해 감소 |
| 상태 기계 명시 | 1.12 | 상태 파악 |
| 파라미터 문서화·검증 | 1.9 | 설정 실수 감소 |
| 버전·커밋 노출 | 4.7 | 무엇이 실행되고 있나 |
| 수집 스크립트 배치 | 11.1.5 | 현장 대응 |
견해: "현장에서 SSH만으로 진단할 수 있는가"를 설계 기준으로 삼으라. 이 질문에 "아니오"라면, 필요한 정보가 시스템 밖으로 나오지 않는다는 뜻이다. 진단·로그·기록이 그 통로이며, 나중에 추가하기보다 처음부터 넣는 것이 훨씬 비용이 낮다.
견해: 버전 정보를 노출하는 것이 의외로 중요하다. 현장에서 "지금 어느 버전이 실행되고 있나"를 모르면 분석이 시작되지 않는다. 노드가 시작 시 커밋 해시·빌드 시각을 로그에 남기거나 진단에 포함하면(4.7), 이 혼란이 사라진다.
11.1.10 제11부를 마치며
제11부가 반복한 것은 셋이다.
- 기록하지 않은 것은 분석할 수 없다. 사건은 예고 없이 오고 재현되지 않는다. 상시 기록과 순환 버퍼가 그 대비다(11.2·11.3).
- 질문에서 도구로 가라. 도구가 많고 각각 강점이 다르다. "무엇을 알고 싶은가"에서 출발하면 도구는 정해지고, 반대로 하면 시간을 버린다(11.5.8).
- 측정 없이 판단하지 말라. 성능도, 회귀도, 병목도 추측으로는 틀린다. 그리고 평균이 아니라 꼬리를 보라(11.7.3).
그리고 이 부 전체를 관통하는 하나 — 진단 가능성은 사후에 얻어지지 않는다. 6.9에서 "진단으로 내보내라"고 한 것, 1.11에서 "의미 있게 로깅하라"고 한 것, 11.3에서 "상시 기록하라"고 한 것이 모두 현장에서 회수되는 투자다.
11.1.14 이 플레이북을 실제로 실행해 봤다 — 다섯 시나리오 실측
책이 "이렇게 진단하라"고 적어 두는 것과 그 절차가 실제로 답을 주는 것은 다르다. Jazzy 컨테이너에서 문제를 일부러 만들고 이 장의 절차대로 따라가 봤다(실행 확인).
11.1.14.1 시나리오 ① — "토픽은 있는데 데이터가 안 온다"
BEST_EFFORT로 발행하는 노드와 RELIABLE을 요구하는 구독자를 실행했다.
| 단계 | 결과 |
|---|---|
| 증상 | 구독자가 6초 동안 0건 수신 — 에러도 경고도 없다 |
11.1.4의 첫 확인: ros2 topic info /probe -v | Endpoint type: PUBLISHER / Reliability: BEST_EFFORT 가 바로 보인다 |
| 판정 | 한 번의 명령으로 원인 확정 |
이 장이 topic info -v를 첫 확인으로 둔 것이 맞다. 발행자·구독자의 QoS가 나란히 출력되므로, 둘을 비교하면 호환성 규칙(1.5.4)에 걸리는지 즉시 안다.
예외 하나 —
system_default엔드포인트에서는 이 판정이 성립하지 않는다. 내구성이TRANSIENT_LOCAL로 표시되는데 실제 동작은VOLATILE이고, 히스토리·깊이는 전혀 안 실린다(82차 실측, 1.6.12). 신뢰성은 위 시나리오처럼 믿을 수 있고, 내구성이 걸린 증상("늦게 참여하면 못 받는다")일 때만 늦은 구독자 시험으로 확인하라.
11.1.14.2 시나리오 ② — "아무것도 안 보인다"
ROS_DOMAIN_ID=7로 노드를 실행하고 기본 도메인에서 찾았다.
| 단계 | 결과 |
|---|---|
| 증상 | ros2 node list가 0줄 |
11.1.3 ③의 확인: echo $ROS_DOMAIN_ID | 셸은 미설정(=0), 노드는 7 |
| 확인 | ROS_DOMAIN_ID=7 ros2 node list → /talker 가 보인다 |
도메인을 바꿔 한 번 더 조회하는 것이 결정적이다. "환경 변수를 확인하라"는 지시만으로는 "내 값이 맞는지"를 모르지만, 다른 값으로 조회해 보이면 원인이 확정된다.
견해: 최초 대응 체크리스트(11.1.3)에 "의심되는 도메인으로 다시 조회"를 넣어 두면 좋다. 값을 읽는 것과 값을 바꿔 시험하는 것은 다른 행동이고, 후자가 훨씬 빠르게 답을 준다.
11.1.14.3 시나리오 ③ — "파라미터가 적용되지 않는다"
YAML의 노드 이름을 일부러 틀리게 두고 노드를 실행했다(실행 확인).
| YAML 최상위 키 | 실제 노드 | 결과 |
|---|---|---|
my_node: | /real_node | 기본값 10.0 — 오류도 경고도 없다 |
/real_node: | /real_node | 99.0 적용 |
/**: | /real_node | 77.0 적용 |
틀린 이름은 조용히 무시된다. 파일을 읽었다는 로그도, 매칭 실패 경고도 없다 — "YAML을 줬는데 값이 그대로"가 유일한 증상이다.
진단 순서: ①
ros2 param get <노드> <이름>으로 서버에 저장된 값을 본다 → 기본값이면 ② YAML 최상위 키와ros2 node list의 이름을 글자 그대로 대조한다(앞 슬래시·네임스페이스 포함) → ③ 급하면/**:로 바꿔 적용되는지 본다. 적용되면 이름 문제가 확정된다(1.9.7·14.3.12.2).*①의 표현에 주의하라 —
param get이 보여 주는 것은 저장값이지 노드가 쓰는 값이 아니다. 값이 제대로 보이는데도 동작이 안 바뀐다면 YAML 문제가 아니라 노드가 시작 때 읽고 캐시한 것이다(83차 실측, 0.7.11.1) — 그때는 노드를 재시작*하거나add_on_set_parameters_callback을 넣어야 한다.
11.1.14.4 시나리오 ④ — "고쳤는데 안 바뀐다"
같은 패키지를 두 워크스페이스에 두고 AMENT_PREFIX_PATH 순서만 바꿨다(실행 확인).
| 경로 순서 | ros2 pkg prefix demo_pkg |
|---|---|
| B가 앞 | /tmp/ws_b/install/demo_pkg |
| A가 앞 | /tmp/ws_a/install/demo_pkg |
ros2 pkg prefix가 실제로 쓰이는 경로를 그대로 알려 준다. 이 장이 첫 확인으로 이 명령을 둔 것이 맞다 — 내가 고친 워크스페이스가 아니라 다른 곳이 나오면 그 자리에서 원인이 확정된다.
함정: 패키지가
Package not found로 나오는 경우가 있다. 경로만 만들고share/ament_index/resource_index/packages/<패키지>마커가 없으면 ament가 못 찾는다(0.5.5). 빌드가 중간에 실패했을 때 이 상태가 되므로, "경로는 있는데 못 찾는다"면 마커를 확인하라.
11.1.14.5 시나리오 ⑤ — "컨테이너에서 미발견"
Docker 기본 브리지 네트워크에서 확인했다(실행 확인).
2: eth0@if4370: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 …
$ ros2 multicast receive # 다른 셸에서
Waiting for UDP multicast datagram...
Received from 172.17.0.5:57330: 'Hello World!'
같은 컨테이너 안에서는 멀티캐스트가 정상 동작한다. eth0에 MULTICAST 플래그가 있고 ros2 multicast가 왕복한다.
즉 "컨테이너라서 디스커버리가 안 된다"는 말은 부정확하다. 진단은 안쪽부터 — 컨테이너 안에서
ros2 multicast send/receive가 되는지 먼저 보고, 되면 그 다음이 네트워크 모드 문제다.정정: 처음에는 여기에 "막히는 것은 컨테이너 사이와 호스트↔컨테이너"라고 적었는데, 다음 회차에 컨테이너를 둘 기동해 측정해 보니 그것도 틀렸다. 같은 Docker 네트워크의 컨테이너끼리는 별도 설정 없이 통한다 — 실제 경계는 3.9.12에 실측으로 정리했다. 널리 퍼진 말을 검증 없이 옮기면 이렇게 된다는 사례로 남겨 둔다.
11.1.14.6 이 장의 조언이 스스로에게도 적용됐다
검증 중에 pkill -f로 자기 셸을 강제 종료했다(exit 143). 이 장이 11.1.13에 "-f는 자기 자신을 매칭한다"고 적어 둔 바로 그 함정이다.
책의 경고가 실제로 일어나는 것을 확인한 셈이라 남겨 둔다 — 진단 중에는 손이 빨라져 아는 함정도 밟는다. pgrep -x를 쓰는 습관이 그래서 필요하다.
11.1.15 ros2 doctor hello 두 표 읽기 — 실측
11.1.2의 원칙은 "계층 먼저"였다. ros2 doctor hello는 그 원칙을 명령 하나로 실행해 주는 도구인데, 출력의 표가 두 개라는 점을 모르면 그 값을 놓친다. 세 가지 상황을 만들어 측정해 봤다(실행 확인 — Jazzy · ros:jazzy-ros-base 컨테이너 둘 · rmw_fastrtps_cpp).
11.1.15.1 출력의 두 표
두 대(또는 두 컨테이너)에서 동시에 실행하면 상대가 표에 나타난다.
MULTIMACHINE COMMUNICATION SUMMARY
Topic: /canyouhearme, Published Msg Count: 10
Subscribed from: ← ① ROS 층 (토픽이 실제로 오가는가)
Hostname Msg Count /1.0s
93f3fa8503b8 10
Multicast Group/Port: 225.0.0.1/49150, Sent Msg Count: 10
Received from: ← ② 네트워크 층 (생 멀티캐스트가 닿는가)
Hostname Msg Count /1.0s
93f3fa8503b8 10
①은 ROS 2 토픽, ②는 생 UDP 멀티캐스트다. 두 층을 각각 재 주기 때문에 어느 층에서 끊겼는지가 한 화면에 나온다.
11.1.15.2 세 상황을 만들어 측정해 봤다
| 상황 | ① Subscribed from | ② Received from | 읽는 법 |
|---|---|---|---|
| 같은 네트워크·같은 도메인 | 채워짐 | 채워짐 | 정상 |
| 다른 Docker 네트워크 | 비어 있음 | 비어 있음 | 네트워크 층 문제 — 방화벽·서브넷·멀티캐스트 차단(3.9) |
| 도메인 불일치(0 ↔ 42) | 비어 있음 | 채워짐 | 네트워크는 정상, ROS 설정 문제 — 도메인·RMW·QoS(1.1·3.8) |
셋째 줄이 이 도구의 존재 이유다. "패킷은 오는데 토픽은 안 온다" — 이 조합이 보이면 네트워크를 더 볼 필요가 없다. 방화벽 규칙을 며칠 뒤지는 대신 ROS_DOMAIN_ID·RMW_IMPLEMENTATION·QoS를 본다.
견해: 노드가 서로 안 보일 때 두 번째로 칠 명령이 이것이다. 첫 번째는
echo $ROS_DOMAIN_ID(11.1.14.2), 그다음이ros2 doctor hello다. 순서를 이렇게 두는 이유는 도메인 확인이 1초면 끝나는 데 비해doctor hello는 양쪽에서 동시에 실행해야 하기 때문이다 — 비용이 낮은 것부터.
네 번째 조합(① 채워짐 · ② 비어 있음)은 만들지 못했다. 멀티캐스트 없이 디스커버리가 이미 성립한 경우 — Discovery Server나 ROS_STATIC_PEERS(3.3.7) — 라면 나올 법하지만 측정해 보지 않았으므로 추측이다.
11.1.15.3 실행할 때의 함정 둘
① 양쪽에서 동시에 실행해야 한다. 한쪽만 실행하면 표가 비고, 그것을 "통신 실패"로 오독하기 쉽다. 자기 자신은 표에 나오지 않는다 — 표에 나타나는 것은 상대뿐이다.
② Ctrl-C가 아니면 잘 종료되지 않는다. 스크립트에서 timeout으로 감쌌더니 SIGTERM으로 종료되지 않아 명령이 계속 매달렸다.
# ❌ 안 끝난다
timeout 15 ros2 doctor hello
# ✅ 확실히 끝낸다
timeout -s KILL 15 ros2 doctor hello
멀티캐스트 수신 스레드가 recvfrom에서 블로킹된 채 남기 때문으로 보인다(확인 필요: 정확한 원인). 자동화 스크립트에 넣을 때는 -s KILL을 쓰라. 11.1.13의 교훈과 같은 계열이다 — 진단 명령 자체가 진단을 방해하는 경우가 있다.
11.1.11 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 위 계층부터 의심 | 오래 걸림 | 아래부터 |
| 2 | /diagnostics 미확인 | 답을 두고 헤맴 | 먼저 보기 |
| 3 | df·dmesg 미확인 | 근본 원인 놓침 | 체크리스트 |
| 4 | 재부팅 먼저 | 증거 소실 | 데이터 먼저 |
| 5 | 여러 변경 동시 | 인과 불명 | 하나씩 |
| 6 | 추측 기반 설정 변경 반복 | 악화 | 되돌리고 분석 |
| 7 | 원인 모른 채 종료 | 재발 | 사후 분석 |
| 8 | 관찰 기록 없음 | 단서 상실 | 사람이 본 것 기록 |
| 9 | 시각 미기록 | 로그 대조 불가 | 시각 명시 |
| 10 | GUI 의존 | 현장에서 무력 | CLI 숙련 |
| 11 | tmux 미사용 | 세션 끊김 | 습관화 |
| 12 | 안전 이상을 디버깅으로 | 위험 | 정지·전문가 |
| 13 | 사람 탓 사후 분석 | 정보 은폐 | 구조를 물음 |
| 14 | 진단 없는 시스템 | 매번 어려움 | 사전 설계 |
| 15 | 버전 불명 | 분석 시작 불가 | 버전 노출 |
| 16 | pgrep -f 로 PID 찾기 | 자기 셸까지 매칭됨 · PID 여러 개 | pgrep -x·대괄호 grep·PID 변수(11.1.13) |
| 17 | 환경 변수 값만 읽고 넘어감 | 내 값이 맞는지 알 수 없다 | 의심되는 값으로 다시 조회해 본다(11.1.14.2) |
| 18 | doctor hello를 한쪽에서만 실행 | 표가 비어 통신 실패로 오독 | 양쪽 동시 실행(11.1.15.3) |
| 19 | 컨테이너면 디스커버리 실패로 단정 | --network host부터 켜게 됨 | 경계를 먼저 좁힌다(3.9.12) |
11.1.12 체크포인트
- 계층을 아래부터 확인하는 이유 셋을 말할 수 있는가.
- 최초 대응 체크리스트에서 자주 원인이 되는 다섯 항목은?
/diagnostics_agg를 가장 먼저 보라는 조언의 근거는?- "아무 데이터도 없다"의 진단 흐름과, 각 분기의 원인 후보를 말할 수 있는가.
- 간헐적 문제에서 결정적인 것은 무엇인가.
- "고쳤는데 안 바뀐다"의 최다 원인과 확인 방법은?
- 현장 제약 여덟 가지와, 가장 아까운 행동은 무엇인가.
- 재부팅 전에 확보할 데이터와, 스크립트를 미리 두는 이유는?
- 데이터 확보 우선순위 일곱 가지 중, 로그에 없는 정보는?
- 에스컬레이션 신호 여섯 가지와, 자각해야 할 상태는?
- "한 번에 하나만 바꾸라"의 근거 둘은?
- 사후 분석에서 가장 가치 있는 두 질문과 그 이유는?
- 사람을 탓하지 않는 사후 분석이 실용적인 이유는?
- 진단 가능한 시스템의 설계 요소 열두 가지를 들 수 있는가.
- "현장에서 SSH만으로 진단할 수 있는가"라는 기준의 의미는?
- 제11부가 반복한 세 원칙은?
- QoS 불일치와 도메인 불일치를 각각 어느 명령 한 번으로 확정할 수 있는가(11.1.14).
ros2 doctor hello의 표 두 개는 각각 무엇을 측정하는가. 한쪽만 채워졌을 때 어디를 봐야 하는가(11.1.15).
여기까지가 제11부 도구·관측성이다. 기록(11.2·11.3), 시각화(11.4·11.5), 내부 관측(11.6), 측정(11.7), 그리고 현장(11.1).
11.7에서 성능을 측정하는 법을 다뤘다면, 다음 질문은 자연스럽다 — 어떻게 빠르게, 그리고 시간을 지키도록 만드는가. 실시간 제약이란 무엇이고, 리눅스에서 어디까지 가능하며, ROS 2의 어느 부분이 실시간을 방해하는가. 제12부 실시간·성능(12.1~12.6) 의 주제다.