TT Lab
Blog

한국어 English 日本語 中文

Get started
Blog

Blog

Flying PX4 Drones in Kubernetes Pods: SITL Square Mission and Six Lessons Learned from Source Builds

Status: Draft (2026-10-18), Measured. Measurements were taken from 2026-10-17 23:55 to 2026-10-18 00:45 (KST) in a temporary pod on a home lab Kubernetes cluster. The node is a single home lab GPU node (AMD Ryzen 7 series, 16 threads), but no GPU was used. The flight pod was launched with a simulator container (capped at 3.5 CPU cores and 3 GiB of memory) and a sidecar container running MAVSDK python:3.12-slim (capped at 0.5 CPU cores and 1 GiB), while the build pod was launched with a capped CPU of 4 cores and 4 GiB of memory. The software versions used are the pre-built image PX4 v1.18.0-beta1-660 (61907962c9, 2026-09-11), source-built PX4-Autopilot main 93579610c6 (2026-10-16), Gazebo Sim 8.15.0 (Harmonic), and MAVSDK-Python 3.17.4. Since each flight was conducted only once, the values in the table are single-measurement results. Raw data (two ULogs, summary JSON, console logs) are located in the working directory measure/sim-px4/ , and all pods were deleted after the measurements.

Table of Contents

1. Intuition: Putting Real Flight Software into a Fake Body

SITL (Software In The Loop) runs the actual flight software destined for the drone as-is on a computer, while a simulator emulates only the sensors and motors. To PX4, it is indistinguishable from a real aircraft, allowing mission planning, emergency behaviors, and ground control station connections to be tested without physical hardware. While the wheeled robot in Part 1 moved on ROS 2, this time the flight controller itself is the test subject.

2. Choosing an Image: Fly Immediately or Build and Modify?

Image Compressed Size Download Time Contents Usage
px4io/px4-sitl-gazebo:latest 656 MiB 32.5 s Built PX4 1.18 + Gazebo Harmonic, startup script Quick test flights, mission and ground station testing
px4io/px4-dev:v1.18.0-rc1 1.77 GiB 2 m 3.9 s Ubuntu 24.04 build tools (excluding simulation tools) Modifying and building PX4 source code

When launching the pre-built image headlessly with HEADLESS=1 , it printed "Startup script returned successfully" within 25 s. Because the development image is nearly three times larger and requires manually installing Gazebo and simulation dependencies, the pre-built image is preferable unless you need to modify the code.

3. Square Mission Flight

Since the simulator container sends MAVLink to localhost:14540 in the same pod, MAVSDK in the sidecar container receives it at udpin://0.0.0.0:14540 . The mission consists of a 10 m takeoff, 4 square waypoints with 40 m sides pointing North and East (speed 5 m/s, fly-through), followed by Return-to-Launch (RTL) and landing.

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()

(Excerpt from TT Lab experiment code fly_square.py . item() is a small function that generates the 15 arguments for MAVSDK 3.x 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 추정). 점선은 계획한 사각형입니다.
Figure: Square mission flight path and altitude re-plotted from ULog
Metric Value
Takeoff command → Reached 9.5 m 12.9 s
Mission (AUTO_MISSION) 41.0 s
Return and Land (AUTO_RTL) 20.6 s
From connection to disarm 79 s
Maximum altitude 10.03 m
Altitude error during mission (Average / Maximum) 0.048 / 0.189 m
Square cross-track error (Average / RMS / p95 / Maximum) 0.256 / 0.328 / 0.657 / 0.722 m
Closest distance to waypoints (WP1–4) 0.80, 0.70, 0.62, 0.00 m
Landing position (relative to home) 0.055 m, landing judgment true
Maximum Roll / Pitch 19.1° / 18.4°
Simulator Container Resources CPU 62.8 s / Elapsed 215 s (Average 0.29 cores), memory.peak 205 MiB

Cross-track error measures how far the EKF-estimated vehicle_local_position deviated from the line segments connecting the home point and the four waypoints. The waypoints being passed 0.6–0.8 m on the inside is presumed (estimated) to be due to the "fly-through" setting and the acceptance radius (NAV_ACC_RAD 10 m) cutting the corners slightly. The figure was rendered by directly reading the ULog (28 MB) using a standard library, and the landing position of 0.055 m and phase-by-phase timestamps matched the analysis summary.

The simulator's battery is configured to drop to 50% over 60 s, decreasing from 100% to 50% and 16.2 V to 15.3 V. This does not reflect actual battery consumption.

4. Source Build Version: Flying with Shell Commands Only

We cloned PX4-Autopilot from the development image (--depth 1, including submodules, 117–121 s, 1.9 GiB), built it, and this time flew it using only PX4 shell commands without MAVSDK. Shell inputs were passed via a FIFO.

param set NAV_DLL_ACT 0        # 지상국이 없어도 이륙을 거부하지 않게
param set MIS_TAKEOFF_ALT 10
commander takeoff
listener vehicle_local_position -n 1
commander land
Metric Value
Dependency installation (Gazebo Harmonic + simulation dependencies) 157 s
HEADLESS=1 make px4_sitl gz_x500 PX4_MAKE_ARGS=-j2 Startup after build 182 s
Hold arm time 37.4 s
Maximum altitude 10.04 m
Maximum horizontal jitter during hover 0.092 m
Runtime memory anon 156 MiB, gz sim RSS 209 MiB, px4 RSS 21 MiB

commander takeoff After 20 s, an altitude of 10.015 m was read, and it ended with "Disarmed by landing" after commander land . Testing can be done using only the flight controller, without a ground station or Python.

5. Six Issues Encountered

  1. MAVSDK 4 API Changes. The latest mavsdk 4.0.6 switched to native bindings, preventing us from calling mavsdk.mission . We pinned the version to mavsdk<4(3.17.4). The successor package for the old approach is named mavsdk-grpc.
  2. Gazebo Dependencies Must Precede the Build. A build directory configured once without Gazebo leaves "Gazebo simulation dependencies not found" in the CMake cache, causing gz_x500 to fail even if Gazebo is installed later. Install dependencies first, then build.
  3. OpenCV. gz-harmonic Installing only ubuntu.sh resulted in the optical_flow plugin failing to find OpenCV. We had to install PX4's minimal simulation dependency list as-is (211 packages, 171 MB added, 39 s).
  4. Parallel Build OOM and PX4_MAKE_ARGS. . Without specifying the parallel job count, ninja used the node's core count (16) + 2, causing an OOMKilled error under the 4 GiB memory limit. However, since the PX4 Makefile reads -j from the terminal (ps T), nohup may not be propagated in make -j2 executions lacking a terminal. We resolved this by passing PX4_MAKE_ARGS=-j2 as a make variable.
  5. ULog Path. Logs from the source build were located in build/px4_sitl_default/rootfs/log/ rather than build/px4_sitl_default/rootfs/fs/log/<날짜>/*.ulg .
  6. Daemon Mode Output. px4 -dWhen launched with px4-<모듈> (daemon), the output of client commands (e.g., mavlink status) goes to the server log rather than the command's terminal. The results were read from the px4 logs.

As a side note, when reading ULogs with pyulog, subtracting timestamps (uint64) directly causes an underflow on negative values. You must cast them to floating-point numbers before subtraction.

Ground control stations (QGroundControl) typically listen on UDP 14550. This makes it easy to assume that "port 14550 on the SITL container must be opened," but actual measurements showed the opposite.

In summary, the ground control station must hold port 14550, and there is no reason for the SITL side to bind or publish 14550. There are reports that publishing container ports using -p 14550:14550/udp on Docker Desktop can cause the container side to occupy the host's 14550 and prevent the ground control station from binding, but we could not reproduce or verify this in our environment (containerd) (unmeasured).

7. Limitations

8. Next Up

The next post, Part 5, returns to the ping-pong robot and covers the return success rate of the classical control baseline run on the ball model from Part 2.

TT's Take: The flight controller is real, but the body is fake. That means you can fly square patterns over and over without worrying about crashing the aircraft. And don't forget: the ground control station port should be held by the ground station, not the drone!

References

All links were verified to be active when accessed on 2026-10-18.

Sign in to like

Comments

No comments yet.

Sign in to leave a comment