TT Lab
요금제

한국어

시작하기
블로그

블로그

PX4 드론을 쿠버네티스 파드에서 날리기: SITL 사각형 임무와 소스 빌드에서 겪은 여섯 가지

상태: 초안 (2026-10-18), 실측. 측정은 2026-10-17 23:55 부터 2026-10-18 00:45(KST)까지 홈랩 쿠버네티스의 임시 파드에서 했습니다. 노드는 홈랩의 GPU 노드 한 대(AMD Ryzen 7 계열 16스레드)이지만 GPU 는 쓰지 않았습니다. 비행 파드는 시뮬레이터 컨테이너(상한 CPU 3.5코어·메모리 3 GiB)와 MAVSDK 를 돌리는 python:3.12-slim 곁 컨테이너(상한 0.5코어·1 GiB)로, 빌드 파드는 상한 CPU 4코어·메모리 4 GiB 로 띄웠습니다. 판은 미리 만든 이미지 PX4 v1.18.0-beta1-660(61907962c9, 2026-09-11), 소스 빌드 PX4-Autopilot main 93579610c6(2026-10-16), Gazebo Sim 8.15.0(Harmonic), MAVSDK-Python 3.17.4 입니다. 비행은 각각 한 번이므로 표의 값은 한 번 잰 값입니다. 원자료(ULog 두 개, 요약 JSON, 콘솔 로그)는 작업 디렉터리 measure/sim-px4/ 에 있고, 파드는 측정 뒤 모두 지웠습니다.

목차

1. 직관: 진짜 비행 소프트웨어를 가짜 몸에 태운다

SITL(Software In The Loop)은 드론에 올라갈 비행 소프트웨어를 그대로 컴퓨터에서 돌리고, 센서와 모터만 시뮬레이터가 대신하는 방식입니다. PX4 가 보기에는 진짜 기체와 구별이 되지 않으므로, 임무 계획, 비상 동작, 지상국 연결을 기체 없이 시험할 수 있습니다. 1편의 바퀴 로봇이 ROS 2 위에서 움직였다면, 이번에는 비행 제어기 자체가 시험 대상입니다.

2. 이미지 고르기: 바로 날릴 것인가, 고쳐서 날릴 것인가

이미지 압축 크기 받는 시간 들어 있는 것 쓰임
px4io/px4-sitl-gazebo:latest 656 MiB 32.5 s 빌드된 PX4 1.18 + Gazebo Harmonic, 시작 스크립트 바로 날려 보기, 임무·지상국 시험
px4io/px4-dev:v1.18.0-rc1 1.77 GiB 2분 3.9 s Ubuntu 24.04 빌드 도구(시뮬레이션 도구 제외) PX4 코드를 고치고 빌드하기

미리 만든 이미지는 HEADLESS=1 로 화면 없이 띄우면 25 s 안에 「Startup script returned successfully」를 찍었습니다. 개발 이미지는 크기가 세 배 가까운 데다 Gazebo 와 시뮬레이션 의존성을 직접 깔아야 해서, 코드를 고칠 일이 없다면 미리 만든 이미지가 낫습니다.

3. 사각형 임무 비행

시뮬레이터 컨테이너가 MAVLink 를 같은 파드의 localhost:14540 으로 보내므로, 곁 컨테이너의 MAVSDK 가 udpin://0.0.0.0:14540 에서 받습니다. 임무는 이륙 10 m, 북쪽과 동쪽으로 한 변 40 m 인 사각형 꼭짓점 4개(속도 5 m/s, 지나치며 통과), 끝나면 귀환(RTL)·착륙입니다.

corners_ne = [(SIDE, 0), (SIDE, SIDE), (0, SIDE), (0, 0)]
wps = [offset(lat0, lon0, n, e) for n, e in corners_ne]
await drone.mission.set_return_to_launch_after_mission(True)
await drone.mission.upload_mission(MissionPlan([item(a, b) for a, b in wps]))
await drone.action.set_takeoff_altitude(ALT)
await drone.action.arm()
await drone.action.takeoff()

(TT Lab 실험 코드 fly_square.py 에서 발췌. item() 은 MAVSDK 3.x 의 15개 인자 MissionItem 을 만드는 작은 함수입니다.)

한 변 40 m 사각형 임무, ULog 로 다시 그린 비행 위에서 본 경로(왼쪽)와 고도(오른쪽). 8배속. ▶ 누르면 재생 홈 WP1 WP2 WP3 동쪽(m) 북 0 5 10 고도(m) 임무 41.0 s 시각: 이륙 명령 뒤 0–78 s 지금: 이륙 지금: 대기(LOITER) 지금: 임무(사각형) 지금: 귀환·착륙(RTL) 지금: 대기(LOITER) 파란 선은 vehicle_local_position(EKF 추정). 점선은 계획한 사각형입니다.
그림: ULog 로 다시 그린 사각형 임무 비행 경로와 고도
항목 값
이륙 명령 → 9.5 m 도달 12.9 s
임무(AUTO_MISSION) 41.0 s
귀환·착륙(AUTO_RTL) 20.6 s
연결부터 시동 해제까지 79 s
최대 고도 10.03 m
임무 중 고도 오차 평균 / 최대 0.048 / 0.189 m
사각형 경로 이탈(cross-track) 평균 / RMS / p95 / 최대 0.256 / 0.328 / 0.657 / 0.722 m
꼭짓점 최근접 거리(WP1–4) 0.80, 0.70, 0.62, 0.00 m
착륙 위치(홈에서) 0.055 m, 착륙 판정 참
최대 롤 / 피치 19.1° / 18.4°
시뮬레이터 컨테이너 자원 CPU 62.8 s / 경과 215 s(평균 0.29코어), memory.peak 205 MiB

경로 이탈은 EKF 가 추정한 vehicle_local_position 이 홈과 네 꼭짓점을 잇는 선분에서 얼마나 떨어졌는지를 잰 값입니다. 꼭짓점을 0.6–0.8 m 안쪽으로 지나간 것은 「지나치며 통과」 설정과 도착 판정 반경(NAV_ACC_RAD 10 m) 때문에 모서리를 조금 깎아 돈 것으로 봅니다(추정). 그림은 ULog(28 MB)를 표준 라이브러리로 직접 읽어 그렸고, 착륙 위치 0.055 m 와 단계별 시각이 분석 요약과 같게 나왔습니다.

시뮬레이터의 배터리는 60 s 동안 50% 까지 줄도록 설정되어 있어 100% 에서 50%, 16.2 V 에서 15.3 V 로 내려갔습니다. 실제 배터리 소모를 뜻하지 않습니다.

4. 소스 빌드판: 셸 명령만으로 날리기

개발 이미지에서 PX4-Autopilot 을 받아(--depth 1, 서브모듈 포함, 117–121 s, 1.9 GiB) 빌드하고, 이번에는 MAVSDK 없이 PX4 셸 명령만으로 날렸습니다. 셸 입력은 FIFO 로 넣었습니다.

param set NAV_DLL_ACT 0        # 지상국이 없어도 이륙을 거부하지 않게
param set MIS_TAKEOFF_ALT 10
commander takeoff
listener vehicle_local_position -n 1
commander land
항목 값
의존성 설치(Gazebo Harmonic + 시뮬레이션 의존성) 157 s
HEADLESS=1 make px4_sitl gz_x500 PX4_MAKE_ARGS=-j2 빌드 후 기동 182 s
시동 유지 37.4 s
최대 고도 10.04 m
제자리 비행 중 수평 흔들림 최대 0.092 m
실행 중 메모리 anon 156 MiB, gz sim RSS 209 MiB, px4 RSS 21 MiB

commander takeoff 뒤 20 s 에 고도 10.015 m 를 읽었고, commander land 뒤 「Disarmed by landing」으로 끝났습니다. 지상국도 파이썬도 없이 비행 제어기만으로 시험이 됩니다.

5. 겪은 문제 여섯 가지

  1. MAVSDK 4 의 API 변경. 최신 mavsdk 4.0.6 은 네이티브 바인딩으로 바뀌어 mavsdk.mission 을 불러올 수 없었습니다. mavsdk<4(3.17.4)로 고정했습니다. 기존 방식의 후속 패키지 이름은 mavsdk-grpc 입니다.
  2. Gazebo 의존성은 빌드보다 먼저. Gazebo 없이 한 번 구성한 빌드 디렉터리는 CMake 캐시에 「Gazebo simulation dependencies not found」를 남겨, 나중에 Gazebo 를 깔아도 gz_x500 이 실패했습니다. 의존성부터 깔고 빌드합니다.
  3. OpenCV. gz-harmonic 만 깔면 optical_flow 플러그인이 OpenCV 를 찾지 못했습니다. PX4 의 ubuntu.sh 가 적은 시뮬레이션 의존성 목록을 그대로 깔아야 했습니다(패키지 211개, 171 MB 추가, 39 s).
  4. 병렬 빌드 OOM 과 PX4_MAKE_ARGS. 병렬 수를 주지 않으면 ninja 가 노드의 코어 수(16)+2 개를 돌려 메모리 상한 4 GiB 에서 OOMKilled 되었습니다. 그런데 PX4 Makefile 은 -j 를 터미널(ps T)로 읽기 때문에 터미널 없는 nohup 실행에서는 make -j2 가 전달되지 않을 수 있습니다. PX4_MAKE_ARGS=-j2 를 make 변수로 넘겨 해결했습니다.
  5. ULog 경로. 소스 빌드의 로그는 build/px4_sitl_default/rootfs/log/ 가 아니라 build/px4_sitl_default/rootfs/fs/log/<날짜>/*.ulg 에 있었습니다.
  6. 데몬 모드의 출력. px4 -d(데몬)로 띄우면 px4-<모듈> 클라이언트 명령(예: mavlink status)의 출력이 그 명령의 터미널이 아니라 서버 로그로 갑니다. 결과는 px4 로그에서 읽었습니다.

덤으로, ULog 를 pyulog 로 읽을 때 타임스탬프(uint64)끼리 빼면 음수에서 넘칩니다. 실수로 바꾼 뒤 빼야 합니다.

지상국(QGroundControl)은 보통 UDP 14550 에서 기다립니다. 그래서 「SITL 컨테이너의 14550 을 열어야 한다」고 생각하기 쉬운데, 실측은 반대였습니다.

정리하면, 지상국이 14550 을 쥐고 있어야 하고 SITL 쪽에서 14550 을 바인드하거나 게시할 이유는 없습니다. Docker Desktop 에서 -p 14550:14550/udp 로 컨테이너 포트를 게시하면 호스트의 14550 을 컨테이너 쪽이 차지해 지상국이 바인드에 실패할 수 있다는 보고가 있는데, 이 환경(containerd)에서는 재현할 수 없어 확인하지 못했습니다(미실측).

7. 한계

8. 다음 편으로

다음 5편은 다시 탁구 로봇으로 돌아가, 2편의 공 모델 위에서 돌린 고전 제어 기준선의 반환 성공률을 다룹니다.

티티 한마디. 비행 제어기는 진짜, 몸은 가짜예요. 그래서 기체를 부술 걱정 없이 사각형을 몇 번이고 돌 수 있어요. 지상국 포트는 드론이 아니라 지상국이 쥐고 있어야 한다는 것도 잊지 마세요!

참고

모든 링크는 2026-10-18 에 열람해 열리는 것을 확인했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다