상태: 초안 (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. 직관: 진짜 비행 소프트웨어를 가짜 몸에 태운다
- 2. 이미지 고르기: 바로 날릴 것인가, 고쳐서 날릴 것인가
- 3. 사각형 임무 비행
- 4. 소스 빌드판: 셸 명령만으로 날리기
- 5. 겪은 문제 여섯 가지
- 6. MAVLink 는 14550 을 듣지 않는다
- 7. 한계
- 8. 다음 편으로
- 참고
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 을 만드는 작은 함수입니다.)
| 항목 | 값 |
|---|---|
| 이륙 명령 → 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. 겪은 문제 여섯 가지
- MAVSDK 4 의 API 변경. 최신 mavsdk 4.0.6 은 네이티브 바인딩으로 바뀌어
mavsdk.mission을 불러올 수 없었습니다.mavsdk<4(3.17.4)로 고정했습니다. 기존 방식의 후속 패키지 이름은 mavsdk-grpc 입니다. - Gazebo 의존성은 빌드보다 먼저. Gazebo 없이 한 번 구성한 빌드 디렉터리는 CMake 캐시에 「Gazebo simulation dependencies not found」를 남겨, 나중에 Gazebo 를 깔아도
gz_x500이 실패했습니다. 의존성부터 깔고 빌드합니다. - OpenCV.
gz-harmonic만 깔면 optical_flow 플러그인이 OpenCV 를 찾지 못했습니다. PX4 의ubuntu.sh가 적은 시뮬레이션 의존성 목록을 그대로 깔아야 했습니다(패키지 211개, 171 MB 추가, 39 s). - 병렬 빌드 OOM 과
PX4_MAKE_ARGS. 병렬 수를 주지 않으면 ninja 가 노드의 코어 수(16)+2 개를 돌려 메모리 상한 4 GiB 에서 OOMKilled 되었습니다. 그런데 PX4 Makefile 은-j를 터미널(ps T)로 읽기 때문에 터미널 없는nohup실행에서는make -j2가 전달되지 않을 수 있습니다.PX4_MAKE_ARGS=-j2를 make 변수로 넘겨 해결했습니다. - ULog 경로. 소스 빌드의 로그는
build/px4_sitl_default/rootfs/log/가 아니라build/px4_sitl_default/rootfs/fs/log/<날짜>/*.ulg에 있었습니다. - 데몬 모드의 출력.
px4 -d(데몬)로 띄우면px4-<모듈>클라이언트 명령(예:mavlink status)의 출력이 그 명령의 터미널이 아니라 서버 로그로 갑니다. 결과는 px4 로그에서 읽었습니다.
덤으로, ULog 를 pyulog 로 읽을 때 타임스탬프(uint64)끼리 빼면 음수에서 넘칩니다. 실수로 바꾼 뒤 빼야 합니다.
6. MAVLink 는 14550 을 듣지 않는다
지상국(QGroundControl)은 보통 UDP 14550 에서 기다립니다. 그래서 「SITL 컨테이너의 14550 을 열어야 한다」고 생각하기 쉬운데, 실측은 반대였습니다.
- 일반 쿠버네티스 파드에서
mavlink status를 보면 첫 인스턴스가UDP (18570, remote port: 14550), 방송은 꺼짐이고, 로그에 「MAVLink only on localhost」가 찍힙니다. - 컨테이너 안의 UDP 소켓 표(
/proc/net/udp)에는 18570, 14580, 14280, 13030 같은 포트만 있고 14550 에 묶인 소켓은 없었습니다. PX4 는 14550 을 듣는 쪽이 아니라 14550 으로 보내는 쪽입니다. - 이미지의 시작 스크립트는
host.docker.internal이 IPv4 로 풀릴 때만 모든mavlink start줄에-t <그 주소>를 붙입니다(Docker Desktop 용). 일반 파드에서는 이름이 풀리지 않아 아무것도 바꾸지 않았습니다. - 파드의 hostAliases 로 그 이름을 127.0.0.2 로 풀리게 하자 다섯 줄 모두
-t 127.0.0.2로 바뀌었고, 같은 파드에서 0.0.0.0:14550 을 연 수신기가 30 s 동안 출발 포트 18570 의 패킷 10,004개를 받았습니다. 14540 쪽은 출발 포트 14580 에서 11,492개였습니다. - 이미지 설정의
EXPOSE 14540/udp 14550/udp 8888/udp는 메타데이터일 뿐 포트를 여는 것이 아닙니다.
정리하면, 지상국이 14550 을 쥐고 있어야 하고 SITL 쪽에서 14550 을 바인드하거나 게시할 이유는 없습니다. Docker Desktop 에서 -p 14550:14550/udp 로 컨테이너 포트를 게시하면
호스트의 14550 을 컨테이너 쪽이 차지해 지상국이 바인드에 실패할 수 있다는 보고가 있는데, 이 환경(containerd)에서는 재현할 수 없어 확인하지 못했습니다(미실측).
7. 한계
- 비행은 각각 한 번입니다. 바람, 센서 잡음, GPS 끊김 같은 조건은 넣지 않았습니다.
- 경로 이탈은 EKF 의 추정 위치로 쟀습니다. 1편의 교훈대로라면 시뮬레이터의 정답 위치(
vehicle_local_position_groundtruth, ULog 에 함께 기록됨)와 나란히 보아야 하는데, 이번 글에서는 아직 비교하지 않았습니다. - 지상국 연결(QGroundControl)과 Docker Desktop 의 포트 충돌은 미실측입니다.
8. 다음 편으로
다음 5편은 다시 탁구 로봇으로 돌아가, 2편의 공 모델 위에서 돌린 고전 제어 기준선의 반환 성공률을 다룹니다.
티티 한마디. 비행 제어기는 진짜, 몸은 가짜예요. 그래서 기체를 부술 걱정 없이 사각형을 몇 번이고 돌 수 있어요. 지상국 포트는 드론이 아니라 지상국이 쥐고 있어야 한다는 것도 잊지 마세요!
참고
모든 링크는 2026-10-18 에 열람해 열리는 것을 확인했습니다.
- PX4 문서(Gazebo 시뮬레이션): https://docs.px4.io/main/en/sim_gazebo_gz/
- PX4-Autopilot 저장소: https://github.com/PX4/PX4-Autopilot, 관련 이슈: https://github.com/PX4/PX4-Autopilot/issues/29292
- PX4 컨테이너 이미지: https://hub.docker.com/r/px4io/px4-sitl-gazebo, https://hub.docker.com/r/px4io/px4-dev
- MAVSDK-Python: https://github.com/mavlink/MAVSDK-Python
- ULog 형식: https://docs.px4.io/main/en/dev_log/ulog_file_format.html
- 인용 코드와 명령은 TT Lab 실험 코드입니다.