Buildroot OTA 이미지 업데이트 — RAUC·swupdate 통합
#한 줄 요약
“OTA는 느린 빌드 단계가 아니라 시스템 설계 결정입니다.” — 어떤 도구를 고르냐보다 A/B 슬롯·서명·롤백 정책을 먼저 정해야 빌드가 따라옵니다. RAUC와 swupdate는 둘 다 Buildroot에 잘 통합돼 있고, 차이는 bundle 포맷·생태계·운영 도구입니다.
#왜 OTA가 별도의 장인가
OTA는 한 패키지를 추가하면 끝나는 작업이 아닙니다. RAUC를 BR2_PACKAGE_RAUC=y로 켜는 일은 30초면 됩니다. 하지만 동작하는 OTA 시스템을 만들려면 7가지가 정렬돼야 합니다. partition layout, bootloader 협업, bundle 포맷, 서명 체계, rollback 정책, delivery 경로, 상태 머신입니다.
이 중 하나가 어긋나도 현장에서 brick이 됩니다. OTA 실패는 대량 RMA로 직결되므로, OTA 설계가 BSP 설계만큼 무겁게 다뤄져야 합니다. 이 장은 Buildroot 위에서 세 도구를 비교하고, RAUC와 swupdate의 실제 통합 흐름을 단계별로 보여 줍니다.
#OTA 결정 차원 3개
도구를 고르기 전에 세 가지 차원을 먼저 결정합니다. 도구 선택은 그 다음입니다.
| 차원 | 옵션 | 실무 기본값 |
|---|---|---|
| 슬롯 모델 | A/B (dual bank) / delta / container 단위 | A/B (storage 2배가 가장 작은 비용) |
| 서명 정책 | unsigned / single key / CA chain | CA chain (단일 키는 회수 불가) |
| rollback | passive (bootcount) / active (health check) | passive + active 동시 |
A/B는 atomic + 즉시 rollback이 가능해 가장 단순합니다. delta는 셀룰러·LPWA처럼 데이터 비용이 큰 환경에서 검토합니다. container 모델은 application 주기가 firmware보다 빠른 시스템에 한정.
서명 정책은 prototyping에서 production 키 체계까지 미리 검증해야 합니다. 한 번 배포된 device의 trust anchor는 사실상 바꿀 수 없습니다.
rollback은 passive (U-Boot bootcount)가 부팅까지의 실패를 잡고, active (userspace health check)가 부팅 이후 실패를 잡습니다. 양쪽 다 있어야 “부팅은 되는데 application이 죽는” 시나리오를 처리합니다.
#도구 비교 — RAUC vs swupdate vs Mender
세 도구의 차이를 한눈에.
| 항목 | RAUC | swupdate | Mender |
|---|---|---|---|
| Buildroot 패키지 | BR2_PACKAGE_RAUC | BR2_PACKAGE_SWUPDATE | BR2_PACKAGE_MENDER |
| 라이선스 | LGPLv2.1 | GPLv2 | Apache 2.0 client, commercial server |
| slot 모델 | A/B per-slot 설명, group 단위 | A/B 또는 single | A/B (rootfs partition) |
| update 단위 | bundle (squashfs + manifest) | CPIO archive + sw-description | mender artifact (tar) |
| delta 지원 | casync 기반 | libarchive + custom handler | 자체 delta |
| 서명 | x509 CA chain, OpenSSL/PKCS#11 | RSA / CMS / ed25519 | RSA |
| HTTP server | 별도 (hawkBit 등) | 내장 mongoose 서버 | 자체 server (commercial) |
| D-Bus API | yes | no | no |
| container 친화도 | 그룹 단위 | handler 작성으로 가능 | 미지원 |
| 학습 곡선 | 중 | 중-상 (handler 자유도 높음) | 낮음 (전체 SaaS) |
선택의 경험적 기준은 다음과 같습니다. RAUC는 D-Bus 통합·systemd 친화·CA chain이 표준화돼 firmware 위주 제품에 깔끔합니다. swupdate는 handler를 직접 작성해 복잡한 partition·container·MCU 펌웨어까지 한 번에 업데이트하는 시스템에 적합합니다. Mender는 server까지 한 번에 끝내고 싶을 때 선택, 단 server는 commercial입니다.
이 장은 RAUC와 swupdate를 다룹니다. Mender는 클라이언트 통합 방식이 비슷하고, 차이는 서버 측 SaaS라 Buildroot 통합 관점에서는 부수적입니다.
#RAUC — Buildroot 통합 흐름
RAUC는 5개 파일만 정렬되면 동작합니다. system.conf, cert/key, slot이 정의된 device tree·genimage.cfg, manifest, bundle build 스크립트입니다.
menuconfig 토글.
BR2_PACKAGE_RAUC=yBR2_PACKAGE_RAUC_SYSTEM_CONF="board/myboard/rauc/system.conf"BR2_PACKAGE_HOST_RAUC=y # bundle 생성용 host toolBR2_PACKAGE_HOST_CASYNC=y # delta 지원 시HOST_RAUC는 호스트에서 bundle을 만드는 도구입니다. Target 빌드 후 post-image hook에서 호출합니다.
board/myboard/rauc/system.conf는 슬롯 정의의 유일한 진실 소스입니다.
[system]compatible=myboard-v1bootloader=ubootmountprefix=/run/raucmax-bundle-download-size=1073741824
[keyring]path=ca.cert.pem
[handlers]system-info=/usr/lib/rauc/system-info.shpost-install=/usr/lib/rauc/post-install.sh
[slot.rootfs.0]device=/dev/mmcblk0p2type=ext4bootname=A
[slot.rootfs.1]device=/dev/mmcblk0p3type=ext4bootname=B
[slot.bootloader.0]device=/dev/mmcblk0boot0type=raw핵심은 compatible과 bootname입니다. compatible은 manifest와 일치하지 않으면 RAUC가 install을 거부합니다. 잘못된 하드웨어에 깔리는 사고를 막는 1차 방어선입니다. bootname은 U-Boot 변수 BOOT_ORDER의 토큰과 일치해야 합니다.
bundle 안에 들어가는 manifest.raucm.
[update]compatible=myboard-v1version=2026.05.19-3description=Q2 maintenance release
[bundle]format=verity
[image.rootfs]filename=rootfs.ext4
[image.bootloader]filename=u-boot.imxformat=verity는 RAUC 1.5+ 권장입니다. bundle 자체가 dm-verity로 무결성 검증되며, 종래의 plain (단일 서명)보다 부분 변조에 강합니다. sha256·size는 rauc bundle이 자동 채웁니다.
Buildroot가 rootfs.ext4를 만든 뒤 post-image script가 RAUC bundle을 생성합니다.
#!/bin/shset -eBOARD_DIR="$(dirname "$0")"IMAGES_DIR="${BINARIES_DIR}"BUNDLE_DIR="${IMAGES_DIR}/rauc-bundle"KEY_DIR="${BR2_EXTERNAL_MYBOARD_PATH}/keys"
rm -rf "${BUNDLE_DIR}"; mkdir -p "${BUNDLE_DIR}"cp "${IMAGES_DIR}/rootfs.ext4" "${BUNDLE_DIR}/"cp "${IMAGES_DIR}/u-boot.imx" "${BUNDLE_DIR}/"cp "${BOARD_DIR}/rauc/manifest.raucm" "${BUNDLE_DIR}/"
rauc bundle \ --cert="${KEY_DIR}/device.cert.pem" \ --key="${KEY_DIR}/device.key.pem" \ --keyring="${KEY_DIR}/ca.cert.pem" \ "${BUNDLE_DIR}" \ "${IMAGES_DIR}/myboard-${BR2_VERSION}.raucb"이 hook을 BR2_ROOTFS_POST_IMAGE_SCRIPT에 등록하면 make 한 번으로 bundle까지 완성됩니다. target에서는 rauc install /tmp/myboard.raucb로 설치하고, rauc status로 현재 슬롯·boot status를 확인합니다. 다음 부팅에서 반대 슬롯으로 자동 전환됩니다.
#swupdate — 통합 흐름
swupdate는 RAUC보다 handler 자유도가 높은 도구입니다. 단일 binary로 raw partition·UBI·MTD·LUKS·MCU 펌웨어·post-script까지 처리합니다.
menuconfig 토글.
BR2_PACKAGE_SWUPDATE=yBR2_PACKAGE_SWUPDATE_CONFIG="board/myboard/swupdate/swupdate.config"BR2_PACKAGE_SWUPDATE_WEBSERVER=y # embedded mongoose webserverBR2_PACKAGE_SWUPDATE_LUASCRIPTS=y # Lua handler 지원swupdate bundle의 manifest는 sw-description입니다. libconfig 문법.
software ={ version = "2026.05.19-3"; description = "Q2 release"; hardware-compatibility: ["1.0", "1.1"];
images: ( { filename = "rootfs.ext4.gz"; volume = "rootfs_b"; type = "ubivol"; compressed = "zlib"; sha256 = "@rootfs.ext4.gz.sha256"; }, { filename = "u-boot.imx"; device = "/dev/mmcblk0boot0"; type = "raw"; sha256 = "@u-boot.imx.sha256"; } );
scripts: ( { filename = "post-install.lua"; type = "lua"; } );
bootenv: ( { name = "upgrade_available"; value = "1"; }, { name = "bootcount"; value = "0"; } );};bootenv: 절이 U-Boot 환경 변수를 직접 갱신합니다. swupdate의 강점 중 하나로, RAUC가 별도 handler에서 처리해야 하는 일을 manifest 한 줄로 끝냅니다.
bundle 패키징은 CPIO newc 포맷.
#!/bin/shset -eSTAGE="${BINARIES_DIR}/swupdate-stage"KEY="${BR2_EXTERNAL_MYBOARD_PATH}/keys/swupdate-priv.pem"
rm -rf "${STAGE}"; mkdir -p "${STAGE}"cp "${BINARIES_DIR}/rootfs.ext4.gz" "${STAGE}/"cp "${BINARIES_DIR}/u-boot.imx" "${STAGE}/"cp "${BOARD_DIR}/swupdate/sw-description" "${STAGE}/"
openssl cms -sign -in "${STAGE}/sw-description" \ -out "${STAGE}/sw-description.sig" \ -signer "${KEY}" -nocerts -noattr -binary -outform DER
# CPIO newc — sw-description이 첫 번째여야 함( cd "${STAGE}" && \ ls sw-description sw-description.sig *.ext4.gz u-boot.imx \ | cpio -ov -H newc ) > "${BINARIES_DIR}/myboard.swu"핵심 함정 — sw-description이 CPIO 안에서 첫 번째 파일이어야 합니다. swupdate는 stream을 앞에서부터 파싱하므로 manifest가 뒤에 있으면 signature 검증을 시작도 못 합니다. WEBSERVER를 켜면 target의 :8080에 업로드 UI가 생겨 별도 server 인프라 없이 노트북·USB로 직접 업로드할 수 있습니다.
#A/B slot 부팅 — U-Boot와의 협업
A/B 부팅의 결정자는 OTA 도구가 아니라 U-Boot입니다. RAUC·swupdate는 어느 슬롯이 다음 부팅 대상인지를 U-Boot 환경 변수로 알립니다.
# 정상 부팅 시도 - bootcount 1 증가bootcmd=run main_bootmain_boot=if test ${BOOT_ORDER} = "A B"; then \ run slot_a; \ else \ run slot_b; \ fi
# 부팅 N회 실패 시 altbootcmd로 전환altbootcmd=setenv BOOT_ORDER "B A" && saveenv && resetbootlimit=3U-Boot가 bootcount > bootlimit이면 altbootcmd를 실행해 반대 슬롯으로 강제 전환합니다. 이게 passive rollback의 핵심입니다. RAUC는 system.conf의 bootloader=uboot 설정으로 install 완료 시 BOOT_ORDER를 갱신하고, rauc status mark-good 호출 시 bootcount를 리셋합니다.
이 helper들은 u-boot-tools의 fw_setenv·fw_printenv를 호출합니다. Buildroot가 자동으로 /etc/fw_env.config를 만들어 환경 partition의 위치·크기를 알려 줍니다.
# MTD/MMC device offset env-size sector-size N-sectors/dev/mmcblk0 0x100000 0x4000 0x4000 2이 파일이 부정확하면 fw_setenv가 엉뚱한 위치를 덮어써 부팅이 깨집니다. Ch 13의 U-Boot env 정렬을 다시 확인해야 하는 부분입니다.
#partition layout
A/B 설계의 최소 layout은 6개 partition입니다.
| 번호 | 이름 | 크기 | 용도 |
|---|---|---|---|
| 1 | boot | 64 MB | kernel + dtb + initramfs (있다면) |
| 2 | rootfs-A | 800 MB | 슬롯 A rootfs |
| 3 | rootfs-B | 800 MB | 슬롯 B rootfs |
| 4 | appdata | 200 MB | app config (공유 또는 슬롯별) |
| 5 | data | 나머지 | 공유 user data (slot 무관) |
| 6 | recovery | 200 MB | 최후 복구용 minimal rootfs |
genimage.cfg에서 다음과 같이 정의합니다.
image sdcard.img { hdimage { partition-table-type = "gpt" }
partition boot { bootable = "true" image = "boot.vfat" size = 64M } partition rootfs-A { image = "rootfs.ext4"; size = 800M; } partition rootfs-B { image = "rootfs.ext4"; size = 800M; } partition appdata { image = "appdata.ext4"; size = 200M; } partition data { image = "data.ext4"; } partition recovery { image = "recovery.ext4"; size = 200M; }}핵심 결정 — A/B 슬롯의 크기는 최대 rootfs + 향후 2년 성장 여유를 잡습니다. 너무 작으면 몇 번의 업데이트 뒤 image가 안 들어가고, 너무 크면 공유 data 영역이 부족합니다. eMMC 4 GB 기준 800 MB × 2 + recovery 200 MB가 흔한 시작점입니다.
#서명·암호화
bundle 서명은 양산의 비가역 결정입니다. 한 번 배포된 device의 trust anchor는 사실상 바꿀 수 없으므로, prototyping 단계에서 production 키 체계를 미리 검증해야 합니다.
RAUC는 전형적인 3단계 CA chain을 씁니다. Root CA는 오프라인 머신·HSM에 보관, intermediate는 build server에 권한 위임, device cert가 실제 bundle 서명을 담당합니다.
# Root CA (한 번만, 오프라인 머신)openssl req -x509 -newkey rsa:4096 -keyout root.key.pem -out root.cert.pem \ -nodes -days 7300 -subj "/CN=MyCompany Root CA"
# Intermediateopenssl req -newkey rsa:4096 -keyout inter.key.pem -out inter.csr \ -nodes -subj "/CN=MyCompany Build Intermediate"openssl x509 -req -in inter.csr -CA root.cert.pem -CAkey root.key.pem \ -CAcreateserial -out inter.cert.pem -days 3650
# Device signing certopenssl req -newkey rsa:2048 -keyout device.key.pem -out device.csr \ -nodes -subj "/CN=myboard-v1 build"openssl x509 -req -in device.csr -CA inter.cert.pem -CAkey inter.key.pem \ -out device.cert.pem -days 730
# device에 들어가는 keyring = root + intermediatecat root.cert.pem inter.cert.pem > ca.cert.pemca.cert.pem을 target rootfs의 /etc/rauc/에 둡니다. Buildroot는 rootfs-overlay 메커니즘으로 처리하는 게 가장 깔끔합니다.
swupdate는 RSA·ECDSA·ed25519를 지원합니다. 빌드 옵션은 swupdate.config에서 CONFIG_SIGNED_IMAGES=y + CONFIG_SIGALG_CMS=y (또는 _RAWED25519)를 켜고, device에는 /etc/swupdate/public.pem을 둡니다. 검증은 manifest 서명 + 각 image의 sha256의 2단입니다.
#롤백 정책 — watchdog과 health check
passive rollback (bootcount)만으로는 “부팅은 되는데 application이 죽는” 시나리오를 못 잡습니다. active rollback이 추가로 필요합니다.
[Unit]Description=Mark RAUC slot good after health checkAfter=multi-user.target network-online.target myapp.serviceWants=network-online.target
[Service]Type=oneshotExecStartPre=/usr/local/bin/health-check.shExecStart=/usr/bin/rauc status mark-goodRemainAfterExit=yes
[Install]WantedBy=multi-user.targethealth-check.sh는 제품별 정의입니다. 다음 5가지가 통과해야 good으로 마킹합니다. 네트워크 reachability (백엔드 ping), 핵심 service status (systemctl is-active), 센서·주변기기 enumeration, 디스크 mount 상태, 정해진 N분의 안정성 관찰입니다.
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS 하에 watchdog을 켜고, health check 서비스가 watchdog을 정기적으로 kick하게 합니다. 부팅 직후부터 N분 동안 kick이 들어오지 않으면 보드가 hardware reset → bootcount 증가 → passive rollback으로 자연스레 이어집니다.
#흔한 실패
slot 크기 부족. 가장 흔한 사고. 처음 800 MB로 잡았는데 18개월 뒤 rootfs가 820 MB가 된 경우. bundle install이 *“No space left on device”*로 실패합니다. 해결책은 처음부터 크게 잡거나, partition repartition 도구를 미리 준비. 양산 후 partition 변경은 boot loader·env까지 같이 갱신해야 해서 사실상 recovery 시나리오가 필요합니다.
env partition 비동기. A 슬롯과 B 슬롯이 같은 env partition을 공유할 때, fw_setenv가 atomic write를 보장하지 못하면 부팅 중 갱신 → 정전으로 env가 깨집니다. U-Boot의 redundant env (CONFIG_ENV_OFFSET_REDUND)를 켜서 두 copy를 유지하는 게 표준 해법입니다.
signature verification 실패.
rauc-install: signature verification failed: certificate not trusted원인 후보 세 가지. target의 ca.cert.pem이 옛 버전이라 rotation 후 갱신이 안 됐거나, bundle 서명 시 intermediate가 누락됐거나 (rauc bundle --intermediate inter.cert.pem 필요), target 시계가 cert valid range 밖인 경우입니다. 세 번째는 의외로 자주. embedded board의 RTC가 1970년이면 모든 cert가 not-yet-valid로 거부됩니다. --no-check-time 옵션은 개발 전용이고 양산에 두면 안 됩니다.
bundle MIME format wrong. swupdate에서 Invalid header magic이 뜨면 CPIO가 newc가 아닌 다른 format으로 만들어진 경우입니다. cpio -ov -H newc가 정답이고, 기본 format이나 tar는 실패합니다. 또한 외부 gzip 압축을 두지 않습니다. swupdate는 stream을 순차로 읽으므로 외부 압축 layer가 있으면 부분 streaming이 불가능해집니다. 압축은 내부 image별로만 적용합니다 (compressed = "zlib";).
#정리
- OTA는 패키지 한 개가 아니라 7개 영역의 정렬 — partition, bootloader, bundle, 서명, rollback, delivery, 상태 머신.
- 결정 차원은 슬롯 모델 (A/B vs delta vs container), 서명 정책 (단일 vs CA chain), *rollback 방식 (passive vs active)*의 3개입니다.
- RAUC·swupdate·Mender의 차이는 bundle 포맷·생태계·서버입니다. firmware 위주면 RAUC, handler 자유도 우선이면 swupdate, server까지 통합 패키지를 원하면 Mender.
- RAUC 통합은 system.conf + manifest + post-image hook의 세 파일로 끝납니다.
rauc bundle명령이 hash·서명을 자동 처리합니다. - swupdate 통합의 핵심은 sw-description + CPIO newc 컨테이너 + sw-description이 첫 파일. embedded webserver를 같이 켜면 별도 서버 없이 현장 업로드가 됩니다.
- A/B 부팅의 결정자는 U-Boot이며, RAUC·swupdate는
fw_setenv로 변수를 갱신합니다./etc/fw_env.config의 정확성이 보드 brick 여부를 가릅니다. - partition layout의 시작점은 boot + rootfs-A + rootfs-B + appdata + data + recovery의 6개. eMMC 4 GB 기준 800 MB × 2가 흔한 비율입니다.
- 양산은 CA chain + active rollback (health check) + watchdog의 3단 방어가 default. RTC 배터리·NTP 동기까지 chain의 일부로 봐야 합니다.
#다음 장 예고
다음 편은 Ch 17: SDK 생성·배포 — make sdk와 application 워크플로. OTA로 시스템 이미지를 갱신하는 흐름을 마쳤다면, 그 위에서 application 개발자가 어떻게 빌드 환경을 받아 쓰는지 다룹니다.
#관련 항목
- Ch 13: U-Boot 통합 — env·bootcmd·MMC layout —
fw_env.config·bootcount의 출처 - Ch 15: post-build·post-image 스크립트 심화 — RAUC bundle 생성 hook의 정석
- Ch 18: 보안·CVE 관리 — secure boot, SBOM, CVE tracking — OTA 서명 키와 secure boot의 chain of trust
- U-Boot Ch 17: A/B 업데이트 — bootcount와 altbootcmd — bootloader 측 시각
- U-Boot Ch 20: RAUC·swupdate 통합 — bootloader 측 책임 — Buildroot 통합의 반대편 절반
- BSP Development Ch 14: 업데이트 시스템 설계 — BSP 관점의 OTA 아키텍처
- 원문 — RAUC documentation
- 원문 — swupdate documentation
Buildroot Practical · 16 of 20
- 1Buildroot가 푸는 문제 — Yocto와의 핵심 차이 분석
- 2Buildroot 디렉터리 구조 분해 — board·configs·dl·output
- 3Buildroot Kconfig 설정 — menuconfig와 defconfig 작성
- 4Buildroot 첫 빌드 — QEMU에서 동작하는 시스템 만들기
- 5Buildroot 패키지 시스템 분석 — .mk와 Config.in 동작 추적
- 6Buildroot 외부 트리 — BR2_EXTERNAL 구성과 활용
- 7Buildroot 보드 Customize — overlay·post-build·post-image 흐름
- 8Buildroot 출력 파일시스템 — initramfs·squashfs·ext4·cpio 선택
- 9Buildroot 새 패키지 작성 — autotools·cmake·python 통합
- 10Buildroot 실전 — BeagleBone Black 시스템 처음부터 끝까지
- 11Buildroot Toolchain 선택 — Internal vs External 비교
- 12Buildroot 커널 Customize — defconfig fragment와 DTS 통합
- 13Buildroot U-Boot 통합 — 빌드·env·fw_env 흐름
- 14Buildroot 빌드 캐싱 분석 — dl·ccache·per-package
- 15Buildroot post-build·post-image 심화 — rootfs 최종 수정 흐름
- 16Buildroot OTA 이미지 업데이트 — RAUC·swupdate 통합
- 17Buildroot SDK 생성·배포 — make sdk와 application 워크플로
- 18Buildroot Security·CVE 추적 — pkg-stats와 Reproducible Builds
- 19Buildroot CI/CD 구축 — Container Build와 Cache 공유
- 20Buildroot → Yocto 마이그레이션 — 언제·어떻게 옮길까
관련 글
Buildroot → Yocto 마이그레이션 — 언제·어떻게 옮길까
Buildroot가 한계에 도달하는 신호와 Yocto/OE로 점진 이전하는 패턴, meta-buildroot 같은 hybrid 옵션.
Buildroot CI/CD 구축 — Container Build와 Cache 공유
GitLab/GitHub Actions에서 Buildroot 트리를 컨테이너로 빌드하고 dl·ccache를 팀이 공유하는 패턴.
Buildroot Security·CVE 추적 — pkg-stats와 Reproducible Builds
Buildroot의 CVE 추적·legal info 산출·SBOM·reproducible build로 보안과 컴플라이언스를 관리하는 패턴.
이 글을 참조하는 글 (6)
- 임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교— Bootloader Internals
- Buildroot CI/CD 구축 — Container Build와 Cache 공유— Buildroot Practical
- Buildroot Security·CVE 추적 — pkg-stats와 Reproducible Builds— Buildroot Practical
- Buildroot post-build·post-image 심화 — rootfs 최종 수정 흐름— Buildroot Practical
- Buildroot U-Boot 통합 — 빌드·env·fw_env 흐름— Buildroot Practical
- 임베디드 A/B 부팅 이중화 — OTA 안전성을 위한 부트 슬롯 설계— Bootloader Internals