AEGLOSLAB ROS 2 Jazzy 대전

제14부 시스템 설계·품질·운영

14.4 launch 심층 II — 대규모 구성

14.4.1 이 장에서 배우는 것 / 선수 지식

작은 시스템의 launch 파일은 한 개면 충분하다. 그런데 노드가 서른 개가 되고, 시뮬과 실기를 모두 지원하고, 로봇이 두 종류이고, 여러 대를 동시에 실행해야 하면 — launch 파일 자체가 유지보수 대상이 된다.

이 장은 그 규모에서의 구성 전략을 다룬다. 목표는 "바꿀 곳이 한 군데" 다. 센서 하나를 추가할 때 launch 파일 다섯 개를 고쳐야 한다면 구조가 잘못된 것이다.

배우는 것:

  • 계층 구조 — 어떻게 나눌 것인가.
  • 인자 전파 — 위에서 아래로.
  • 설정 파일 조직 — YAML을 어떻게 둘 것인가.
  • 변형 다루기 — 로봇·환경·모드.
  • 다중 로봇 — 네임스페이스 전략.
  • 재사용 도구 — 공통 함수·패턴.
  • 검증 — launch를 시험하기.
  • 병합 의미론 실측(14.4.11) — 딥 머지, 그리고 순서에 우선하는 구체성 규칙.

선수 지식: 14.3(launch 구조), 4.2(패키지 구성), 1.1.6(네임스페이스), 14.2(오케스트레이션), 14.9(플릿 — 함께 읽으면 좋다).

정직: 이 장은 구성 전략을 다루며, 유일한 정답을 제시하지 않는다. 팀 규모·로봇 종류·운영 방식에 따라 적합한 구조가 다르다. API 세부는 배포판 의존이다(확인 필요). 이 환경에서 검증하지 않았다(목차 3). 다만 파라미터 병합 규칙은 실물로 확인했다(실행 확인 — 14.4.11).

14.4.2 계층 구조

최상위: robot.launch.py 전체 시스템 하드웨어 계층 drivers · ros2_ control 상태 추정 tf · odom · 측위 인지 항법 · 조작 응용 · 미션 도구 · 시각화 센서별 launch
계층책임
최상위전체 조합·모드 선택
서브시스템한 기능 묶음
개별 컴포넌트노드 하나 또는 소수
원칙설명
각 계층은 자기 인자만하위로 전달
하위는 상위를 모른다재사용 가능
기본값 제공단독 실행 가능
하나의 책임14.1의 원칙 적용

견해: "각 서브시스템 launch가 단독으로 실행 가능해야 한다"가 좋은 규율이다. ros2 launch my_robot_bringup perception.launch.py만으로 인지 파이프라인이 기동되면, 부분 시험과 디버깅이 쉬워진다. 기본값을 잘 주는 것이 그 조건이다.

견해: 패키지 분리도 함께 고려하라(4.2). <robot>_description(URDF)·<robot>_bringup(launch·config)·기능별 패키지로 나누는 것이 관행이며, bringup 패키지가 launch와 설정의 집합소가 된다.

전형적 파일 구성:

파일역할
robot.launch.py전체 — 인자로 모드 선택
hardware.launch.py드라이버·ros2_control
localization.launch.pytf·측위
perception.launch.py인지
navigation.launch.pyNav2
manipulation.launch.pyMoveIt
sim.launch.py시뮬 전용(10.2)
rviz.launch.py시각화

14.4.3 인자 전파

상위 인자를 하위로 넘기는 것이 반복 작업이 된다.

# 공통 인자를 모아 두는 패턴
COMMON_ARGS = ['use_sim_time', 'robot_name', 'namespace', 'log_level']

def pass_args(names):
    return {n: LaunchConfiguration(n) for n in names}.items()

IncludeLaunchDescription(
    PythonLaunchDescriptionSource(PathJoinSubstitution([
        FindPackageShare('my_bringup'), 'launch', 'navigation.launch.py'])),
    launch_arguments=pass_args(COMMON_ARGS + ['map_file']))
문제대처
인자 전달 누락헬퍼 함수·목록
이름 불일치규약·상수
기본값 중복한 곳에 정의
인자 폭증그룹화·설정 파일로

견해: 인자가 열 개를 넘으면 설정 파일로 옮기는 것을 검토하라. launch 인자는 "실행할 때마다 바뀌는 소수"에 적합하고, 나머지는 YAML이 낫다. use_sim_time·robot_name·mode 정도가 인자, 나머지는 파일이 흔한 경계다.

견해: SetParameter로 전역 파라미터를 주면 인자 전파가 크게 준다(14.3.5). use_sim_time을 최상위 GroupAction에서 한 번 설정하면 하위 전부에 적용되므로, 모든 launch에 인자로 넘길 필요가 없다. 다만 범위를 인지해야 한다.

함정: 하위 launch가 자기 기본값을 가지면 상위 값과 충돌할 수 있다. 상위가 넘기지 않으면 하위 기본값이 쓰이는데, 그것이 상위 의도와 다를 수 있다. 핵심 인자는 반드시 명시적으로 전달하는 규율이 안전하다.

14.4.4 설정 파일 조직

config/ common/ 모든 로봇 공통 robot_a/ 로봇별 env/ 환경별 (공장 · 실험실) sim/ 시뮬 전용 덮어쓰기
조직 방식특성
기능별 파일nav2.yaml·controllers.yaml
로봇별 디렉터리변형 관리
계층적 병합공통 + 덮어쓰기
단일 대형 파일단순하나 관리 어려움

견해: "공통 + 덮어쓰기" 구조가 여러 로봇·환경을 다룰 때 유리하다. 파라미터 목록의 뒤가 앞을 덮어쓰는 성질(14.3.8)을 이용해, [common.yaml, robot_a.yaml, sim_overrides.yaml] 순으로 주면 된다.

parameters=[
    PathJoinSubstitution([cfg, 'common', 'nav2.yaml']),
    PathJoinSubstitution([cfg, LaunchConfiguration('robot_name'), 'nav2.yaml']),
    {'use_sim_time': LaunchConfiguration('use_sim_time')},
]

함정: 덮어쓰기에는 순서보다 강한 규칙이 있다 — 지정자의 구체성이다(실행 확인, 14.4.11.2). 중첩 구조의 부분 덮어쓰기는 기대대로 동작하지만(딥 머지, 14.4.11.1), 나중에 준 /**: 파일은 앞선 /노드이름: 항목을 덮지 못한다. sim_overrides.yaml을 /**:로 쓸 계획이라면 14.4.11.4를 먼저 보라. ros2 param get으로 최종 값을 검증하는 것이 확실하다.

견해: 설정 파일에 주석으로 "왜 이 값인가"를 남기라. 튜닝된 값의 근거가 없으면 나중에 아무도 바꾸지 못하거나, 반대로 함부로 바꾼다. 측정 근거·날짜·담당을 적으면 지식이 유지된다.

견해: 설정을 버전 관리하고 로봇별 차이를 diff로 볼 수 있게 하라. 로봇 A는 되는데 B는 안 될 때, 설정 diff가 첫 번째 단서다. 파일이 잘 조직되어 있으면 이 비교가 쉽다.

14.4.5 변형 다루기

같은 코드로 여러 구성을 지원한다.

변형 축예
실행 환경mock · sim · real(10.4.3)
로봇 모델A형·B형·세대
하드웨어 옵션센서 유무·팔 유무
운영 환경공장·실험실·전시
모드항법·조작·점검
# 환경 축 — 조건으로 분기
Node(package='my_driver', executable='real_driver',
     condition=IfCondition(PythonExpression(
         ["'", LaunchConfiguration('backend'), "' == 'real'"])))

IncludeLaunchDescription(sim_launch,
     condition=IfCondition(PythonExpression(
         ["'", LaunchConfiguration('backend'), "' == 'sim'"])))

견해: 변형 축이 셋을 넘으면 OpaqueFunction이 명확하다(14.3.6). 조건 조합이 복잡해지면 PythonExpression이 읽을 수 없게 되므로, 실행 시점에 값을 읽어 Python으로 구성하는 편이 유지보수에 낫다.

견해: 변형을 "조합 폭발"로 만들지 말라. 모든 조합을 지원하려 하면 시험이 불가능해진다. 실제로 쓰는 조합만 정의하고 목록화하는 것이 실용적이다 — 그 목록이 곧 시험 대상이 된다(14.6).

함정: URDF·설정·launch가 각자 다른 방식으로 변형을 다루면 어긋난다. 5.6.6·10.4.3에서 강조한 단일 출처 원칙을 여기서도 지켜야 한다 — 하나의 인자(backend)가 xacro·launch·설정을 함께 결정하도록.

14.4.6 다중 로봇

다중 로봇 전략 ① 네임스페이스 분리 한 도메인 ② 도메인 분리 ROS_DOMAIN_ID ③ 완전 분리 별도 네트워크 + 브리지
전략특성
① 네임스페이스서로 볼 수 있음·트래픽 공유
② 도메인 분리격리·통신하려면 브리지
③ 완전 분리최대 격리·복잡
# 네임스페이스 전략
GroupAction([
    PushRosNamespace(LaunchConfiguration('robot_name')),
    SetParameter('use_sim_time', LaunchConfiguration('use_sim_time')),
    IncludeLaunchDescription(robot_launch, launch_arguments=...),
])
주의설명
tf 프레임 접두robot1/base_link(5.1.8)
절대 이름 토픽네임스페이스 무시됨(1.1.5)
전역 토픽/clock·/tf는 공유 여부 결정
파라미터 YAML네임스페이스 포함
디스커버리 부하로봇 수에 비례(3.3.4)

함정: /tf를 여러 로봇이 공유하면 프레임 이름이 충돌한다. 각 로봇의 base_link가 같은 이름이면 tf 트리가 손상된다(5.1.4). 프레임에 접두사를 붙이거나(robot1/base_link), /tf를 네임스페이스 안으로 넣어야 한다.

함정: 코드에 절대 토픽 이름(/scan)이 하드코딩되어 있으면 네임스페이스가 적용되지 않는다(1.1.5). 다중 로봇을 지원하려면 노드가 상대 이름을 쓰도록 작성해야 하며, 이것은 코드 수준의 준비다.

견해: 시뮬에서는 네임스페이스, 실기에서는 도메인 분리가 흔한 조합이다. 시뮬은 한 프로세스에서 여러 로봇을 다루므로 네임스페이스가 자연스럽고, 실기는 로봇마다 별도 컴퓨터이므로 도메인 분리가 격리와 부하 면에서 유리하다(14.9).

견해: 로봇 수가 늘면 디스커버리가 병목이 된다(3.3.4·3.4). 수십 대 규모에서는 Discovery Server나 도메인 분리를 고려해야 하며, 이것은 launch 구성보다 상위의 아키텍처 결정이다.

14.4.7 재사용 도구

공통 패턴을 함수로 만든다.

# my_bringup/launch_utils.py (패키지 내 파이썬 모듈)
def robot_node(package, executable, name, cfg_files, extra=None, **kw):
    """표준 인자·파라미터·리매핑을 적용한 Node 생성."""
    params = list(cfg_files)
    params.append({'use_sim_time': LaunchConfiguration('use_sim_time')})
    if extra:
        params.append(extra)
    return Node(package=package, executable=executable, name=name,
                parameters=params,
                arguments=['--ros-args', '--log-level',
                           LaunchConfiguration('log_level')],
                output='screen', **kw)
재사용 대상예
노드 생성 헬퍼공통 인자 적용
인자 선언 묶음표준 인자 세트
include 헬퍼경로·인자 전파
조건 표현자주 쓰는 조건
enclave 지정보안(13.2.6)

견해: 헬퍼 함수가 일관성을 보장하는 가장 값싼 수단이다. 로그 레벨·use_sim_time·enclave를 노드마다 수동으로 넣으면 빠뜨린다. 한 함수를 거치게 하면 전부에 적용된다.

함정: launch 유틸리티 모듈을 import하려면 패키지가 설치되어 있어야 한다. setup.py·CMakeLists.txt에 포함시키고, 개발 중에는 --symlink-install을 쓰면 편리하다(4.5).

견해: 다만 추상화를 과하게 하면 launch를 읽어도 무엇이 실행되는지 모르게 된다. launch의 가치 하나는 "구성을 읽을 수 있다" 는 것이므로, 헬퍼는 얇게 유지하는 것이 좋다.

14.4.8 검증

launch도 시험 대상이다.

시험방법
구문·구성 오류import·기술 시점 실행
인자 목록--show-args
노드 기동launch_testing(14.6)
파라미터 적용ros2 param dump 비교
변형 조합정의된 조합 전부
종료정상 종료되는가
# 최소 시험: 기술이 오류 없이 만들어지는가
def test_launch_description_builds():
    from my_bringup.launch.robot_launch import generate_launch_description
    ld = generate_launch_description()
    assert ld is not None

견해: "launch 파일이 오류 없이 기동되는가"를 CI에 넣는 것만으로도 가치가 크다(4.10). launch 오류는 대개 현장에서 발견되는데, 경로 오타·인자 누락은 CI에서 검출할 수 있다.

견해: 정의된 변형 조합 목록을 CI에서 전부 기동해 보라(14.4.5). 조합이 다섯 개면 다섯 번 실행하고 노드가 다 기동됐는지 확인하는 것으로, "이 조합은 기동되지 않는다"를 배포 전에 알 수 있다.

함정: CI에서 하드웨어가 없어 실패하는 노드가 있다. mock 백엔드(6.2.8)나 시뮬 조합만 시험하거나, 기동 확인 대상을 선별해야 한다.

14.4.11 파라미터 병합 의미론을 실제로 확인했다 — 실측

14.4.4는 "뒤가 앞을 덮어쓰는 성질을 이용해 [common, robot_a, sim_overrides] 순으로 주면 된다"고 적고, 중첩 구조의 부분 덮어쓰기가 기대대로 되는지 확인해야 한다(확인 필요)고 남겼다. 확인했다(실행 확인, Jazzy).

방법: automatically_declare_parameters_from_overrides=True로 노드를 실행하고 최종 파라미터를 전부 출력했다. 파일 순서·지정자·명령줄을 조합해 각각의 결과를 봤다.

14.4.11.1 부분 덮어쓰기는 딥 머지다 — 기대대로 동작한다

A.yaml이 top_a·shared·sub.x·sub.y를, B.yaml이 shared·sub.y만 지정한 경우:

구성결과
A만top_a=1 shared=from_A sub.x=10 sub.y=20
B만shared=from_B sub.y=99
A → Btop_a=1 유지됨 · shared=from_B · sub.x=10 유지됨 · sub.y=99

B가 언급하지 않은 키는 그대로 남는다. 파일 전체 교체가 아니라 키 단위 병합이며, 중첩 구조도 부분 덮어쓰기가 된다(sub.y만 바꿔도 sub.x는 유지). 14.4.4가 권한 "공통 + 덮어쓰기" 구조가 의도대로 작동한다.

순서를 뒤집으면(B → A) shared=from_A·sub.y=20이 되어 뒤가 앞을 덮는 성질도 확인된다.

14.4.11.2 그러나 순서보다 강한 것이 있다 — 지정자의 구체성

여기서 이 절의 핵심이 나온다. common.yaml(/**:)·robot_a.yaml(/pnode:)·wild_after.yaml(/**:)을 이 순서대로 줬다.

파일 순서mode의 최종 값
공통(/**:) → 로봇별(/pnode:)robot_a — 로봇별이 우선한다
로봇별(/pnode:) → 공통(/**:)common — 뒤가 우선한다
공통 → 로봇별 → 와일드카드(가장 나중)robot_a ← 나중 파일이 졌다
공통 → 와일드카드(로봇별 없이)wildcard_later — 순서대로
공통 → 로봇별 → 노드 이름으로 지정한 나중 파일specific_later

가장 나중 파일인데도 /**:이면 앞선 /노드이름:에 우선하지 못한다(3회 반복 동일). 로봇별 파일을 빼면 같은 파일이 우선하므로, 순서 규칙이 깨진 것이 아니라 구체성이 먼저 적용되는 것이다.

규칙은 두 단계다.

  1. 지정자의 구체성이 먼저 — /노드이름: > /**:
  2. 같은 구체성 안에서 뒤가 앞을 덮는다

14.4.4의 "뒤가 앞을 덮는다"는 ②에만 해당한다.

14.4.11.3 가장 실무적인 함정 — 명령줄 -p도 진다

구성결과
/**: 파일 + -p mode:=from_clifrom_cli — 명령줄이 우선한다
/노드이름: 파일 + -p mode:=from_clirobot_a — 명령줄이 진다

명령줄 -p는 와일드카드와 같은 수준으로 취급된다. 노드 이름으로 지정된 YAML 항목은 명령줄로 덮을 수 없다.

이것이 현장에서 피해가 크다. "급하니 -p로 값 하나만 바꿔 보자"가 조용히 무시된다 — 오류도 경고도 없고, 값이 아무 표시 없이 안 바뀐다. 11.1.14.3이 측정한 "YAML 노드 이름 불일치"와 증상이 똑같지만 원인은 정반대다: 그때는 이름이 안 맞아서 무시됐고, 이번엔 이름이 맞아서 명령줄에 우선한다.

진단: -p가 안 먹으면 어느 파일이 그 노드를 이름으로 지정하고 있는지 찾으라. ros2 param get으로 실제 값을 보는 것이 먼저다(1.9.10).

14.4.11.4 sim_overrides.yaml을 쓸 때의 결론

14.4.4의 예시 순서 [common, robot_a, sim_overrides]는 sim_overrides가 무엇으로 지정되는지에 달렸다.

sim_overrides.yaml의 지정자로봇별 파일의 값을 덮는가
/**: (use_sim_time 같은 전역 설정에 자연스러운 선택)덮지 못한다
/노드이름:덮는다

use_sim_time처럼 전역으로 주고 싶은 값은 /**:이 맞지만, 그 키를 로봇별 파일이 이름으로 지정해 두었다면 시뮬 오버라이드가 먹지 않는다. 전역 오버라이드를 확실히 하려면 명령줄이 아니라 같은 구체성(노드 이름)으로 지정하거나, 로봇별 파일에서 그 키를 빼야 한다.

14.4.11.5 확인하지 못한 것

  • launch 파일의 parameters=[...] 목록에서도 같은 규칙인지는 직접 확인하지 않았다 — 이 실험은 --ros-args --params-file로 했다(확인 필요). rcl의 같은 경로를 쓰므로 같을 것으로 보이나 확인하지 않았다.
  • /** 와 /* 등 중간 구체성(14.3.12)이 섞였을 때의 순위는 측정하지 않았다(확인 필요).
  • 배열·중첩 딕셔너리의 부분 갱신(원소 단위)은 시험하지 않았다.

14.4.9 함정 카탈로그

#함정증상대처
1단일 거대 launch유지 불가계층화
2하위가 상위를 앎재사용 불가인자로만
3인자 전파 누락기본값 적용헬퍼·명시 전달
4인자 폭증관리 어려움설정 파일로
5하위 기본값 충돌의도와 다름핵심은 명시 전달
6설정 병합 규칙 미확인예상 밖 값param dump 검증
7설정 근거 미기록변경 불가주석
8변형 조합 폭발시험 불가조합 목록화
9변형 축이 여러 곳에 흩어짐어긋남단일 출처
10다중 로봇 tf 충돌트리 손상프레임 접두
11절대 토픽 하드코딩NS 무효상대 이름
12로봇 수에 따른 디스커버리 부하지연·불안정도메인·서버
13헬퍼 과추상화읽을 수 없음얇게
14launch 시험 없음현장 발견CI 기동 시험
15CI에서 하드웨어 의존실패mock·선별
16나중 /**: 파일로 로봇별 값을 덮으려 함안 덮인다 — 구체성이 순서에 우선한다같은 구체성으로 지정(14.4.11.2)
17-p로 급히 값 변경노드 이름 지정 항목은 조용히 안 바뀜ros2 param get으로 확인(14.4.11.3)

14.4.10 체크포인트

  • launch 계층 구조의 세 층과 네 원칙을 말할 수 있는가.
  • "서브시스템 launch가 단독 실행 가능해야 한다"의 이점은?
  • 인자 전파의 네 문제와 대처를 들 수 있는가.
  • 인자와 설정 파일의 경계를 어디에 두는가.
  • SetParameter가 인자 전파를 줄이는 방식과 그 주의점은?
  • 하위 launch의 기본값이 만드는 문제와 규율은?
  • 설정 파일 조직 네 방식과, 여러 로봇에 유리한 구조는?
  • 설정 병합에서 검증이 필요한 이유와 방법은?
  • 설정 주석에 무엇을 남겨야 하는가.
  • 변형 축 다섯 가지와, 조합 폭발을 피하는 방법은?
  • 변형에서 단일 출처 원칙이 왜 중요한가.
  • 다중 로봇 세 전략과 각각의 특성은?
  • 다중 로봇에서 tf와 토픽 이름의 두 함정과 대처는?
  • 시뮬과 실기에서 다른 전략을 쓰는 이유는?
  • 재사용 헬퍼의 이점과, 과추상화의 위험은?
  • launch 검증 여섯 가지와, CI에 넣을 최소 시험은?
  • 파라미터 파일 병합은 전체 교체인가 키 단위 병합인가. 중첩 구조는 어떤가(14.4.11.1).
  • "뒤가 앞을 덮는다"가 성립하지 않는 경우는 언제인가(14.4.11.2).
  • 명령줄 -p가 조용히 무시되는 조건은(14.4.11.3).

다음 장 14.5은 시험의 세계로 들어간다 — 단위 시험과 모의 그래프로 로봇 소프트웨어를 검증하는 방법.