概要
CI/CD(Continuous Integration / Continuous Delivery:継続的インテグレーション / 継続的デリバリー)とは、ソフトウェア開発においてコードのビルド・テスト・デプロイを自動化する開発プラクティスおよびその仕組みの総称です。
CI(継続的インテグレーション):開発者がコードをリポジトリに頻繁にプッシュし、毎回自動でビルドとユニットテスト・静的解析を実行して問題を早期発見するプラクティスです。
CD(継続的デリバリー/デプロイメント):CIが成功した後、自動または半自動でリリース可能な成果物(ファームウェアバイナリ等)を生成・配布するプラクティスです。
組み込み開発では、クロスコンパイル環境のDockerコンテナ化、ホストPC上でのユニットテスト自動実行、ファームウェアバイナリのアーティファクト管理、フラッシュサイズ監視などをCI/CDで自動化できます。
歴史・背景
1990年代後半:グレディ・ブーチが「継続的インテグレーション」の概念を提唱。しかしこの時代は手動ビルドが中心でした。
2001年:アジャイル宣言。継続的デリバリーがアジャイル開発の重要プラクティスとして位置づけられました。
2001年:CruiseControl(Javaプロジェクト向けCI)がオープンソースで登場。自動ビルドの先駆けとなりました。
2005年:Hudson(後のJenkins)がリリース。Webベースのダッシュボードで多くのチームに採用されました。
2011年:Travis CI・CircleCIがSaaSとして登場。設定ファイル(.travis.yml)によるコードベースのCI設定が普及しました。
2011年:Jez HumbleとDavid Farleyの著書「Continuous Delivery」が出版され、CDの実践方法が体系化されました。
2014年:GitLab CI/CDが統合リポジトリ+CI機能として普及。2019年にGitHub Actionsが登場し、GitHubリポジトリとCI/CDの統合が極めて容易になりました。
2020年代:DockerやKubernetesとCI/CDの統合が標準化。組み込み開発でもDockerコンテナ化されたクロスコンパイル環境によるCI/CDが普及しています。
技術仕様
主要なCI/CDプラットフォーム
| プラットフォーム | 種別 | 特徴 | 組み込み対応 |
|---|---|---|---|
| GitHub Actions | クラウドSaaS | GitHub統合、無料枠あり、エコシステム豊富 | 良好 |
| GitLab CI/CD | クラウド/セルフホスト | パイプライン可視化、セルフホスト可 | 良好 |
| Jenkins | セルフホスト | 高度にカスタマイズ可能、プラグイン豊富 | 最も柔軟 |
| CircleCI | クラウドSaaS | 高速、Dockerネイティブ | 良好 |
| Bitbucket Pipelines | クラウドSaaS | Atlassian製品との統合 | 可 |
| Azure DevOps | クラウド/セルフホスト | Microsoft製品との統合 | 良好 |
組み込みCI/CDパイプラインの構成例
コードプッシュ(git push)
│
▼
[ステージ1: ビルド]
- クロスコンパイル(x86_64 → ARM)
- ビルドサイズ確認(Flash/RAM使用量)
- ビルドアーティファクト保存
│
▼ 成功
[ステージ2: 静的解析]
- Cppcheck
- MISRA C チェック(商用ツールの場合)
- GCC警告ゼロ確認
│
▼ 成功
[ステージ3: ユニットテスト(ホスト)]
- ホストGCCでネイティブビルド
- Unity / Google Testでテスト実行
- コードカバレッジ計測
│
▼ 成功
[ステージ4: 統合テスト(オプション)]
- エミュレータ(QEMU等)でのテスト
- HILテスト(自社サーバー)
│
▼ 成功(タグプッシュ時)
[ステージ5: リリース]
- バージョンタグからリリースノート生成
- ファームウェアバイナリ署名
- GitHubリリースにバイナリ添付
- OTA配布システムへアップロード
動作原理
GitHub Actions による組み込みCI実装
# .github/workflows/firmware-ci.yml
name: Firmware CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
TOOLCHAIN_VERSION: "10.3-2021.10"
jobs:
# ジョブ1: クロスコンパイルビルド
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout with submodules
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install ARM toolchain
run: |
wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/\
${TOOLCHAIN_VERSION}/gcc-arm-none-eabi-${TOOLCHAIN_VERSION}-x86_64-linux.tar.bz2
tar xjf gcc-arm-none-eabi-*.tar.bz2
echo "${PWD}/gcc-arm-none-eabi-${TOOLCHAIN_VERSION}/bin" >> $GITHUB_PATH
- name: Build firmware
run: |
mkdir -p build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain/arm-cortex-m4.cmake \
-DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
- name: Check binary size
run: |
arm-none-eabi-size build/firmware.elf
# フラッシュ使用量が上限(512KB)の90%を超えたら警告
FLASH_USED=$(arm-none-eabi-size build/firmware.elf | awk 'NR==2{print $1+$2}')
FLASH_MAX=524288 # 512KB
if [ "$FLASH_USED" -gt "$((FLASH_MAX * 90 / 100))" ]; then
echo "::warning::Flash usage exceeds 90%: ${FLASH_USED}/${FLASH_MAX} bytes"
fi
- name: Upload firmware artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ github.sha }}
path: |
build/firmware.elf
build/firmware.bin
build/firmware.hex
# ジョブ2: ユニットテスト(ホストPC上で実行)
unit-test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get install -y gcc lcov
- name: Build and run tests
run: |
mkdir -p build-test && cd build-test
cmake .. -DCMAKE_BUILD_TYPE=Debug \
-DUNIT_TEST_BUILD=ON \
-DCMAKE_C_FLAGS="--coverage"
make -j$(nproc)
ctest --output-on-failure
- name: Generate coverage report
run: |
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '*/test/*' '*/unity/*' --output-file coverage.info
genhtml coverage.info --output-directory coverage-html
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage-html/
# ジョブ3: 静的解析
static-analysis:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install Cppcheck
run: sudo apt-get install -y cppcheck
- name: Run Cppcheck
run: |
cppcheck --enable=all \
--error-exitcode=1 \
--suppress=missingIncludeSystem \
--std=c11 \
--xml src/ 2> cppcheck-report.xml
- name: Upload analysis report
uses: actions/upload-artifact@v4
if: always()
with:
name: static-analysis-report
path: cppcheck-report.xml
Dockerによるビルド環境の統一
# Dockerfile.build - 再現性のあるクロスコンパイル環境
FROM ubuntu:22.04
ARG TOOLCHAIN_VERSION=10.3-2021.10
RUN apt-get update && apt-get install -y \
cmake ninja-build make \
cppcheck lcov \
python3 python3-pip \
wget curl git \
&& rm -rf /var/lib/apt/lists/*
# ARM toolchain のインストール
RUN wget -q https://developer.arm.com/.../gcc-arm-none-eabi-${TOOLCHAIN_VERSION}-x86_64-linux.tar.bz2 \
&& tar xjf gcc-arm-none-eabi-*.tar.bz2 -C /opt \
&& rm gcc-arm-none-eabi-*.tar.bz2
ENV PATH="/opt/gcc-arm-none-eabi-${TOOLCHAIN_VERSION}/bin:${PATH}"
WORKDIR /workspace
# ローカルでもCI環境と同じビルドを実行
docker run --rm -v $(pwd):/workspace arm-build:latest \
bash -c "cd /workspace && mkdir -p build && cd build && cmake .. && make"
フラッシュサイズの継続的な監視
# check_size.py - CI/CDでのサイズチェックスクリプト
import subprocess
import sys
LIMITS = {
'text': 450 * 1024, # .text + .rodata: 最大450KB(512KB中)
'data': 100 * 1024, # .data: 最大100KB(128KB中)
'bss': 100 * 1024, # .bss: 最大100KB
}
result = subprocess.run(
['arm-none-eabi-size', '--format=SysV', 'build/firmware.elf'],
capture_output=True, text=True
)
for line in result.stdout.splitlines():
for section, limit in LIMITS.items():
if line.strip().startswith(f'.{section}'):
size = int(line.split()[1])
pct = size * 100 // limit
print(f"{section}: {size:,} bytes ({pct}% of {limit//1024}KB)")
if size > limit:
print(f"ERROR: .{section} exceeds limit!", file=sys.stderr)
sys.exit(1)
elif pct > 90:
print(f"WARNING: .{section} is at {pct}% of limit")
用途・ユースケース
自動リリースと OTA 配布
# タグプッシュ時に自動リリースとOTA配布
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build release firmware
run: make release
- name: Sign firmware
env:
SIGNING_KEY: ${{ secrets.FIRMWARE_SIGNING_KEY }}
run: |
echo "$SIGNING_KEY" > /tmp/signing.key
openssl dgst -sha256 -sign /tmp/signing.key \
-out build/firmware.sig build/firmware.bin
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
build/firmware.bin
build/firmware.hex
build/firmware.sig
generate_release_notes: true
- name: Upload to OTA server
env:
OTA_API_KEY: ${{ secrets.OTA_API_KEY }}
run: |
curl -X POST https://ota.example.com/api/v1/upload \
-H "Authorization: Bearer $OTA_API_KEY" \
-F "firmware=@build/firmware.bin" \
-F "version=${{ github.ref_name }}" \
-F "signature=@build/firmware.sig"
複数ターゲット向けの並列ビルド
jobs:
build-matrix:
strategy:
matrix:
target:
- { board: nucleo_f401re, mcu: STM32F401RE }
- { board: nucleo_l476rg, mcu: STM32L476RG }
- { board: esp32_devkit, mcu: ESP32 }
runs-on: ubuntu-latest
name: Build for ${{ matrix.target.board }}
steps:
- uses: actions/checkout@v4
- name: Build
run: make BOARD=${{ matrix.target.board }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ matrix.target.board }}
path: build/${{ matrix.target.board }}/firmware.bin
実装・開発のポイント
シークレット管理
# 署名キーやAPIキーはGitHub Secretsで管理
# → リポジトリ設定 → Secrets and variables → Actions で設定
# 使用例(YAMLで参照)
env:
SIGNING_KEY: ${{ secrets.FIRMWARE_SIGNING_KEY }}
OTA_API_KEY: ${{ secrets.OTA_SERVER_KEY }}
キャッシュによる高速化
- name: Cache ARM toolchain
uses: actions/cache@v3
with:
path: /opt/gcc-arm-none-eabi
key: arm-toolchain-${{ env.TOOLCHAIN_VERSION }}
- name: Cache CMake build directory
uses: actions/cache@v3
with:
path: build
key: cmake-${{ hashFiles('CMakeLists.txt', 'src/**/*.c') }}
セルフホストランナー(実機テスト用)
# 実機(ターゲットボード)に接続したサーバーでのテスト
jobs:
hardware-test:
runs-on: self-hosted # 実機接続サーバーのランナーを指定
steps:
- uses: actions/checkout@v4
- name: Flash and test
run: |
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program build/firmware.elf verify reset exit"
python3 test/hardware_test.py --port /dev/ttyUSB0
他技術との比較
| 比較項目 | GitHub Actions | GitLab CI/CD | Jenkins |
|---|---|---|---|
| セットアップ難易度 | 低(設定ファイルのみ) | 低〜中 | 高(インストール設定) |
| セルフホスト | 可(GitHub Actions Runner) | 可(GitLab Runner) | 必須 |
| 無料枠 | 2000分/月(publicは無料) | 400分/月 | 完全無料 |
| エコシステム | 最大(Marketplaceのアクション) | 豊富 | 最大(プラグイン数) |
| 組み込み対応 | 良好 | 良好 | 最も柔軟 |
| 秘密情報管理 | Secrets | CI/CD Variables | Credentials管理 |
バージョン管理との統合とDockerコンテナ化されたビルド環境が、現代の組み込みCI/CDの基盤です。