임베디드를 좋아하는 조금 특이한 개발자?

[ROS2] Docker를 통한 ROS2 개발 및 배포 본문

ROS

[ROS2] Docker를 통한 ROS2 개발 및 배포

Gordon_ 2026. 8. 2. 11:46
반응형

- 개발 환경

WSL2 (Ubuntu 24.04 LTS)

Docker : 29.6.2

ROS2 : Jazzy

- 소스 코드

https://github.com/MainForm/ros2_jazzy_tutorial_with_docker

 

GitHub - MainForm/ros2_jazzy_tutorial_with_docker

Contribute to MainForm/ros2_jazzy_tutorial_with_docker development by creating an account on GitHub.

github.com


1. 개요

  ROS2를 Native 환경에서 개발하다 보면 너무 복잡한 의존성 때문에 프로젝트를 유지하거나 배포하는데 큰 어려움을 격는 경우가 많습니다. 이러한 문제를 해결하기 위해 Docker를 사용하여 필요한 패키지를 Dockerfile에 명시하여 자동적으로 개발 환경이 구성된 이미지를 빌드하고 Docker Compose를 통해 Docker Container를 목적에 맞추어 실행할 수 있습니다. 그래서 이번 포스트에서는 Docker를 통해 ROS2를 통해 어떻게 개발 할수 있고 어떻게 배포 할 수 있는지 소개해 드립니다.

 

2. Visual studio code Extension 설치

2.1. Remote Development

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack

 

Remote Development - Visual Studio Marketplace

Extension for Visual Studio Code - An extension pack that lets you open any folder in a container, on a remote machine, or in WSL and take advantage of VS Code's full feature set.

marketplace.visualstudio.com

  WSL 및 Docker Continer에 Visual Studio Code로 접속하여 편하게 개발하기 위함입니다.

2.2. Docker

https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker

 

Docker - Visual Studio Marketplace

Extension for Visual Studio Code - Makes it easy to create, manage, and debug containerized applications.

marketplace.visualstudio.com

 

 

3. 프로젝트 구성

 프로젝트를 처음부터 직접 구성하는 것은 매우 힘든일이므로 Git repo를 Clone하고 분석하는 방법으로 하도록 하겠습니다.

# 개발 환경 Clone
git clone https://github.com/MainForm/ros2_jazzy_tutorial_with_docker.git ./ros2_dev

# 프로젝트 폴더로 이동
cd ros2_dev

3.1. Dockerfile

  Dockerfile은 Docker Image을 어떻게 구성할 것인지를 명시한 파일입니다.

  저는 ros에서 제공해주는 공식 이미지를 기반으로 이미지를 구성했습니다.

# ROS 2 Jazzy의 기본 ROS 환경을 기반 이미지로 사용
FROM ros:jazzy-ros-base

# 이후 RUN 명령을 Bash로 실행
SHELL ["/bin/bash", "-c"]

# colcon 등 ROS 2 C++ 패키지 빌드에 필요한 도구 설치
RUN apt-get update \
    && apt-get install -y \
        ros-dev-tools

# 컨테이너 내부 ROS 2 workspace 경로
WORKDIR /workspace

# 기반 이미지에 포함된 UID/GID 1000의 ubuntu 사용자로 실행
# 일반적인 Linux 호스트 사용자와 파일 소유권이 일치함
USER ubuntu

3.2. docker-compose.yaml

# Talker와 Listener를 각각 별도 컨테이너로 실행
name: cpp-pubsub

# 노드 컨테이너가 공유하는 실행 환경
x-ros-common: &ros-common
  # 동일한 Dockerfile로 만든 공통 이미지 사용
  build: .
  image: cpp-pubsub:local

  # 소스와 빌드 결과를 모든 서비스가 동일하게 공유
  volumes:
    - ./ros2_ws:/workspace

  # 두 노드가 동일한 ROS 2 DDS domain 사용
  environment:
    ROS_DOMAIN_ID: 2
    RCUTILS_LOGGING_BUFFERED_STREAM: 1
    HOME: /home/ubuntu

  # 호스트 및 다른 host network ROS 2 노드와 통신
  network_mode: host

  # Fast DDS shared-memory transport를 컨테이너 사이에서 공유
  ipc: host

services:
  # 소스를 실시간으로 수정하고 빌드하는 개발 컨테이너
  # dev profile을 지정했을 때만 생성되므로 기본 노드 실행에는 포함되지 않음
  dev:
    <<: *ros-common
    profiles:
      - dev
    command: bash
    stdin_open: true
    tty: true

  # Talker와 Listener 실행 전에 workspace를 한 번 빌드하는 일회성 서비스
  workspace-build:
    <<: *ros-common
    command:
      - bash
      - -lc
      - colcon build --symlink-install

  # /topic에 std_msgs/msg/String 메시지 발행
  talker:
    <<: *ros-common
    depends_on:
      workspace-build:
        condition: service_completed_successfully
    command:
      - bash
      - -lc
      - source install/setup.bash && exec ros2 run cpp_pubsub talker

  # /topic의 std_msgs/msg/String 메시지 구독
  listener:
    <<: *ros-common
    depends_on:
      workspace-build:
        condition: service_completed_successfully
    command:
      - bash
      - -lc
      - source install/setup.bash && exec ros2 run cpp_pubsub listener

 

위 docker compose는 내용이 많으므로 각 부분별로 설명 드리도록 하겠습니다.

3.2.1. x-ros-common 필드

# 노드 컨테이너가 공유하는 실행 환경
x-ros-common: &ros-common
  # 동일한 Dockerfile로 만든 공통 이미지 사용
  build: .
  image: cpp-pubsub:local

  # 소스와 빌드 결과를 모든 서비스가 동일하게 공유
  volumes:
    - ./ros2_ws:/workspace

  # 두 노드가 동일한 ROS 2 DDS domain 사용
  environment:
    ROS_DOMAIN_ID: 2
    RCUTILS_LOGGING_BUFFERED_STREAM: 1
    HOME: /home/ubuntu

  # 호스트 및 다른 host network ROS 2 노드와 통신
  network_mode: host

  # Fast DDS shared-memory transport를 컨테이너 사이에서 공유
  ipc: host

  x-ros-common은 4개의 서비스에 공통으로 적용할 설정을 모아 둔 확장 필드입니다. Docker Compose에서는 정의되지 않은 필드가 사용되면 오류가 발생할 수 있지만, 이름이 x-로 시작하는 필드는 확장 필드로 취급되어 Compose의 실행 대상에서 제외됩니다.  이 확장 필드에 공통 설정을 정의한 뒤 YAML의 anchor와 alias 문법을 사용하면, 여러 서비스에서 동일한 설정을 반복해서 작성하지 않고 재사용할 수 있습니다.

 

- 확장 필드 관련 내용

https://docs.docker.com/reference/compose-file/extension/

 

Extensions

Define and reuse custom fragments with extensions in Docker Compose

docs.docker.com

 

- volumes

  Docker에서는 Host PC와 Container 간 파일을 공유 하기 위해서 volume를 사용합니다. 특히, Container의 경우 Container가 삭제 된다면 Container내 파일들도 동시에 삭제됩니다. 그러므로 Container와 상관없이 삭제되면 곤란한 파일들을 volume 설정으로 공유할 수 있습니다. 이 프로젝트의 경우 ROS2의 소스코드를 Container 공유하여 실제 ROS2의 빌드 및 노드 실행은 Container에서 하도록 하였습니다.

 

  https://docs.docker.com/engine/storage/bind-mounts/

 

Bind mounts

Using bind mounts

docs.docker.com

 

- environment

- network_mode : host

  컨테이너가 호스트의 네트워크 환경을 그대로 사용하도록 설정합니다. 이를 통해 컨테이너 내부의 ROS 2 노드가 호스트 또는 같은 물리적 네트워크에 있는 다른 ROS 2 노드를 쉽게 발견하고 통신할 수 있습니다. 단, ROS_DOMAIN_ID가 같아야 합니다.

- ipc: host

  컨테이너가 호스트의 IPC 공간을 공유하도록 설정합니다. 이를 통해 같은 호스트에서 실행되는 여러 컨테이너의 Fast DDS가 동일한 공유 메모리 영역을 사용하여 데이터를 더 효율적으로 전달할 수 있습니다.

 

3.2.2. Service 필드

3.2.2.1. dev 서비스

# 소스 코드를 실시간으로 수정하고 빌드하기 위한 개발용 컨테이너
dev:
  # ROS 2 컨테이너의 공통 설정 적용
  <<: *ros-common

  # 기본 docker compose up 실행에서는 제외하고,
  # dev 프로필을 지정한 경우에만 실행
  profiles:
    - dev

  # 컨테이너 시작 시 ROS 2 노드 대신 Bash 실행
  command: bash

  # 대화형 Bash 환경을 유지하여 컨테이너가 종료되지 않게 하고,
  # Visual Studio Code에서 실행 중인 컨테이너에 접근할 수 있도록 설정
  stdin_open: true
  tty: true

 

  해당 dev 서비스는 Visual Studo Code로 Container에 접속하여 개발하기 위한 서비스입니다. 

 

3.2.2.2. build 서비스

# Talker와 Listener 실행 전에 ROS 2 workspace를 빌드하는 일회성 서비스
workspace-build:
  # ROS 2 컨테이너의 공통 설정 적용
  <<: *ros-common

  # Bash 로그인 셸에서 colcon build 명령 실행
  command:
    - bash
    - -lc
    - colcon build --symlink-install

 

 해당 build 서비스는 단순하게 일회성으로 talker와 listener 노드가 실행되기 위해 ros2를 빌드하기 위한 서비스 입니다.

 

 

3.2.2.3. talker 서비스 와 listenr 서비스

# /topic에 std_msgs/msg/String 메시지를 발행하는 Talker 서비스
talker:
  # ROS 2 컨테이너의 공통 설정 적용
  <<: *ros-common

  # workspace-build 서비스가 성공적으로 완료된 후 실행
  depends_on:
    workspace-build:
      condition: service_completed_successfully

  # 빌드된 workspace 환경을 적용한 후 Talker 노드 실행
  # exec를 사용하여 ros2 프로세스가 컨테이너의 메인 프로세스가 되도록 설정
  command:
    - bash
    - -lc
    - source install/setup.bash && exec ros2 run cpp_pubsub talker


# /topic의 std_msgs/msg/String 메시지를 구독하는 Listener 서비스
listener:
  # ROS 2 컨테이너의 공통 설정 적용
  <<: *ros-common

  # workspace-build 서비스가 성공적으로 완료된 후 실행
  depends_on:
    workspace-build:
      condition: service_completed_successfully

  # 빌드된 workspace 환경을 적용한 후 Listener 노드 실행
  # exec를 사용하여 ros2 프로세스가 컨테이너의 메인 프로세스가 되도록 설정
  command:
    - bash
    - -lc
    - source install/setup.bash && exec ros2 run cpp_pubsub listener

 

 

3.3. ros2_ws 폴더

  ROS2를 실행하기 위한 예제가 작성되었습니다. 해당 예제는 ROS2에서 제공하는 기본 예제를 사용하였습니다.

 

https://docs.ros.org/en/jazzy/Tutorials.html

 

4. ROS2 개발 방법

# dev service 실행
docker compose up -d dev

 

위 과정까지 따라 왔다면, 새로운 Visual Studio Code가 열리고 dev container에 자동으로 접속 될 것입니다.

 

이제 해당 Visual Studo Code에서 평소대로 개발하듯이 개발이 가능합니다. 

source /opt/ros/jazzy/setup.bash

colcon build

source ./install/setup.bash

ros2 run cpp_pubsub talker

 

※주의)

  새로운 패키지를 설치할때 Container 내 apt 패키지 매니져로 설치한다면 Container 재 실행시 해당 패키지을 재설치해야할 수 있습니다. 그러므로 새로운 패키지를 설치해야한다면 Dockerfile 내에서 추가해주어야 합니다.

 

 

5. ROS2 노드 배포

  이제 개발이 완료 되었다면 이제 해당 노드를 자동적으로 실행하도록 할 수있습니다. 

  

  2개의 터미널을 실행하여 각 2개의 명령어를 별개로 싱행하면 ros2 node가 정상적으로 실행되는 것을 확인할 수 있습니다.

 

docker compose up talker
docker compose up listener

 

※주의)

  해당 두 노드는 서로 다른 컨테이너에서 실행되고 있음을 인지하고 있어야합니다.

반응형