AEGLOSLAB ROS 2 Jazzy 대전

제4부 빌드·패키징·배포

4.8 크로스 컴파일과 arm64

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

로봇의 온보드 컴퓨터는 대개 arm64다 — Jetson·Raspberry Pi·각종 임베디드 보드. 개발 머신은 대개 x86_64다. 이 불일치가 빌드 파이프라인의 난제가 된다.

보드에서 직접 빌드하면 되지 않느냐 — 되기는 한다. 다만 라즈베리파이에서 Nav2를 빌드하면 몇 시간이 걸리고, 메모리가 부족해 종료되고(4.5.5), SD 카드 수명을 단축한다. 그래서 대안을 찾게 된다.

이 장은 각 접근의 실제 비용을 정직하게 비교한다. 결론을 먼저 말하면 — ROS 2의 전통적 크로스 컴파일은 어렵고, 대부분의 팀에게 Docker 다중 아키텍처 빌드가 더 실용적이다.

배우는 것:

  • ROS 2 크로스 컴파일이 어려운 구조적 이유 — 메시지 생성이 걸림돌인 이유.
  • 네 가지 접근 — 타깃 네이티브·QEMU 에뮬레이션·크로스 툴체인·Docker buildx.
  • Docker 다중 아키텍처 빌드 — 실제 절차.
  • 빌드 시간 최적화 — ccache·캐시 마운트.
  • 배포 전략 — 이미지·deb·워크스페이스 복사.
  • 검증 — 크로스 빌드 결과를 어떻게 믿는가.

선수 지식: 4.5(colcon), 4.6(rosdep), 4.7(소스 빌드), Docker 기본.

정직: 크로스 컴파일 도구·절차는 빠르게 변하며, ROS 2 공식 크로스 컴파일 도구의 유지 상태는 시점에 따라 다르다(확인 필요). 아래 Docker 기반 절차는 일반적으로 통용되는 방식이며 이 환경에서 실행 검증하지 않았다(목차 3). 빌드 시간·성능 수치는 인용하지 않는다(3.6.1의 방침).

검증 상태(추가): 130번째 회차(151차 갭 점검)까지 이 장은 실행 검증이 전혀 없었다. 130번째 회차가 ②·④(QEMU 에뮬레이션·Docker buildx — 이 장이 대부분의 팀에 권장하는 경로)를 실제 arm64 빌드로 끝까지 확인해 본문에 (실행 확인) 표기를 처음 추가했고, 그 과정에서 이 장의 "--load 다중 플랫폼 불가" 함정 서술에 반례를 하나 찾았다(→ 아래 정정). ③(크로스 툴체인)은 부분적으로만 진행했다 — 호스트 툴체인 설치는 쉬웠지만 arm64 sysroot 구성 단계에서 실제 장애물 두 가지에 부딪혔고, 이 장이 말하는 rosidl 코드 생성 문제 자체까지는 이번 회차에 도달하지 못했다. 4.8.4·4.8.7을 보라. 131번째 회차(152차 갭 점검)가 이 미완료를 이어받아 실제로 rosidl 코드 생성 크로스 컴파일을 끝까지 실행했고, 이 장의 "①이 핵심 문제다" 서술을 상당 부분 재구성했다(→ 아래 4.8.2·4.8.3 정정). 4.8.2·4.8.3을 보라.

4.8.2 ROS 2 크로스 컴파일이 어려운 이유

일반적인 C++ 프로젝트의 크로스 컴파일은 어렵지 않다. 크로스 툴체인을 지정하고 sysroot를 주면 된다. ROS 2가 어려운 이유는 별도로 있다.

ROS 2 빌드 ① 메시지 코드 생성 생성기가 호스트에서 실행 ② Python 확장 모듈 타깃용 Python 필요 ③ 의존 라이브러리 타깃 아키텍처 sysroot ④ 빌드 중 실행되는 도구 ament · rosidl 호스트용·타깃용 빌드가 모두 필요
걸림돌설명
① 코드 생성 도구rosidl 생성기(1.3.2)가 빌드 중 실행된다. 타깃용으로 빌드된 도구는 호스트에서 실행할 수 없다
② Python 확장rclpy의 C 확장(2.8.2)이 타깃 Python 헤더·라이브러리를 요구
③ sysroot모든 의존 라이브러리의 타깃 아키텍처 버전이 필요
④ 빌드 시 실행 도구ament의 여러 도구가 빌드 과정에서 실행됨

①이 핵심 문제다. 일반적인 해법은 "호스트용 도구를 따로 빌드해 두고 크로스 빌드에서 그것을 쓰게 하는" 것인데, 이 구성이 복잡하고 깨지기 쉽다.

실행 확인(152차, Jazzy) — "①이 핵심 문제다"는 이 절이 말하는 형태로는 재현되지 않았다. rosidl 코드 생성 자체는 크로스 컴파일에서 별도 조치 없이 그대로 작동했다(→ 정정). 4.8.3 ③의 "부분 검증"(151차)을 이어받아, 이번에는 sysroot를 끝까지 조립하고 실제로 colcon build --cmake-args -DCMAKE_TOOLCHAIN_FILE=...을 커스텀 메시지 패키지(.msg 정의 포함)에 실행했다. 결과: 첫 시도에서 그대로 성공했다(Finished my_msgs [3.61s], 별도의 "호스트용 도구 따로 빌드" 작업 없음). 원인을 소스에서 확인하니 — rosidl_generator_c 등 코드 생성기의 실제 진입점이 컴파일된 실행 파일이 아니라 파이썬 스크립트였다(file 확인: "Python script, ASCII text executable"). 이 절의 예시 토대인 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER(프로그램은 sysroot가 아니라 호스트 경로에서 찾음)가 CMake로 하여금 호스트에 이미 설치된 python3와, 그 python3가 실행하는 (역시 호스트에 이미 있는) rosidl 생성기 스크립트를 그대로 쓰게 만들었다 — 로그에 Found rosidl_default_generators: ... /opt/sysroot-arm64/...(패키지 설정은 sysroot에서)와 Found Python3: /usr/bin/python3(실행기는 호스트에서)가 나란히 출력됐다. "코드 생성 도구를 호스트용으로 따로 빌드해야 한다"는 이 절의 서술은 Jazzy의 rosidl 생성기 구현(파이썬 기반)에는 해당하지 않는다 — 개발자가 평소 쓰는 호스트 ROS 2 설치가 이미 "호스트용 도구"였다. 실제로 만든 마찰은 ③(sysroot 준비)이었다 — 아래 4.8.3에서 자세히 다룬다. 생성된 라이브러리(libmy_msgs__rosidl_typesupport_cpp.so 등 8종)는 ELF 헤더로 실제 arm64(EM_AARCH64)임을 확인했다. 원시 로그는 tools/gap_harness/gap152/logs/cross_build_full_detail.log. 목차 3.

견해: 이 복잡도 때문에 "실제 크로스 컴파일"은 대부분의 팀에게 비용이 이득을 넘는다. 아래 네 접근 중 ④(Docker buildx)가 실무에서 가장 널리 쓰이는 이유가 이것이다 — 크로스 컴파일 문제를 에뮬레이션으로 우회한다.

견해에 대한 실행 확인(152차) — 이 견해의 결론(대부분의 팀에게 ④가 정답)은 152차 실측과도 여전히 부합한다. ③이 "핵심 문제(코드 생성)"에서는 뜻밖에 쉬웠지만, sysroot 준비 자체의 마찰(4.8.3을 보라)은 여전히 ④보다 훨씬 컸다 — 이번 회차에 289개 패키지를 개별 내려받아 수동으로 조립해야 했다. "왜 ③이 어려운가"의 정확한 지점이 이 절의 설명과는 다르게 밝혀졌을 뿐, ③ 자체가 ④보다 복잡하다는 결론은 바뀌지 않는다.

4.8.3 네 가지 접근

① 타깃에서 네이티브 빌드

# 보드에 SSH로 들어가서
colcon build --parallel-workers 1 --executor sequential \
  --cmake-args -DCMAKE_BUILD_TYPE=Release
축평가
정확성✅ 가장 확실 — 실제 환경 그대로
설정 복잡도✅ 없음
속도❌ 매우 느림
메모리❌ 부족하기 쉬움(4.5.5)
저장장치 수명❌ SD 카드 마모
CI 적합성❌ 어려움

완화: 스왑 추가, --parallel-workers 1, SSD·NVMe 사용, ccache.

# 스왑 임시 추가 (메모리 부족 대응)
sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile

② QEMU 에뮬레이션

x86_64 머신에서 arm64 바이너리를 에뮬레이션해 실행한다. 크로스 컴파일이 아니라 "느린 네이티브 빌드" 다.

# binfmt 등록 (호스트에서 arm64 바이너리 실행 가능하게)
docker run --privileged --rm tonistiigi/binfmt --install all
축평가
정확성✅ 네이티브와 동일한 빌드 절차
설정 복잡도✅ 낮음
속도⚠️ 느리지만 강력한 x86 머신이면 보드보다 빠를 수 있다
CI 적합성✅ 좋음

③ 크로스 툴체인

실제 크로스 컴파일. CMake 툴체인 파일과 sysroot를 준비한다.

# toolchain-arm64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_SYSROOT /opt/sysroot-arm64)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
colcon build --cmake-args -DCMAKE_TOOLCHAIN_FILE=$PWD/toolchain-arm64.cmake
축평가
정확성⚠️ 미묘한 차이 가능
설정 복잡도❌ 매우 높음(4.8.2의 걸림돌들)
속도✅ 가장 빠름
유지보수❌ 깨지기 쉬움

실행 확인(151차, 부분적) — "설정 복잡도가 매우 높다"를 실제로 시도해 확인했지만, 4.8.2가 지목한 ①(rosidl 코드 생성) 문제 자체까지는 이번 회차에 도달하지 못했다(정직하게 명시). 호스트 툴체인 설치(crossbuild-essential-arm64)는 어렵지 않았다(즉시 성공). 그러나 sysroot를 준비하는 단계에서 이 절이 언급하지 않는 실제 장애물 두 가지에 부딪혔다. ① archive.ubuntu.com에는 arm64 패키지가 없다 — Ubuntu는 amd64·i386만 archive.ubuntu.com에 두고, arm64를 포함한 나머지 아키텍처는 ports.ubuntu.com에 별도로 둔다. dpkg --add-architecture arm64 후 그대로 apt-get update를 실행하면 noble/restricted arm64 Packages 404 Not Found류 오류가 난다 — ports.ubuntu.com을 가리키는 별도 소스 항목을 Architectures: arm64로 명시해야 한다(4.8.4의 Docker 이미지는 arm64 베이스 이미지 자체가 이미 이렇게 구성돼 있어 이 문제를 안 겪는다 — 이 절 ③처럼 호스트에서 직접 sysroot를 만들 때만 나온다). ② 여러 ROS 2 Python 보조 패키지가 arm64로 설치 불가능하다고 나온다 — apt-get install --download-only ros-jazzy-rosidl-default-generators:arm64 등을 시도하면 python3-catkin-pkg-modules:arm64 but it is not installable류 오류가 여럿 난다. 원인을 dpkg-query로 추적하니 python3-catkin-pkg-modules가 Architecture: all인데 Multi-Arch: no였다(foreign이어야 다른 아키텍처의 의존을 만족시킬 수 있다) — rosidl 코드 생성 도구 체인을 구성하는 여러 Python 패키지의 멀티아크 메타데이터가 크로스 아키텍처 의존 해석과 맞지 않는다. 이 두 장애물 자체가 "설정 지옥"이라는 이 절의 평가를 뒷받침하지만, 이것을 우회해 실제로 colcon build --cmake-args -DCMAKE_TOOLCHAIN_FILE=...을 실행하고 4.8.2의 ①(rosidl 생성기가 호스트에서 실행되는 문제)이 정확히 어떻게 나타나는지까지는 이번 회차 예산 안에서 도달하지 못했다(확인 필요, 다음 회차 후보). 원시 로그는 tools/gap_harness/gap151/logs/cross_toolchain_sysroot_apt_failure.log. 목차 3.

실행 확인(152차) — 151차가 남긴 (확인 필요)를 이어받아 sysroot를 끝까지 조립하고, 실제 rosidl 코드 생성 크로스 컴파일까지 도달했다(→ 4.8.2 정정과 연결). ②(Multi-Arch: no 문제)의 우회법은 단순했다 — apt-get install(설치 가능 여부를 전부 검증) 대신 apt-get download(이름으로 단순히 받아오기, 조인트 설치 가능성을 안 따짐)를 쓰면 된다. apt-cache depends --recurse로 추출한 필요 패키지 289개를 전부 apt-get download <pkg>:arm64(실패 시 아키텍처 접미사 없이 재시도 — Architecture: all 패키지는 접미사를 안 받는다) 방식으로 시도하니 289개 전부 성공했다(실패 0건) — Multi-Arch: no는 apt의 설치 시점 의존성 그래프 검증에서만 걸리는 문제였지, 개별 패키지 파일 자체를 받는 데는 아무 지장이 없었다. dpkg-deb -x로 전부 /opt/sysroot-arm64에 풀고(503MB), 이 절의 예시 그대로(CMAKE_SYSROOT·CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER 등) 툴체인 파일을 써서 .msg 정의가 있는 커스텀 메시지 패키지를 colcon build했다. 결과는 4.8.2에 적었듯 첫 시도에 성공 — sysroot만 다 갖추면 그 다음은 뜻밖에 순조로웠다. 이번 회차에 실제로 겪은 "설정 지옥"은 ①·②(sysroot 조립 자체)뿐이었고, 코드 생성 실행 문제는 발생하지 않았다. 원시 데이터는 tools/gap_harness/gap152/data/sysroot_packages_downloaded.txt(289개 목록)·toolchain-arm64.cmake. 목차 3.

④ Docker 다중 아키텍처(buildx)

②를 컨테이너로 포장한 것이며, 결과가 배포 가능한 이미지라는 점이 다르다.

축평가
정확성✅ 좋음
설정 복잡도✅ 낮음
속도⚠️ 에뮬레이션 속도
배포 통합✅ 이미지가 곧 배포물
CI 적합성✅ 가장 좋음

견해: 대부분의 팀에게 ④가 정답이다. 설정이 단순하고, CI에 자연스럽게 들어가며, 배포 아티팩트가 바로 나온다. 속도가 정말 문제일 때만 ③을 검토하되, 그 전에 ccache·캐시 마운트(4.8.5)로 얼마나 개선되는지 먼저 보라. ①은 "빠르게 한 번 시험"할 때, ②는 이미지가 필요 없을 때 쓴다.

4.8.4 Docker 다중 아키텍처 빌드

준비:

# QEMU binfmt 등록 (호스트 1회)
docker run --privileged --rm tonistiigi/binfmt --install arm64

# buildx 빌더 생성
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap

Dockerfile:

FROM ros:jazzy-ros-base AS builder

ARG DEBIAN_FRONTEND=noninteractive
WORKDIR /ws

# ① 의존 해석용으로 package.xml만 먼저 (4.6.6)
COPY src/ /tmp/src/
RUN mkdir -p src && \
    find /tmp/src -name package.xml -exec bash -c \
      'd=src/$(basename $(dirname {})); mkdir -p $d; cp {} $d/' \; && \
    apt-get update && \
    rosdep install --from-paths src -y --ignore-src && \
    rm -rf /var/lib/apt/lists/*

# ② 소스 복사 후 빌드
COPY src/ /ws/src/
RUN . /opt/ros/jazzy/setup.sh && \
    colcon build --merge-install \
      --cmake-args -DCMAKE_BUILD_TYPE=Release \
      --parallel-workers 2

# ③ 런타임 이미지 (빌드 산출물만)
FROM ros:jazzy-ros-core AS runtime
COPY --from=builder /ws/install /ws/install
COPY --from=builder /opt/ros /opt/ros
WORKDIR /ws
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["ros2", "launch", "my_robot", "bringup.launch.py"]

빌드:

# arm64용
docker buildx build --platform linux/arm64 \
  -t registry.example.com/my_robot:1.2.0-arm64 --push .

# 다중 아키텍처 매니페스트 (하나의 태그로 둘 다)
docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/my_robot:1.2.0 --push .

멀티스테이지가 중요하다. 빌드 도구·소스·중간 산출물이 최종 이미지에 남지 않아 이미지가 작아지고 공격면이 줄어든다(13.4).

함정: --push 없이 다중 플랫폼 빌드를 하면 로컬에 저장할 수 없다(확인 필요: buildx 버전에 따라 --load는 단일 플랫폼만 지원). 레지스트리가 필요하다. 로컬 시험은 단일 플랫폼으로:

docker buildx build --platform linux/arm64 --load -t my_robot:test .
docker run --rm --platform linux/arm64 my_robot:test ros2 pkg list | head

실행 확인(151차, Jazzy) — 이 절의 준비·빌드 절차 전체를 실제 arm64로 끝까지 실행했다. docker run --privileged --rm tonistiigi/binfmt --install arm64가 이 환경(중첩 Docker, 샌드박스)에서도 그대로 성공했고(installing: arm64 OK), docker buildx create --use + --bootstrap도 정상 동작했다. 이 절의 Dockerfile 구조 그대로(패키지.xml만 먼저 복사해 rosdep install → 소스 복사 → colcon build → 멀티스테이지) rclcpp·std_msgs에 의존하는 최소 노드 패키지를 docker buildx build --platform linux/arm64 --load로 빌드하니 rosdep install이 ports.ubuntu.com에서 실제 arm64 패키지 인덱스를 받아왔고, colcon build가 QEMU 에뮬레이션 위에서 실제로 컴파일을 수행해(58.7초, 스킵되지 않음) 성공했다. 결과 바이너리를 file이 없는 최소 이미지에서 ELF 헤더를 직접 읽어(오프셋 18~19바이트 e_machine) 확인하니 b7 00 = EM_AARCH64로 실제 arm64 바이너리였다. docker run --platform linux/arm64로 직접 실행하니 rclcpp 노드가 실제로 기동해 로그를 냈다(ros2 run으로 감쌌을 때는 에뮬레이션 오버헤드로 짧은 타임아웃 안에 출력이 안 나온 적이 있었다 — 바이너리를 직접 실행하면 재현됨, 아래에서 정리).

다만 이 함정 서술 자체는 정정이 필요하다(→ 이번 회차 반례로 정정) — "--load는 단일 플랫폼만 지원"은 이 환경에서 재현되지 않았다. 같은 Dockerfile을 --platform linux/amd64,linux/arm64(양쪽 다) + --load로 빌드하니 실패하지 않고 정상 종료했으며, 로컬에 실제 다중 아키텍처 이미지가 저장됐다 — 같은 이미지 태그를 --platform linux/arm64로 실행하면 ELF가 EM_AARCH64, --platform linux/amd64로 실행하면 EM_X86_64로, 하나의 태그가 두 아키텍처를 정확히 구분해 서빙했다. 원인은 Docker 서버(버전 29.5.2)가 containerd 이미지 스토어(driver-type: io.containerd.snapshotter.v1)를 쓰고 있었기 때문으로 보인다 — 이 스토어가 활성화되면 다중 플랫폼 이미지를 로컬 인덱스로 저장할 수 있다. book의 원래 서술이 틀린 것이 아니라 버전·설정에 따라 갈린다 — classic(비-containerd) 이미지 스토어에서는 여전히 책이 설명한 대로 실패할 수 있다(확인 필요: classic 스토어에서의 재현). 원시 로그는 tools/gap_harness/gap151/logs/buildx_arm64_build.log·buildx_multiload.log, ELF 확인 원문은 tools/gap_harness/gap151/data/elf_arch_verification.txt. 목차 3.

4.8.5 빌드 시간 최적화

에뮬레이션 빌드는 느리므로 캐시가 중요하다.

① ccache

RUN apt-get update && apt-get install -y ccache
ENV PATH="/usr/lib/ccache:${PATH}"
ENV CCACHE_DIR=/ccache

RUN --mount=type=cache,target=/ccache \
    . /opt/ros/jazzy/setup.sh && \
    colcon build --merge-install --mixin release
# BuildKit 필요
DOCKER_BUILDKIT=1 docker buildx build ...

② 레이어 캐시 활용 — package.xml만 먼저 복사(4.6.6). 소스 변경 시 의존 설치를 다시 하지 않는다.

③ apt 캐시 마운트

RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt/lists \
    apt-get update && rosdep install --from-paths src -y --ignore-src

④ 베이스 이미지에 의존 미리 설치

의존이 자주 바뀌지 않는다면, 의존이 설치된 중간 이미지를 따로 만들어 두고 CI는 그것을 쓴다.

# my_robot_deps:jazzy — 주기적으로만 재빌드
FROM ros:jazzy-ros-base
COPY deps.repos /tmp/
RUN ... rosdep install ...
# 실제 빌드 — 매 커밋
FROM registry.example.com/my_robot_deps:jazzy
COPY src/ /ws/src/
RUN colcon build ...

견해: ④가 CI 빌드 시간에 가장 큰 영향을 준다. 의존 설치는 대개 빌드의 상당 부분을 차지하는데, 매 커밋마다 반복할 이유가 없다. 4.6.6에서 "CI는 의존 설치 이미지를 쓰라"고 한 것과 같은 논리다.

⑤ 병렬도 조정 — 에뮬레이션에서는 병렬도가 높아도 이득이 적을 수 있다. 실측해서 정한다(4.5.5).

4.8.6 배포 전략

빌드 산출물을 로봇에 배포하는 방법 세 가지다.

① 컨테이너 이미지

# 로봇에서
docker pull registry.example.com/my_robot:1.2.0
docker run --network=host --restart=always ... my_robot:1.2.0
축평가
재현성✅ 최고
롤백✅ 태그 전환
네트워크 설정⚠️ host 모드 필요(3.9.5)
하드웨어 접근⚠️ 디바이스 매핑 필요
이미지 크기⚠️ 대역·저장공간

② deb 패키지

# 빌드 머신에서 deb 생성 (4.9)
# 로봇에서
sudo apt install ./my-robot-package_1.2.0_arm64.deb
축평가
통합✅ OS 패키지 관리와 일관
크기✅ 작음(증분 업데이트)
구축 비용❌ 사내 apt 저장소·bloom 설정 필요

③ 워크스페이스 복사

rsync -avz --delete install/ robot:/opt/my_robot/install/
축평가
단순함✅ 가장 쉬움
의존❌ 시스템 의존은 별도 관리
재현성❌ 낮음
롤백❌ 수동

견해: 개발 중에는 ③, 운영에서는 ① 또는 ② 가 흔한 조합이다. ③은 빠른 반복에 좋지만 "로봇에 무엇이 설치돼 있는지" 추적이 안 되어 운영에는 부적합하다. ③으로 배포한 로봇이 여러 대가 되면 반드시 문제가 생긴다 — 어느 로봇이 어느 버전인지 알 수 없게 된다(14.9).

함정: ③을 쓸 때 --symlink-install로 빌드한 워크스페이스를 복사하면 깨진다(4.3.5). 배포용은 심링크 없이 빌드해야 한다.

4.8.7 검증

크로스 빌드 결과를 어떻게 믿는가.

# ① 아키텍처 확인
file install/my_pkg/lib/my_pkg/my_node
# ELF 64-bit LSB pie executable, ARM aarch64, ...

# ② 동적 링크 확인 (타깃에서)
ldd /ws/install/my_pkg/lib/my_pkg/my_node | grep 'not found'

# ③ 실제 실행
docker run --rm --platform linux/arm64 my_robot:test \
  bash -c 'source /ws/install/setup.bash && ros2 pkg list | wc -l'

# ④ 테스트 실행 (에뮬레이션에서)
docker run --rm --platform linux/arm64 my_robot:test \
  bash -c 'source /opt/ros/jazzy/setup.bash && cd /ws && colcon test && colcon test-result --verbose'

④가 중요하다. 에뮬레이션 환경에서 테스트를 실행하면 아키텍처 의존 버그(엔디언·정렬·부동소수점 차이)를 검출할 수 있다.

실행 확인(151차) — ①·③은 실제로 확인했고, ②·④는 다루지 못했다. file 명령이 없는 최소 이미지에서는 ELF 헤더를 직접 읽어(오프셋 18~19바이트) 같은 결론에 도달했다 — arm64 빌드는 b7 00(EM_AARCH64), amd64는 3e 00(EM_X86_64). docker run --platform linux/arm64로 rclcpp 노드를 실제 기동해 로그까지 확인했다(③). ldd로 링크 누락 확인(②)과 colcon test(④)는 이번 회차 범위 밖이었다(확인 필요, 다음 회차 후보). 자세한 수치는 4.8.4를 보라. 목차 3.

아키텍처 차이가 실제로 문제가 되는 경우:

문제설명
char의 부호x86에서는 signed, ARM에서는 unsigned가 기본
구조체 정렬패딩 차이(1.3.4와 연결)
부동소수점미세한 반올림 차이
SIMD 명령SSE ↔ NEON 이식
원자 연산메모리 모델 차이(2.9.4)

함정: char의 부호 차이가 조용한 버그를 만든다. x86에서 개발한 코드가 ARM에서 다르게 동작하는 전형적 원인이며, 컴파일 경고도 안 난다. 명시적으로 int8_t·uint8_t를 쓰는 습관이 방어가 된다.

# 부호를 명시적으로 고정 (이식성)
add_compile_options(-fsigned-char)

에뮬레이션이 검출하지 못하는 것:

항목이유
실시간 성능에뮬레이션은 속도가 완전히 다르다
하드웨어 접근GPIO·CAN·카메라
GPU·가속기CUDA·NPU
열·전력 특성물리 환경

따라서 실기 시험은 여전히 필요하다. 크로스 빌드는 "빌드가 되는가"를 빠르게 확인하는 수단이지 실기 검증의 대체가 아니다.

4.8.8 함정 카탈로그

#함정증상대처
1보드에서 전체 빌드OOM·수 시간스왑·병렬도 제한·Docker 빌드
2binfmt 미등록exec format errortonistiigi/binfmt --install
3다중 플랫폼 + --load저장 불가레지스트리 push 또는 단일 플랫폼
4멀티스테이지 미사용이미지 비대·공격면builder/runtime 분리
5캐시 미활용CI 빌드 시간 폭증ccache·레이어·의존 이미지
6--symlink-install로 빌드 후 복사 배포대상에서 깨짐심링크 없이 빌드
7char 부호 가정조용한 동작 차이int8_t·-fsigned-char
8에뮬레이션 성능으로 실기 판단잘못된 결론실기 측정 필수
9rsync 배포로 운영버전 추적 불가이미지·deb
10크로스 툴체인을 성급히 도입설정 지옥Docker 방식 먼저
11하드웨어 의존 테스트를 에뮬레이션에서통과했는데 실기 실패HIL 시험(14.6)
12타깃 아키텍처 확인 생략잘못된 바이너리 배포file 명령

4.8.9 체크포인트

  • ROS 2 크로스 컴파일이 일반 C++ 프로젝트보다 어려운 구조적 이유 네 가지를 말할 수 있는가. 핵심 걸림돌은?
  • 네 가지 접근의 정확성·복잡도·속도·CI 적합성을 비교할 수 있는가. 대부분의 팀에게 권장되는 것은 무엇이며 왜인가.
  • QEMU 에뮬레이션이 "크로스 컴파일"이 아니라 무엇인지 정확히 말할 수 있는가.
  • Docker 다중 아키텍처 빌드의 준비 단계와, 멀티스테이지가 주는 이점 두 가지는?
  • 빌드 시간을 줄이는 다섯 가지 방법 중 CI에서 가장 효과가 큰 것은 무엇이며 왜인가.
  • 배포 전략 세 가지의 장단을 비교하고, rsync 방식이 운영에 부적합한 이유를 말할 수 있는가.
  • 크로스 빌드 결과를 검증하는 네 단계를 말할 수 있는가.
  • 아키텍처 차이로 생기는 문제 다섯 가지 중, 컴파일 경고 없이 조용히 발생하는 것은 무엇인가.
  • 에뮬레이션이 검출하지 못하는 것 네 가지를 들 수 있는가. 그래서 크로스 빌드의 역할을 어떻게 규정해야 하는가.

다음 장 4.9는 빌드한 결과를 공식적으로 릴리스하는 절차다 — bloom·rosdistro·빌드팜을 거쳐 apt install ros-jazzy-*가 되기까지, 그리고 사내 빌드팜을 세우는 선택지.