GitHub Actionsの限界を突破せよ:ランナーのCPU・メモリを極限までチューニングする裏技
テックリードの〇〇だ。
日々のCI/CDパイプライン、「なんか最近ビルド遅いな…」「またNode.jsが `JavaScript heap out of memory` で落ちやがった…」と絶望していないか?
GitHub Actionsの標準ホステッドランナー(`ubuntu-latest` 等)は、CPU 4コア、RAM 16GB、SSD 14GBというスペックを持っている。一見十分そうに見えるが、デフォルト設定のままDockerビルドや大規模なNode.jsテストスイートを回すと、このリソースはドブに捨てられているようなものだ。OSのデフォルト設定やツールの自動検出機能は、CI環境の特殊性(エフェメラル、かつリソースが固定されたコンテナ/VM)を考慮してくれない。
今回は、この標準ランナーのポテンシャルを極限まで引き出し、ビルド時間を最大50%削減しつつ、メモリ爆発エラーを完全に撲滅するプロのチューニング技法を叩き込む。
—
1. Node.js環境におけるメモリ制限の「真の」回避術
現代のフロントエンド/フルスタック開発において、最も多いCIの死因は `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed – JavaScript heap out of memory` だ。
多くの開発者は `NODE_OPTIONS=”–max-old-space-size=4096″` のように環境変数を適当に突っ込んで終わりにする。だが、これでは不十分だ。ランナーの物理メモリ(16GB)に対してどの程度割り当てるべきか、そしてOSののスワップ領域との兼ね合いを考慮しなければならない。
黄金のYAMLレシピ
Node.jsの並列処理(JestやViteなど)がCPUコア数(4コア)を検知してプロセスをフォークしまくり、メモリを食い潰すのが根本原因だ。環境変数で明示的に制御せよ。
name: Optimized Node.js CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
# ワークフロー全体で適用する環境変数
env:
# ランナーのRAM(16GB)の安全圏として12GBをV8ヒープに割り当て
NODE_OPTIONS: “–max-old-space-size=12288”
# UV(libuv)のプールサイズをCPUコア数にハードコードし、無駄なコンテキストスイッチを防ぐ
UV_THREADPOOL_SIZE: 4
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install Dependencies
run: npm ci
# JestなどのテストランナーでCPUコアを適切に制限し、メモリ枯渇を防ぐ
- name: Run Tests with Optimized Concurrency
run: |
npx jest –maxWorkers=2 –ci –colors
> プロの知見: `maxWorkers=2` にしている点に注目してほしい。4コアあるからと `4` や `50%` にすると、各ワーカーが巨大なヒープを持ち合い、OOM Killer(Out of Memory Killer)にプロセスをブチ殺される。CI環境では「コア数 – 1」または「物理メモリ逆算での安全値」に固定するのが鉄則だ。
—
2. プロセス間並列処理の最適化パラメーター(Make, Turborepo, Vite)
ビルドツールやタスクランナー(Turborepo, Nx, Make, Gradle等)は、デフォルトで「マシンの全CPUコア」を使い潰そうとする。GitHub Actionsのランナーは仮想環境であり、他のプロセスも動いているため、CPUを100%張り付かせるとI/O待ちが発生し、かえって処理が遅延する(スロットル現象)。
コア数の物理的制約を逆手に取る
例えば、C++のコンパイルやDockerビルド、Monorepoのビルドでは、スレッド数を明示的に制御せよ。
- name: Monorepo Build with Capped Concurrency
env:
# TurborepoやVercel等で並列度をCPUコア数(4)の範疇に制限
TURBO_CONCURRENCY: 3
run: npx turbo run build –filter=web…
Makefileを使う古いC/C++プロジェクトやネイティブモジュールを含むnpmパッケージの場合は、`MAKEFLAGS` をワークフローのグローバルまたはジョブレベルで指定する。
jobs:
native-build:
runs-on: ubuntu-latest
env:
# コア数+1を並列ジョブの限界値にする(I/O待ちを効率的に隠蔽する伝統的ハック)
MAKEFLAGS: “-j5”
steps:
- uses: actions/checkout@v4
- run: make release
—
3. チームで共有すべき「神プラグイン」と設定ファイル構成
個別のワークフローに場当たり的な設定を書くのはアマチュアのやることだ。チーム全体の生産性を底上げするためには、「再利用可能なワークフロー(Reusable Workflows)」と「カスタムキャッシュ戦略」をコードベースで共通化する必要がある。
組織で共有すべきベストプラクティス構成
プロジェクトの `.github/workflows/` 配下を以下のように構造化せよ。
.github/
├── workflows/
│ ├── ci.yml # 呼出元の薄いラッパー
│ └── reusable-backend-ci.yml # チーム共通の最適化済みパイプライン
└── actions/
└── setup-env/
└── action.yml # リソース設定をカプセル化されたカスタムアクション
隠れた神プラグイン:`actions/cache` の高度な使いこなし
単に `node_modules` をキャッシュするだけでは不十分だ。ビルド成果物(WebpackのキャッシュやTypeScriptのビルド情報)がディスクI/Oを圧迫する。
以下のカスタムアクション(`.github/actions/setup-env/action.yml`)をチームに導入し、CPUとメモリの初期プロファイルを設定させろ。
name: ‘Optimized Environment Setup’
description: ‘Sets up Node.js with optimal memory and caching for runner architecture’
inputs:
node-version:
description: ‘Node version’
required: false
default: ’20’
runs:
using: ‘composite’
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: ‘npm’
- name: Tune Linux VM Kernel Parameters for CI
shell: bash
run: |
# ディスクキャッシュのフラッシュ頻度を調整し、I/O待機時間を削減
echo “VmSts: Tuning kernel for CI I/O performance”
# ※ランナーの権限範囲内で実施可能なメモリ・IOプロファイルの調整
sudo sysctl -w vm.swappiness=10
> 解説: `vm.swappiness=10` に下げることで、Linuxカーネルが容易にメモリをスワップ領域(遅いディスク)に追い出さないようにし、物理RAM(16GB)の領域内で処理を完結させる。これにより、大量のファイルを読み書きするビルドの速度が目に見えて向上する。
—
4. 現場で震えるほど役立つ「限界突破」ハック集
最後に、GitHub Actionsのドキュメントの片隅にも載っていない、現場のテックリードがこっそり使っている裏技を授けよう。
ハック1: `/dev/shm`(共有メモリ)をフル活用したインメモリビルド
テストの実行やデータベースの起動(PostgreSQLやMySQLをCI上で回す場合)、あるいは重いビルドのテンポラリファイル出力先に `/dev/shm`(RAMディスク)を指定せよ。ディスクアクセスが消滅し、速度が桁違いになる。
- name: Run Tests in RAM Disk
env:
# テスト用の一時データベースのストレージをRAMディスク上に作成
TEST_DB_PATH: “/dev/shm/test_db.sqlite”
run: npm test
ハック2: 不要なバックグラウンドサービスの停止
GitHub Actionsの標準ランナーには、最初から様々なツールやサービス(データベース、ブラウザ、Azure CLI等)がプレインストールされており、バックグラウンドでメモリを消費している。使っていないものをキルし、リソースを奪い返せ。
- name: Purge Unused Services to Free Up RAM & CPU
shell: bash
run: |
# 例えば不要なMySQLやDocker daemonの停止(必要に応じて)
sudo systemctl stop mysql || true
sudo systemctl stop postgresql || true
# 空きメモリとCPUの確認
free -h
nproc
—
まとめ
GitHub Actionsの最適化は、単なる「コードの綺麗さ」ではなく、チームのリードタイム(Developer Experience)に直結する死活問題だ。
1. V8エンジンのヒープサイズ(`–max-old-space-size`)をランナーの物理メモリに合わせて手動チューニングする
2. プロセス並列度(`maxWorkers`, `TURBO_CONCURRENCY`, `MAKEFLAGS`)をCPUコア数から逆算して制限する
3. カーネルパラメータやRAMディスク(`/dev/shm`)を活用してI/Oボトルネックを粉砕する
これらを今日から君のプロジェクトのパイプラインにブチ込み、ビルド待ちのコーヒーブレイクを廃止に追い込んでくれ。