제14부 시스템 설계·품질·운영
14.2 시스템 오케스트레이션 — 모드·부팅·감시
14.2.1 이 장에서 배우는 것 / 선수 지식
노드를 잘 나눴다고 시스템이 되는 것은 아니다. 누가 언제 켜고, 무엇을 기다리고, 문제가 생기면 어떻게 하는가 — 이것을 정하지 않으면 개별 노드가 아무리 좋아도 시스템은 불안정하다.
실무에서 흔한 증상들이 이 문제다. "재부팅하면 가끔 기동되지 않는다", "센서가 늦게 기동돼서 실패한다", "노드 하나가 종료됐는데 아무도 모른다", "수동 모드에서 자동으로 바꾸면 로봇이 급격히 움직인다". 모두 오케스트레이션의 부재다.
배우는 것:
- 시스템 상태와 모드 — 무엇을 정의하는가.
- 부팅 순서 — 의존과 대기.
- 준비 상태 판정 — 언제 "준비됨"인가.
- 감시 — 실행 중인가·정상인가.
- 장애 대응 — 재시작·강등·정지.
- 모드 전환 — 안전하게 바꾸기.
- 종료 — 정상 종료하기.
- 명령 중재 —
/cmd_vel에 여럿이 쓸 때 누가 우선하는가(14.2.12).
선수 지식: 1.12(생애주기 노드), 6.9(진단), 8.7.6(항법 복구), 14.1(노드 분할). launch는 0.6의 첫 경험이면 충분하다 — 이 장은 launch를 대조 대상으로만 쓰며, 심층은 뒤의 14.3에서 다룬다. 14.8(배포·systemd)은 함께 읽으면 좋다.
정직: 생애주기 노드의 정확한 상태·전이 이름과 launch의 이벤트 처리 API는 배포판 의존이다
(확인 필요). 이 장은 패턴과 판단을 다루며, 안전 관련 상태 설계는 안전 요구·표준·전문가의 영역이다(8.7.1·14.10). 이 환경에서 검증하지 않았다(목차 3).검증 상태(추가): 이 장의 서술 가운데 1곳은 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).
14.2.2 시스템 상태와 모드
개별 노드의 상태(1.12)와 시스템 전체의 모드는 다른 층위다.
| 모드 | 의미 |
|---|---|
| 초기화 | 노드 기동·센서 준비 |
| 대기 | 준비됐으나 작업 없음 |
| 수동 | 사람이 직접 조작 |
| 자동 | 자율 동작 |
| 점검 | 정비·캘리브레이션 |
| 고장 | 안전 상태·복구 필요 |
| 종료 중 | 정리 |
모드가 결정하는 것:
| 결정 | 예 |
|---|---|
| 어떤 명령을 받는가 | 자동 모드에서만 목표 수락 |
| 어떤 노드가 활성 | 항법은 자동에서만 |
| 속도 제한 | 수동 모드는 저속 |
| 안전 감시 강도 | 상황별 |
| 표시·알림 | 운영자에게 |
견해: 모드를 명시적으로 정의하고 한 곳에서 관리하라. 모드가 여러 노드에 암묵적으로 흩어지면(각자 "자동인가?"를 다르게 판단), 불일치가 사고로 이어진다. 모드를 발행하는 단일 노드를 두고 나머지는 그것을 구독하는 구조가 명확하다.
견해: "모드 전환 시 무슨 일이 일어나는가"를 표로 만들라. 각 전환에서 어떤 노드가 활성/비활성되고, 어떤 상태가 초기화되고, 무엇이 유지되는지. 이 표가 없으면 전환 때마다 예상 밖 동작이 나온다.
함정: 모드를 파라미터로 두면 안 된다. 파라미터는 누구나 바꿀 수 있고(13.1.5), 변경 시점 통제가 없으며, 전환 로직을 실행하지 않는다. 서비스·액션으로 전환을 요청하고 조율자가 검증 후 수행하는 것이 옳다.
14.2.3 부팅 순서
"모두 동시에 실행하기"가 안 되는 이유들이 있다.
| 의존 종류 | 예 |
|---|---|
| 하드웨어 준비 | 드라이버 → 컨트롤러 |
| 데이터 의존 | tf → 인지 → 계획 |
| 설정 의존 | 파라미터 서버·지도 |
| 시계 동기 | 인증(13.3.3)·정합(7.7) |
| 자원 | GPU·메모리 |
함정: "launch에서 delay를 넣어 순서를 맞추는" 방식은 취약하다. 하드웨어가 느린 날·부하가 높은 날 실패한다. 준비 상태를 확인하고 진행하는 구조가 견고하다(14.2.4).
견해: 생애주기 노드(1.12)가 이 문제의 표준적 해법이다.
configure에서 자원을 확보하고, 준비되면activate한다. 조율자가 각 노드를 순서대로 전이시키면 명시적이고 관측 가능한 부팅이 된다.
견해: 다만 모든 노드를 생애주기로 만드는 것은 과할 수 있다. 서드파티 노드는 생애주기가 아닐 수 있고, 변환 비용도 있다. 핵심 경로만 생애주기로 하고 나머지는 준비 확인으로 다루는 절충이 실용적이다.
부팅 실패 처리:
| 상황 | 대응 |
|---|---|
| 필수 노드 실패 | 시스템을 고장 모드로 |
| 선택 노드 실패 | 강등 운전(14.2.6) |
| 타임아웃 | 재시도 횟수 제한 후 고장 |
| 부분 실패 | 어느 기능이 없는지 명시 |
함정: 부팅 실패를 무한 재시도하면 문제가 숨는다. 로그만 쌓이고 시스템은 영원히 준비되지 않는다. 재시도 횟수·시간을 제한하고, 초과하면 명확히 고장 상태로 가야 사람이 알아챈다.
14.2.4 준비 상태 판정
"이 노드가 준비됐다"를 어떻게 아는가.
| 방법 | 특성 |
|---|---|
| 생애주기 상태 | 명시적·표준(1.12) |
| 진단 발행 | /diagnostics의 OK(6.9) |
| 토픽 발행 여부 | 간접·간단 |
| 서비스 응답 | 요청 기반 |
| 전용 준비 토픽 | 명시적·커스텀 |
// 준비 확인 패턴 (개념)
bool wait_for_ready(const std::string & topic, std::chrono::seconds timeout)
{
auto deadline = steady_clock::now() + timeout;
while (steady_clock::now() < deadline) {
if (count_publishers(topic) > 0 && got_first_message_) return true;
std::this_thread::sleep_for(100ms);
}
return false;
}
견해: "토픽이 발행되는가"는 간단하지만 불충분하다. 발행은 시작했지만 캘리브레이션이 안 끝났거나, 값이 아직 무효일 수 있다. 노드가 스스로 "준비됨"을 선언하게 하는 것이 정확하다.
견해: 진단(6.9)을 준비 판정에 재사용하는 것이 경제적이다. 이미 상태를 발행하고 있다면 조율자가 그것을 읽어 판정하면 되고, 별도 메커니즘을 만들지 않아도 된다.
/diagnostics_agg의 최상위가 OK인가가 시스템 준비의 좋은 근사다.
함정: 준비 확인에 타임아웃이 없으면 영원히 기다린다. 모든 대기에 시간 제한과 그 후의 행동을 정하라(14.2.3의 부팅 실패 처리).
14.2.5 감시
실행 중인가, 그리고 정상인가.
| 층 | 탐지 대상 | 수단 |
|---|---|---|
| ① 프로세스 | 종료·재시작 | systemd(14.8) |
| ② 노드 | 그래프에서 사라짐 | 그래프 감시 |
| ③ 신선도 | 멈춤·느려짐 | 토픽 주기(6.9.2) |
| ④ 상태 | 자체 진단 | /diagnostics |
| ⑤ 기능 | 실행 중이지만 무용 | 종단 확인 |
견해: ③(신선도)이 가장 실용적인 감시다. 프로세스가 실행 중이어도 콜백이 멈추거나 데드락에 걸리면 데이터가 안 나온다(1.7.3). "마지막 메시지로부터 N초" 를 감시하면 이 상태를 검출한다.
견해: ⑤(종단 기능)를 잊지 말라. 모든 노드가 실행 중이고 모든 토픽이 흐르는데 로봇이 아무것도 못 하는 상황이 있다. "실제로 목표에 도달할 수 있는가" 같은 상위 지표를 주기적으로 확인하거나, 실패율을 추적하는 것이 대응이다.
워치독 패턴:
| 패턴 | 설명 |
|---|---|
| 하트비트 | 주기적 생존 신호 |
| 타임아웃 감시 | 신호가 끊기면 조치 |
| 상호 감시 | 서로를 봄 |
| 외부 워치독 | 하드웨어·별도 프로세스 |
| 계층적 | 상위가 하위를 |
견해: 안전 관련 워치독은 소프트웨어 밖에 두는 것이 원칙이다(8.7.7·12.1.6·13.1.4의 반복). 시스템이 전체적으로 멈췄을 때 소프트웨어 워치독도 함께 멈춘다. 하드웨어 워치독이나 독립 회로가 최종 보장이며, 그 설계는 안전 표준의 영역이다(14.10).
함정: 감시 자체가 부하가 되면 안 된다. 모든 토픽을 구독해 감시하면 대역과 CPU를 소모한다(12.6.8). 진단으로 각 노드가 스스로 보고하게 하고, 감시자는 그것만 보는 구조가 효율적이다.
14.2.6 장애 대응
문제를 발견한 다음 무엇을 하는가.
| 대응 | 적용 |
|---|---|
| 안전 정지 | 안전 관련·제어 상실 |
| 강등 운전 | 일부 기능만 |
| 재시작 | 일시적 오류 |
| 경보 | 사람에게 알림 |
| 무시·기록 | 경미 |
강등 운전 예:
| 상실 | 강등 동작 |
|---|---|
| 카메라 | 라이다만으로 저속 |
| 측위 | 정지 후 재측위 요청 |
| 한쪽 센서 | 감속·범위 제한 |
| 원격 통신 | 자율 계속·복귀(3.9.3) |
| 계획기 | 정지·수동 요청 |
견해: 강등 운전을 설계하면 가용성이 크게 오른다. "무언가 잘못되면 정지"만 있으면 사소한 문제로도 운영이 멈춘다. "이 기능이 없으면 무엇을 못 하고, 나머지로 무엇을 할 수 있는가" 를 미리 정하면 부분 고장에서도 일을 계속한다.
견해: 다만 강등이 위험을 키우지 않아야 한다. 센서 하나를 잃고 계속 운전하는 것이 안전한지는 판단이 필요하며, 속도 제한·범위 제한을 동반하는 것이 일반적이다. 이 판단은 안전 요구의 영역이다(14.10).
함정: 자동 재시작이 문제를 감춘다. 노드가 계속 종료되고 재시작되는데 겉으로는 동작하면, 근본 원인이 방치된다. 재시작 횟수를 세어 진단으로 노출하고(6.9), 임계 초과 시 강등·정지로 가야 한다.
14.2.7 모드 전환
전환 순간이 가장 위험하다.
| 위험 | 예 |
|---|---|
| 상태 불일치 | 일부만 전환됨 |
| 급격한 명령 변화 | 로봇이 튐 |
| 잔여 명령 | 이전 모드의 명령이 남음 |
| 권한 혼선 | 두 소스가 동시에 명령 |
| 미완료 작업 | 진행 중 작업 처리 |
안전한 전환 절차:
| 단계 | 이유 |
|---|---|
| 조건 검사 | 자동 전환은 정지 상태에서만 등 |
| 정지·정착 | 튐 방지 |
| 비활성화 | 잔여 명령 차단 |
| 상태 초기화 | 적분기·필터(6.5.5) |
| 활성화 | 새 모드 |
| 완료 알림 | 관측 가능성 |
견해: "전환 전 정지"가 대부분의 문제를 없앤다. 움직이는 중에 제어권을 바꾸면 명령이 불연속이 되어 로봇이 급격히 움직인다. 정지 상태에서만 전환을 허용하는 것이 단순하고 안전하며, 필요한 경우에만 무충격 전환(bumpless transfer)을 구현한다.
견해: 전환 완료를 명시적으로 알리라. 요청과 완료가 구분되지 않으면, 전환 중에 다른 요청이 들어오거나 운영자가 완료된 줄 알고 명령을 보낸다. 액션(1.8)이 이 성격에 잘 맞는다 — 진행·완료·실패가 구분된다.
함정: 컨트롤러 전환 시 적분기·필터 상태가 남는다(6.5.5). 오래된 오차가 누적된 적분기가 새 모드에서 갑자기 큰 출력을 낸다. 활성화 시 상태를 초기화하는 것을 규약으로 두라.
14.2.8 종료
정상 종료하는 것도 설계다.
| 항목 | 이유 |
|---|---|
| 동작 정지 먼저 | 안전 |
| 역순 종료 | 의존 관계 |
| 상태 저장 | 지도·설정·통계 |
| 기록 마감 | bag 정상 종료(11.2.7) |
| 하드웨어 안전 상태 | 브레이크·전원 |
| 타임아웃 후 강제 | 매달림 방지 |
함정:
SIGKILL로 강제 종료하면 bag의metadata.yaml이 안 써지고 상태가 저장되지 않는다(11.2.7). systemd의 종료 타임아웃을 충분히 주고(14.8), 노드는 종료 신호에서 정리를 수행해야 한다.
견해: "전원을 예고 없이 끊는 경우"도 설계에 포함하라. 배터리 방전·비상 차단에서는 정상 종료가 없다. 중요한 상태는 주기적으로 저장하고, 파일 시스템이 손상되지 않도록(저널링·동기화) 하는 것이 대비다.
14.2.9 조율자 노드
이 모든 것을 담당하는 노드를 둘 것인가.
| 방식 | 특성 |
|---|---|
| 중앙 조율자 | 명확·단일 지점 |
| 분산(각자 판단) | 견고하나 일관성 어려움 |
| 계층적 | 큰 시스템 |
| launch만 | 단순 시스템 |
견해: 중앙 조율자가 명확하지만 단일 실패 지점이다. 조율자가 종료되면 시스템이 관리되지 않으므로, 조율자 자체를 감시하고 그것이 없어도 안전 상태로 갈 수 있어야 한다. 안전은 조율자에 의존하지 않는다(8.7.7).
견해: 작은 시스템에서는 launch와 생애주기만으로 충분하다. 조율자를 만드는 것은 비용이므로, 모드가 여럿이고 장애 대응이 필요한 규모에서 도입한다. 처음부터 만들 필요는 없지만, 모드 개념은 처음부터 명시해 두면 나중에 조율자를 추가하기 쉽다.
14.2.12 명령 중재 — 누가 로봇을 움직이는가
번호에 대하여: 이 절은 나중에 추가되었다. 규약(CLD-03)에 따라 번호는 끝에 붙이고, 읽는 흐름을 위해 함정 카탈로그 앞에 놓았다.
14.2.2가 모드를 정의했다. 이제 실행 층의 질문이 남는다 — /cmd_vel에 명령을 쓰는 주체가 여럿일 때 누가 우선하는가.
현실의 로봇은 거의 항상 여럿이다.
아무 중재 없이 같은 토픽에 여럿이 발행하면 어떻게 되는가. ROS 2에는 발행자 우선순위가 없다(OWNERSHIP 정책은 노출되지 않는다 — 1.5.3.7). 구독자는 도착한 순서대로 마지막 것을 본다. 즉 20 Hz 항법과 10 Hz 조이스틱이 동시에 쓰면 로봇은 두 명령을 번갈아 실행한다 — 어느 쪽도 의도하지 않은 움직임이다.
14.2.12.1 세 가지 방식
| 방식 | 구조 | 맞는 상황 | 대가 |
|---|---|---|---|
| ① 중재 노드(mux) | 여러 입력 토픽을 받아 우선순위·타임아웃으로 하나를 골라 출력 | 대부분의 이동 로봇 | 노드 하나 추가, 지연 한 단계 |
| ② 모드가 발행자를 켜고 끈다 | 모드 전환 시 라이프사이클(1.12)로 항법·조작 중 하나만 활성 | 모드가 명확히 배타적일 때 | 전환 지연, 전환 중 공백 구간 |
| ③ 상위 조율자만 발행 | 조율자 노드(14.2.9)가 유일한 발행자이고 나머지는 그에게 제안 | 조율 로직이 이미 중앙집중일 때 | 조율자가 단일 장애점·병목 |
twist_mux는 ①을 구현한 널리 쓰이는 패키지다 — 입력마다 우선순위와 타임아웃을 주고, 높은 우선순위 입력이 들어오는 동안 낮은 것을 막는다. 조이스틱 입력이 끊기면 타임아웃 후 자동으로 항법으로 돌아가는 식이다.
실행 확인(2026-08-16): Jazzy apt 인덱스에
ros-jazzy-twist-mux(4.5.0)·ros-jazzy-teleop-twist-joy(2.6.5)·ros-jazzy-joy가 실재한다. 확인한 것은 패키지 존재·버전까지이며, 파라미터 이름·동작은 실행해 보지 않았다(확인 필요).
14.2.12.2 설계 원칙
| 원칙 | 이유 |
|---|---|
| 타임아웃은 필수다 | 우선순위만 있고 타임아웃이 없으면, 조작자가 손을 뗀 뒤에도 그 입력이 계속 우선한다. 링크가 끊기면 영원히 우선한다 |
| 정지는 중재 밖에 둔다 | 비상 정지·안전 감속은 "우선순위가 가장 높은 입력"이 아니라 중재 결과와 무관하게 작동하는 경로여야 한다(14.2.6·14.10) |
| 누가 우선하고 있는지 발행하라 | 중재 결과(현재 활성 입력)를 토픽·진단으로 내보내면 "왜 안 움직이나"가 즉시 답해진다(6.9) |
| 중재 지점을 하나로 | 여러 층에 중재가 흩어지면 우선순위 규칙이 서로를 덮어써 추적이 불가능해진다(14.1.2의 경계 원칙) |
견해: 중재는 안전 기능이 아니라 조정 기능으로 설계하라. mux에 "안전 정지"를 최고 우선순위 입력으로 넣는 구성은 흔하지만, 그러면 안전이 소프트웨어 노드 하나의 정상 동작에 의존하게 된다. 그 노드가 종료되면 중재 자체가 멈춘다. 안전 정지는 가능하면 하드웨어 또는 기저 컨트롤러 층에서 독립적으로 걸리게 하고(6.6·14.10), mux는 정상 동작 중의 입력 조정만 맡는 것이 견고하다.
함정:
/cmd_vel을 여러 곳에서 발행하면서 "잘 되던데요"라고 말하는 시스템이 실제로 많다. 우연히 한쪽이 훨씬 자주 발행해 대부분의 주기를 차지하고 있을 뿐이며, 부하가 오르거나 주기가 바뀌면 비율이 달라진다.ros2 topic info /cmd_vel로 발행자 수를 세는 것이 이 문제의 1분 걸리는 진단이다(1.1.8).
함정: 원격 조작 경로는 중재만으로 충분하지 않다. 명령 지연·끊김이 그대로 안전 문제가 되므로, 타임아웃 정지와 링크 감시가 함께 필요하다(3.9.3·13.4.3).
14.2.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 모드가 암묵적·분산 | 불일치 사고 | 단일 관리·발행 |
| 2 | 모드를 파라미터로 | 무통제 변경 | 서비스·액션 |
| 3 | delay로 부팅 순서 | 간헐 실패 | 준비 확인 |
| 4 | 준비 판정이 "토픽 있음" | 미완료 상태 사용 | 명시적 선언 |
| 5 | 대기에 타임아웃 없음 | 영원히 대기 | 제한·후속 행동 |
| 6 | 부팅 실패 무한 재시도 | 문제 은폐 | 횟수 제한 |
| 7 | 프로세스 생존만 감시 | 멈춘 노드 놓침 | 신선도 감시 |
| 8 | 종단 기능 미확인 | 무용 상태 방치 | 상위 지표 |
| 9 | 감시가 부하 | 성능 저하 | 진단 기반 |
| 10 | 안전 워치독이 SW에만 | 전체 정지 시 무력 | 하드웨어 |
| 11 | 강등 운전 미설계 | 사소한 고장에 정지 | 강등 정의 |
| 12 | 자동 재시작이 원인 은폐 | 반복 | 횟수 노출 |
| 13 | 움직이는 중 모드 전환 | 로봇이 튐 | 정지 후 전환 |
| 14 | 전환 시 상태 미초기화 | 갑작스러운 출력 | 초기화 규약 |
| 15 | 전환 완료 미통지 | 혼선 | 액션·명시 발행 |
| 16 | 강제 종료 | 상태·기록 손실 | 정상 종료 경로 |
| 17 | 조율자 단일 실패 미고려 | 관리 상실 | 감시·안전 독립 |
| 18 | 프로세스·노드 존재로 건강 판정 | finalized 노드는 살아서 아무것도 안 한다 | 라이프사이클 상태로 감시(1.12.10.3) |
| 19 | 부팅 실패 시 관리자가 정리해 줄 것으로 기대 | 부분 활성이 남는다 | 실패 경로에 RESET을 직접 넣어라(1.12.11.2) |
14.2.11 체크포인트
- 시스템 모드와 노드 상태의 층위 차이는?
- 모드가 결정하는 것 다섯 가지와, 단일 관리를 권하는 이유는?
- 모드를 파라미터로 두면 안 되는 이유 셋은?
- 부팅 순서의 일곱 계층과 의존 종류 다섯 가지를 말할 수 있는가.
- delay 기반 순서 맞추기가 취약한 이유와 대안은?
- 모든 노드를 생애주기로 만들지 않아도 되는 이유와 절충안은?
- 부팅 실패 네 상황의 대응과, 무한 재시도의 문제는?
- 준비 판정 다섯 방법과, "토픽 발행"이 불충분한 이유는?
- 진단을 준비 판정에 재사용하는 것의 이점은?
- 감시 다섯 층과, 가장 실용적인 층·잊기 쉬운 층은?
- 워치독 다섯 패턴과, 안전 워치독을 SW 밖에 두는 이유는?
- 감시가 부하가 되지 않게 하는 구조는?
- 장애 대응 다섯 가지와, 강등 운전이 가용성에 주는 효과는?
- 강등이 위험을 키우지 않게 하는 통상적 조치는?
- 자동 재시작이 문제를 감추는 경로와 대처는?
- 모드 전환의 위험 다섯 가지와 안전한 절차 여섯 단계는?
- "전환 전 정지"가 대부분의 문제를 없애는 이유는?
- 종료 시 고려할 여섯 항목과, 강제 종료의 손실은?
- 조율자 방식 네 가지와, 중앙 조율자의 위험·대비는?
/cmd_vel에 발행자가 둘 이상일 때 실제로 무슨 일이 일어나는가. 중재의 세 방식과 각각이 맞는 상황은?- 중재에 타임아웃이 없으면 어떤 사고가 나는가. 안전 정지를 중재 밖에 두라는 조언의 근거는?
다음 장 14.3는 이 모든 구성을 실제로 기술하는 도구 — launch의 구조와 substitution을 심층으로 다룬다.