# ROS2 Humble용 Docker 이미지 활용

#### 개요

ROS2 Humble을 다루기 위해서는 여러 의존성 라이브러리와 툴이 필요하며, 시스템 환경이 달라지면 설정 과정도 복잡해진다. 이를 간소화하기 위해 Docker 컨테이너 환경을 활용하는 방법이 많이 사용된다. Docker 이미지를 이용하면, 한 번 구성해 둔 ROS2 Humble 개발 환경을 다른 사용자와 손쉽게 공유할 수 있고, OS 배포판 차이로 인한 호환성 문제도 최소화할 수 있다.

#### Docker 환경의 장점

* 일관된 개발 환경
  * 모든 개발 참여자가 동일한 Docker 이미지를 사용하면 ROS2 버전, 의존성 라이브러리 버전 등이 표준화되어, 환경 편차로 인한 문제를 줄일 수 있다.
* 간편한 배포 및 확장
  * 이미 구성된 컨테이너를 이미지 형태로 공유하면, 새로 환경을 세팅할 때 더 이상 복잡한 설정 단계를 반복할 필요가 없어진다.
* 호스트 환경 오염 최소화
  * 호스트 운영체제에 직접 ROS2 Humble을 설치하는 대신 Docker 컨테이너 안에 격리된 상태로 구성하므로, 호스트 환경에 미치는 영향을 최소화하고 충돌을 방지할 수 있다.

#### 사전 준비

Docker 설치

* 호스트 운영체제(예: Ubuntu, Windows, macOS)에 Docker Engine이 설치되어 있어야 한다.
* 설치 여부 확인:

```
docker --version
```

Docker 권한 설정(리눅스 환경인 경우)

* 일반 사용자 권한으로 Docker를 사용할 수 있도록 $docker$ 그룹에 현재 사용자를 추가하거나, $sudo$ 권한으로 Docker를 이용해야 한다.
* 일반 사용자 권한으로 Docker를 사용하고 싶다면 다음과 같이 설정할 수 있다.

```
sudo usermod -aG docker $USER
```

변경 후에는 로그아웃했다가 다시 로그인해야 한다.

#### 기본 Docker 이미지 선택

ROS2 Humble 이미지는 공식적으로 여러 가지 형태로 제공된다. 예를 들어, 다음과 같은 태그를 가진 이미지를 사용할 수 있다.

* `ros:humble-ros-core`
* `ros:humble-ros-base`
* `ros:humble-ros1-bridge`
* `ros:humble-perception`
* ...

이 중 가장 가벼운 형태의 이미지는 `ros:humble-ros-core`이며, 기본 ROS2 기능만 포함하고 있다. 프로젝트 규모나 필요 기능에 따라 다른 태그의 이미지를 선택할 수 있다.

#### Docker 이미지 다운로드

공식 Docker Hub에서 ROS2 Humble용 이미지를 내려받으려면 다음과 같이 명령을 실행한다.

```
docker pull ros:humble-ros-base
```

이 명령은 `ros:humble-ros-base`라는 이름의 이미지를 호스트로 다운로드한다.

#### 컨테이너 실행과 기본 설정

다운로드가 완료되면, 다음과 같은 명령을 통해 컨테이너를 실행하고 쉘에 진입할 수 있다.

```
docker run -it --name ros2_humble_dev ros:humble-ros-base /bin/bash
```

* `-it`: 인터랙티브 모드로 실행
* `--name`: 컨테이너에 별칭을 지정
* `ros:humble-ros-base`: 사용할 Docker 이미지 이름
* `/bin/bash`: 컨테이너의 쉘에 진입

컨테이너 쉘에 진입하면 ROS2 Humble이 이미 설치된 상태이다. `$ ros2 -h` 명령으로 ROS2 명령어를 확인해 볼 수 있다.

#### GUI 애플리케이션 실행

ROS2 환경에서는 RViz, rqt 등 GUI 툴을 사용하는 경우가 많다. Docker 컨테이너에서 GUI 애플리케이션을 사용하기 위해서는 다음과 같이 X forwarding 혹은 다른 그래픽 환경 연동을 해주어야 한다.

**X11 Forwarding 방식**

호스트 측에서 X 서버를 구동하고, 컨테이너 실행 시 X11 소켓을 볼륨 마운트하거나 네트워크 포트를 열어 GUI를 forwarding한다. 예를 들어, Linux 환경에서 다음과 같이 설정할 수 있다.

```
xhost +local:docker
docker run -it \
    --env DISPLAY \
    --env="QT_X11_NO_MITSHM=1" \
    --volume /tmp/.X11-unix:/tmp/.X11-unix:rw \
    ros:humble-ros-base
```

* `--env DISPLAY`: 컨테이너 내부에서 사용할 디스플레이 환경 변수를 호스트와 동일하게 설정
* `/tmp/.X11-unix` 볼륨: GUI 통신용 소켓 파일을 공유

컨테이너 내부에서 GUI 애플리케이션을 실행하면 호스트의 디스플레이를 통해 출력된다.

#### Dockerfile을 통한 커스텀 이미지 작성

ROS2 Humble 이미지를 그대로 사용하는 것만으로는 부족할 수 있다. 예를 들어, 아래와 같은 추가 요구사항이 있을 수 있다.

* 빌드 툴(예: colcon, cmake)의 특정 버전
* ROS1 브리지 패키지
* 자주 사용하는 유틸리티 툴

이 경우에는 별도의 `Dockerfile`을 작성하여 필요한 패키지를 미리 설치한 다음, 커스텀 이미지를 만들어두면 편리한다.

```
FROM ros:humble-ros-base

# 빌드 도구 설치
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    python3-colcon-common-extensions \
 && rm -rf /var/lib/apt/lists/*
```

위 예시는 `ros:humble-ros-base` 이미지를 기반으로 빌드 도구를 추가 설치하는 Dockerfile의 간단한 예시이다.

#### Docker 이미지 빌드

작성한 Dockerfile이 있는 디렉터리에서 아래 명령을 실행하면 새 이미지를 빌드할 수 있다.

```
docker build -t my_ros2_humble .
```

* `-t my_ros2_humble`: 빌드 후 생성될 이미지의 이름
* `.`: 현재 디렉터리에 있는 Dockerfile을 사용

이미지가 정상적으로 빌드되면, `docker images` 명령에서 `my_ros2_humble`라는 이름의 이미지를 확인할 수 있다.

#### 컨테이너 관리 흐름 예시

아래는 Docker 컨테이너를 기반으로 ROS2 Humble 개발 환경을 구성하는 전반적인 흐름을 간단히 나타낸 예시 다이어그램이다.

{% @mermaid/diagram content="flowchart TD
A\["Docker 이미지 선택/빌드"]
B\["컨테이너 실행"]
C\["ROS2 개발 및 빌드"]
D\["토픽, 서비스 통신 테스트"]
E\["ROS2 패키지 배포"]

```
A --> B --> C --> D --> E" %}
```

#### 컨테이너 유지보수와 업데이트

Docker 컨테이너는 실행 도중에 내부 환경을 수정할 수도 있으며, 이후 해당 변경 사항을 영구적으로 보관하기 위해 이미지를 새로 만들 수도 있다. 여기서는 몇 가지 유지보수와 업데이트 방법을 다룬다.

**컨테이너 재접속**

이미 실행 중인 컨테이너에 다시 들어가서 작업하려면 다음과 같은 명령을 사용할 수 있다.

```
docker exec -it ros2_humble_dev /bin/bash
```

* `ros2_humble_dev`: 재접속하려는 컨테이너의 이름 또는 ID

**수정 사항 반영**

컨테이너 안에서 새로운 패키지를 설치하거나 설정 파일을 편집하는 등 변경 사항이 생긴 경우, 컨테이너를 종료하기 전에 이미지를 새로 만들어두면 이후에 같은 상태로 빠르게 재사용할 수 있다.

컨테이너를 종료하기 전, 다른 터미널에서 다음과 같이 실행하면 컨테이너의 현재 상태를 기반으로 새로운 이미지를 생성한다.

```
docker commit ros2_humble_dev my_ros2_humble_updated
```

* `ros2_humble_dev`: 변경이 적용된 컨테이너의 이름
* `my_ros2_humble_updated`: 새로 생성할 이미지 이름

이 명령을 실행하면, 새 이미지는 `$ docker images`로 확인할 수 있고, 다음부터는 `my_ros2_humble_updated`라는 이미지로 컨테이너를 실행하면 된다.

#### 볼륨 마운트로 데이터 영구 보관

ROS2 프로젝트를 진행하다 보면 다양한 로그, 빌드 아티팩트, 소스 코드 등이 Docker 컨테이너 내부에 존재하게 된다. 컨테이너가 삭제되면 이 데이터도 함께 삭제되므로, 호스트 디렉터리를 컨테이너 내부에 마운트하는 방식을 자주 사용한다.

예를 들어, 호스트의 `~/ros2_workspace` 폴더를 컨테이너 내부 `/workspace` 경로로 마운트하려면 다음과 같은 명령을 사용한다.

```
docker run -it \
    --name ros2_humble_dev \
    -v ~/ros2_workspace:/workspace \
    ros:humble-ros-base \
    /bin/bash
```

이렇게 하면 컨테이너 내부 `/workspace` 경로에 저장된 파일이 호스트에도 그대로 남으므로, 컨테이너를 지우거나 다시 만들어도 빌드 결과와 로그를 보존할 수 있다.

#### 네트워크 설정

ROS2는 DDS(데이터 분산 서비스) 프로토콜을 기반으로 노드 간 통신이 이루어진다. Docker 컨테이너 환경에서 네트워크를 다룰 때는 다음 사항을 유의해야 한다.

**Bridge 네트워크**: 기본적으로 Docker는 컨테이너를 브리지 네트워크에 연결한다. 동일 호스트에서 여러 컨테이너가 실행될 경우, ROS2 통신이 원활하게 이뤄지도록 각 컨테이너의 DDS 포트를 열어두거나, 같은 사용자 정의 브리지 네트워크를 사용하도록 설정해야 한다.

**host 네트워크**: 개발 및 디버깅 목적이라면 네트워크 모드를 `host`로 설정해 호스트와 동일한 네트워크 스택을 쓰도록 할 수도 있다. 예:

```
docker run -it --network host ros:humble-ros-base
```

단, 여러 컨테이너가 동시에 실행되는 경우 충돌이 발생할 수도 있으므로 주의가 필요하다.

#### Multi-stage 빌드 예시

프로젝트가 커지면 Docker 이미지를 빌드하는 속도와 이미지 크기가 부담될 수 있다. 이를 줄이기 위해 Multi-stage 빌드를 적용할 수 있다. 예를 들어, ROS2 소스 컴파일 단계를 별도의 빌드 스테이지에서 진행한 뒤, 최종적으로 필요한 파일만 담는 방식이다.

```
FROM ros:humble-ros-base AS builder

# ROS2 패키지 소스 복사 및 빌드
WORKDIR /tmp/ws
COPY . /tmp/ws
RUN colcon build --symlink-install

# 최종 스테이지
FROM ros:humble-ros-base
COPY --from=builder /tmp/ws/install /opt/ros2_install

ENV ROS2_WS=/opt/ros2_install
```

이 방식은 중간 빌드 결과물을 “builder” 스테이지에서만 유지하고, 최종 스테이지에는 필요한 파일만 복사해서 이미지 용량을 줄이는 효과가 있다.

#### Docker Compose를 통한 멀티 컨테이너 구성

ROS2 시스템은 여러 노드가 상호 통신하는 분산 구조가 일반적이다. Node를 여러 컨테이너에 나누어 배포하고자 할 때, Docker Compose를 사용하면 간단한 설정으로 컨테이너를 일괄 관리할 수 있다. 아래는 예시 `docker-compose.yml` 파일이다.

```yaml
version: '3'
services:
  talker:
    image: ros:humble-ros-base
    container_name: ros2_talker
    command: bash -c "source /opt/ros/humble/setup.bash && ros2 run demo_nodes_cpp talker"

  listener:
    image: ros:humble-ros-base
    container_name: ros2_listener
    command: bash -c "source /opt/ros/humble/setup.bash && ros2 run demo_nodes_cpp listener"
```

* `talker`와 `listener` 두 컨테이너가 각각 ROS2 노드를 실행
* `$ docker-compose up` 명령으로 동시에 실행 가능

이처럼 Compose를 사용하면 멀티 컨테이너 환경에서 ROS2 노드를 쉽게 띄웠다가 내릴 수 있어, 복잡한 시스템 구성 관리가 수월해진다.

#### GPU 연동(NVIDIA Docker)

이미지 처리, 자율 주행 등 GPU 가속이 필요한 ROS2 응용에서는 NVIDIA Docker 플러그인을 활용하여 컨테이너에서 GPU 리소스를 접근하도록 설정할 수 있다.

```
docker run -it --gpus all ros:humble-ros-base
```

* `--gpus all` 옵션을 통해 컨테이너에서 호스트 GPU를 인식 가능

[NVIDIA 컨테이너 툴박스](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) 등을 통해 CUDA, cuDNN 같은 라이브러리를 연동해 쓸 수도 있다.

#### 크로스 컴파일(ARM 등) 환경 구축

임베디드 디바이스나 소형 컴퓨팅 보드(예: Raspberry Pi 등)에서 ROS2 Humble을 구동해야 하는 경우가 많다. 이때 직접 ARM 아키텍처용 이미지를 빌드하거나, x86(데스크톱) 환경에서 ARM 용 바이너리를 만들어 전달하는 과정을 거쳐야 한다. Docker를 사용하면 이러한 크로스 컴파일 환경을 좀 더 쉽게 설정할 수 있다.

멀티 아키텍처 빌드: Docker Buildx 기능을 사용하여 x86 호스트에서 ARM용 이미지를 빌드할 수 있다. 예:

```
docker buildx build --platform linux/arm64 -t my_ros2_humble_arm .
```

CMake toolchain 파일:

* ROS2 패키지들을 컴파일할 때, `$CMAKE_TOOLCHAIN_FILE` 변수를 지정하면 타겟 아키텍처에 맞춰 빌드가 가능한다.
* Dockerfile 예시에서 `$CMAKE_TOOLCHAIN_FILE`을 ENV 변수로 등록 후, `colcon build` 시점에 넘길 수도 있다.

#### 디버깅 및 개발 툴 연동

컨테이너 내부에서 통합 개발환경(IDE)이나 디버거를 활용하여 ROS2 노드를 디버깅할 수 있다.

VSCode Remote Containers:

* VSCode에 “Remote - Containers” 익스텐션을 설치하면, Docker 컨테이너 내에서 직접 코드를 편집하고 디버깅을 진행할 수 있다.
* `.devcontainer/devcontainer.json` 파일을 활용하면 컨테이너 환경 설정, 확장, 패키지 설치 등을 자동화할 수 있다.

CLI 디버거:

* `$gdb` 혹은 `$ lldb` 등을 활용해 컨테이너 내부에서 프로세스를 디버깅할 수도 있다.
* 필요하다면 Dockerfile에 `gdb` 패키지를 설치해두어야 한다.

#### 빌드 캐시 최적화

ROS2 패키지는 종속성이 많은 편이고, Docker 이미지를 자주 재빌드해야 할 수 있다. 빌드 속도를 개선하기 위해 다음 방법을 활용한다.

의존성 먼저 설치:

* Dockerfile에서 자주 변하지 않는 의존성 설치 단계를 상단에 배치해 캐시를 최대한 활용한다.
* 예를 들어, ROS2와 관련된 패키지 설치를 먼저 하고, 프로젝트 소스 코드를 복사하는 단계를 그 뒤에 배치한다.

계층 최소화:

* Dockerfile에서 `RUN` 지시자를 최소화하고 여러 패키지 설치를 한 번에 처리한다.
* 이미지 크기를 줄이기 위해 중간에 `rm -rf /var/lib/apt/lists/*` 등을 수행한다.

#### CI/CD 파이프라인에 Docker 적용

지속적 통합(Continuous Integration) 환경에서 Docker 이미지를 활용하면, 테스트 환경을 매번 깨끗한 상태로 재현할 수 있어 유용하다.

GitLab CI:

* `.gitlab-ci.yml`에서 `image: ros:humble-ros-base` 등을 사용해 CI 파이프라인이 실행될 컨테이너 이미지를 지정할 수 있다.
* 또는, CI 단계에서 별도로 Docker를 설치하고, 자신이 관리하는 커스텀 이미지를 풀(pull) 받아서 사용 가능한다.

GitHub Actions:

* GitHub Actions 워크플로에서 `jobs.<job_id>.container.image` 옵션을 통해 특정 Docker 이미지를 사용하도록 설정할 수 있다. 예:

```yaml
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    container:
      image: ros:humble-ros-base
    steps:
      - uses: actions/checkout@v2
      - name: Build
        run: colcon build
      - name: Test
        run: colcon test
```

* 빌드 결과 아티팩트 업로드
  * 테스트 후 빌드한 ROS2 패키지나 로그 파일 등을 아티팩트로 업로드하여, 다른 단계나 사용자와 결과를 공유할 수 있다.

#### Podman 등 Docker 대안

일부 환경이나 보안 요구사항에 따라 Docker 대신 Podman, LXD 등 다른 컨테이너 기술을 사용할 수도 있다. 대체로 Dockerfile을 그대로 사용 가능하며, 명령어 사용 방식에 약간의 차이가 있을 수 있다.

Podman: 무거운 데몬 프로세스 없이 rootless 환경 운영이 가능 예:

```
podman build -t my_ros2_humble .
podman run -it --name ros2_dev my_ros2_humble
```

LXD: 전통적인 리눅스 컨테이너(LXC) 기반이며, 가상 머신처럼 더 넓은 호스트 통합이 필요할 때 사용

#### 성능 고려사항

컨테이너는 가상 머신보다는 오버헤드가 훨씬 적지만, 그래도 호스트 사용 대비 성능 차이가 발생할 수 있다. 주로 고려해야 할 항목은 다음과 같다.

* 네트워크 지연
  * ROS2 DDS 통신에서 트래픽이 많을 경우, 컨테이너 네트워크 인터페이스를 통해 발생하는 지연을 주의 깊게 살펴봐야 한다.
  * `--network host` 모드를 사용할 때와 일반 브리지 모드를 사용할 때 성능 차이가 날 수 있다.
* 리소스 제한
  * CPU 코어 수, 메모리 용량 등 컨테이너별 리소스 제한이 걸려 있으면 빌드나 런타임 성능에 영향을 준다.
  * Docker Desktop(Windows, macOS) 사용 시, Docker 설정에서 CPU, 메모리, 스왑 크기를 적절히 조정해야 한다.

#### 에러 트러블슈팅 팁

컨테이너 환경에서 ROS2를 다루다 보면 여러 형태의 에러가 발생할 수 있다. 아래는 일반적인 트러블슈팅 팁이다.

* bashrc 설정 누락
  * `$ source /opt/ros/humble/setup.bash`를 실행하지 않아서 `ros2` 명령어가 인식되지 않는 경우가 많다.
  * Dockerfile 혹은 컨테이너 시작 스크립트에 자동으로 소싱하도록 설정한다.
* 권한 문제
  * /dev/ttyUSB0 같은 시리얼 포트, USB 카메라 등의 하드웨어 리소스를 컨테이너 내부에서 액세스하려면 권한 문제가 발생할 수 있다.
  * `--device` 옵션으로 디바이스를 전달하거나, `--privileged` 모드를 적용해보는 방법이 있다.
* 환경 변수 충돌
  * 컨테이너 안에서 여러 버전의 ROS가 섞인 경우, `$AMENT_PREFIX_PATH` 등이 꼬여 빌드/실행 오류가 날 수 있다.
  * 불필요한 소스 명령을 제거하거나, 사용 중인 ROS2 버전만 확실히 소스하도록 관리한다.

#### 컨테이너 오케스트레이션(Kubernetes 등)

ROS2 시스템은 여러 노드가 분산되어 상호작용하는 아키텍처로 구성될 때가 많다. 이를 더욱 대규모로 확장하고, 자동 배포/관리하려면 Kubernetes(쿠버네티스) 같은 컨테이너 오케스트레이션 플랫폼을 사용할 수 있다.

* ROS2용 Helm 차트
  * Helm을 활용하면 ROS2 노드(파드)들이 서로 통신하기 위한 설정 및 리소스 할당을 쉽게 템플릿화할 수 있다.
  * 예: `chart/values.yaml`에 이미지, 환경 변수, 포트 등을 정의해 두고, 필요에 따라 확장/축소 가능한다.
* 서비스 디스커버리(DDS 포트 매핑)
  * 쿠버네티스는 기본적으로 포트 기반 서비스 디스커버리를 사용하지만, ROS2의 DDS는 다중 포트 및 멀티캐스트를 사용하는 경우가 많다.
  * Flannel, Calico 등 CNI 플러그인을 사용해 파드 간 멀티캐스트를 허용하거나, ROS2가 UDP 유니캐스트 모드를 쓰도록 설정하는 방법을 고려해야 한다.
* 로깅 및 모니터링
  * Kubernetes 환경에서는 노드 로그, DDS 트래픽 등을 중앙 집중 방식으로 모니터링할 수 있다.
  * 예: Fluentd, Prometheus, Grafana 등을 조합하여 ROS2 노드 동작 상태를 시각화할 수 있다.

#### 컨테이너 보안 고려사항

ROS2 애플리케이션이 실 환경에서 동작해야 한다면, 컨테이너 보안도 중요하다.

* 최소 권한 원칙
  * Dockerfile에서 불필요한 패키지를 설치하지 말고, 앱 실행에 꼭 필요한 유틸리티만 설치한다.
  * `USER ros` 등 일반 계정으로 컨테이너 프로세스를 실행하여, root 권한을 최소화한다.
* 이미지 스캐닝
  * 바이너리, 라이브러리에 알려진 취약점이 있는지 점검하기 위해 이미지 스캐너(예: Trivy, Clair 등)를 활용한다.
* 비밀정보 관리
  * 패스워드, 토큰 등 민감한 정보는 Docker 이미지를 빌드할 때 직접 넣지 않고, 런타임 환경 변수나 Kubernetes Secret 등을 통해 전달하는 편이 안전한다.

#### Private Registry 사용

공개 Docker Hub를 쓰기 어려운 기업/기관 환경에서는 사설 레지스트리를 구축해 ROS2 Humble 이미지를 운영할 수도 있다.

* 사설 레지스트리 배포
  * Docker Registry를 자체 서버에 설치하고, `$ docker push my_registry:5000/my_ros2_humble` 같은 명령으로 이미지를 푸시한다.
  * 다른 사용자는 `$ docker pull my_registry:5000/my_ros2_humble` 명령으로 동일 이미지를 받을 수 있다.
* 인증/접근 제어
  * 기본적으로 레지스트리는 인증 없이 열려 있을 수 있으므로, TLS 인증서 설정과 사용자 인증을 함께 구성해야 안전한다.

#### 에필로그

ROS2 Humble용 Docker 환경은 개발자나 사용자 모두에게 편리한 방식으로 ROS2를 배포하고 실행할 수 있게 도와준다. 위에서 언급한 방법과 주의점을 잘 활용하면, 프로젝트 규모에 맞춘 확장성과 안정적인 운영이 가능해진다.
