Post

[ROS2 실전] Day 3: 실전 배포 - Docker, systemd, OTA 업데이트

[ROS2 실전] Day 3: 실전 배포 - Docker, systemd, OTA 업데이트

서론: 개발 환경과 현장 환경을 일치시킨다

“내 컴퓨터에서는 됐는데”는 로봇 현장에서 통하지 않는다. Docker로 실행 환경을 패키징하면 개발 환경과 현장 환경의 차이를 제거할 수 있다. systemd는 로봇 부팅 시 자동 실행을 보장한다.

1. ROS2 Docker 이미지 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Dockerfile
FROM ros:jazzy-ros-base

# 시스템 의존성
RUN apt-get update && apt-get install -y \
    ros-jazzy-nav2-bringup \
    ros-jazzy-slam-toolbox \
    ros-jazzy-ros2-control \
    && rm -rf /var/lib/apt/lists/*

# 소스 복사 및 빌드
WORKDIR /ros2_ws
COPY src/ src/

RUN . /opt/ros/jazzy/setup.sh && \
    rosdep install --from-paths src --ignore-src -r -y && \
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release \
    && rm -rf build/ log/

# 엔트리포인트
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["ros2", "launch", "robot_bringup", "bringup.launch.py"]
1
2
3
4
5
#!/bin/bash
# docker/entrypoint.sh
source /opt/ros/jazzy/setup.bash
source /ros2_ws/install/setup.bash
exec "$@"

2. Docker Compose로 서비스 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# docker-compose.yml
version: '3.8'

services:
  robot_core:
    image: myorg/robot:1.2.0
    network_mode: host          # ROS2 DDS는 host 네트워크 필요
    privileged: true            # 하드웨어 장치 접근
    volumes:
      - /dev:/dev               # 시리얼·CAN 장치
      - /tmp/.X11-unix:/tmp/.X11-unix  # GUI (개발 시)
      - robot_logs:/ros2_ws/logs
    environment:
      - ROS_DOMAIN_ID=42
      - DISPLAY=$DISPLAY
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0
    restart: unless-stopped

  rosbridge:
    image: myorg/rosbridge:1.0.0
    network_mode: host
    depends_on: [robot_core]
    restart: unless-stopped

volumes:
  robot_logs:
1
2
3
4
5
6
7
8
# 배포 및 실행
docker-compose up -d

# 로그 확인
docker-compose logs -f robot_core

# 업데이트
docker-compose pull && docker-compose up -d

3. 멀티 스테이지 빌드로 이미지 최적화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 빌드 스테이지
FROM ros:jazzy-ros-base AS builder

WORKDIR /ros2_ws
COPY src/ src/

RUN . /opt/ros/jazzy/setup.sh && \
    rosdep install --from-paths src --ignore-src -r -y && \
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release

# 런타임 스테이지 (빌드 도구 제외)
FROM ros:jazzy-ros-base AS runtime

# 런타임 의존성만 설치
RUN apt-get update && apt-get install -y \
    ros-jazzy-nav2-bringup \
    && rm -rf /var/lib/apt/lists/*

# 빌드 결과물만 복사 (src, build 제외)
COPY --from=builder /ros2_ws/install /ros2_ws/install

COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

멀티 스테이지 빌드로 최종 이미지 크기를 40~60% 줄일 수 있다.

4. systemd 서비스 등록

로봇 부팅 시 Docker 컨테이너를 자동 시작한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# /etc/systemd/system/robot.service
[Unit]
Description=Robot ROS2 Stack
After=docker.service network-online.target
Requires=docker.service

[Service]
Type=simple
WorkingDirectory=/opt/robot
ExecStartPre=/usr/bin/docker-compose pull --quiet
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose down
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
# 서비스 등록 및 시작
sudo systemctl daemon-reload
sudo systemctl enable robot.service
sudo systemctl start robot.service

# 상태 확인
sudo systemctl status robot.service
sudo journalctl -u robot.service -f

5. 설정 분리: 이미지와 환경 설정 분리

이미지는 코드만, 현장별 설정은 외부 볼륨으로 주입한다.

1
2
3
4
5
6
7
8
9
10
# docker-compose.yml
services:
  robot_core:
    image: myorg/robot:1.2.0
    volumes:
      # 현장별 파라미터 파일 오버라이드
      - /opt/robot/config:/ros2_ws/install/robot_bringup/share/robot_bringup/config:ro
    environment:
      - ROBOT_MODEL=model_B
      - SITE_ID=warehouse_seoul_01
1
2
3
4
/opt/robot/config/  (현장 서버에만 존재)
  nav2_params.yaml  ← 창고 맵 맞게 튜닝된 값
  map.yaml
  map.pgm

6. OTA 업데이트 전략

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
# /opt/robot/update.sh

set -e

NEW_TAG=$1
COMPOSE_FILE="/opt/robot/docker-compose.yml"

echo "[OTA] 새 이미지 pull: $NEW_TAG"
docker pull myorg/robot:$NEW_TAG

echo "[OTA] 서비스 중단"
systemctl stop robot.service

echo "[OTA] 이미지 태그 업데이트"
sed -i "s|myorg/robot:.*|myorg/robot:$NEW_TAG|" $COMPOSE_FILE

echo "[OTA] 서비스 재시작"
systemctl start robot.service

# 헬스 체크 (30초 내 정상 기동 확인)
for i in $(seq 1 30); do
    if ros2 topic hz /odom --window 1 2>/dev/null | grep -q "Hz"; then
        echo "[OTA] 정상 기동 확인"
        exit 0
    fi
    sleep 1
done

echo "[OTA] 기동 실패, 롤백"
sed -i "s|myorg/robot:$NEW_TAG|myorg/robot:$PREV_TAG|" $COMPOSE_FILE
systemctl restart robot.service
exit 1

7. 헬스체크

1
2
3
4
# Dockerfile에 헬스체크 추가
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
    CMD ros2 topic hz /odom --window 3 2>/dev/null \
        | grep -q "Hz" || exit 1
1
2
3
# 컨테이너 상태 확인
docker inspect --format='' robot_core
# → healthy / unhealthy / starting

8. Day 3 체크리스트

  1. 멀티 스테이지 Dockerfile로 빌드 도구를 제외한 런타임 이미지를 만들었다.
  2. network_mode: host/dev 볼륨 마운트로 하드웨어 접근을 설정했다.
  3. systemd 서비스로 부팅 시 자동 시작과 실패 시 재시작을 설정했다.
  4. 이미지와 현장 설정 파일을 분리해 이미지 재빌드 없이 설정을 교체했다.
  5. OTA 스크립트에 헬스체크와 자동 롤백을 구현했다.

다음 글 예고

Day 4에서는 CI/CD 파이프라인을 다룬다. GitHub Actions에서 빌드·테스트·이미지 빌드·레지스트리 푸시까지 자동화하고, 시뮬레이션 기반 HIL 테스트를 통합하는 방법을 정리한다.

This post is licensed under CC BY 4.0 by the author.