CircleCI : Flaky Test におけるパイプラインの再実行 max_auto_reruns パラメータ
2025年8月6日に失敗したパイプラインで起動したワークフローのリトライ機能がリリースされました。
ワークフローが失敗した場合に、手動操作なしで自動的に “rerun from failed” が実行され、失敗したジョブだけが再実行されます(成功済みのジョブは再実行されません)。UI でも自動リトライ状況と試行回数が表示されます。
この機能は Flaky Test(何も変えていないのに成功/失敗が揺れる不安定テスト)や、ネットワーク遅延・外部依存の一時的障害など一過性の失敗に対して有効です。
Flaky Test とは
「flaky test(フレイキーテスト)」とは、同じテストコードを何も変更していないのに、あるときは成功し、あるときは失敗する不安定なテストのことを指します。再現性が低く、同じ環境で実行しても結果が安定しないケースがあります。テスト対象のバグではなく、テストコードや環境要因に依存して失敗することが多いく、意図せずCI/CDパイプラインを停止させてしまいます。
たとえば、テストで呼び出すコードがランダム値を使っている、時刻依存の処理が含まれている、非同期処理のタイミングがずれる、ネットワークやファイル I/O の遅延、など理由は様々です。
シンプルな例で言えばWeb Serverをインストールして起動するconfig.ymlを作成した際に、npm や git コマンドで必要なライブラリやモジュールをインストールする際にダウンロードが遅い、OSの状況によりインストールにいつもより時間がかかる、などでtest用curlコマンドが間に合わないケース、などがあります。
max_auto_reruns パラメータ
この問題を解決させるためにリリースされた機能が max_auto_reruns パラメータです。workflowの設定に以下の通り記載をしておくことでパイプラインが失敗した際に、指定回数再実行を行います。
workflows:
deploy:
jobs:
- deploy-nodeapp
# ここで自動リトライ回数を指定
max_auto_reruns: 2
成功した際にはパイプラインは再実行されません。
さっそくやってみる
前回の記事でnodeを用いたwebサーバをSSHでLinuxにログインしたのちデプロイする手順を纏めました。
config.yml,server.jsの2つをさらに置換します。
version: 2.1
jobs:
deploy-nodeapp:
docker:
- image: cimg/base:stable
steps:
- checkout
# CircleCIに登録したSSH鍵を使用(Fingerprintで指定)
- add_ssh_keys:
fingerprints:
- "SHA256:xxKOKDhqcEfxkek9slpnuS4mDrXaNRPC2eMjq1oXNBk"
# デプロイ先サーバの known_hosts 登録
- run:
name: Register known_hosts
command: |
mkdir -p ~/.ssh
ssh-keyscan 163.43.218.xx >> ~/.ssh/known_hosts
# Nodeアプリのデプロイ&起動
- run:
name: Deploy & start node app
command: |
ssh -p 22 ubuntu@163.43.218.xx "
set -eo pipefail
APP_DIR=/home/ubuntu/nodeapp
REPO_URL=https://github.com/h-kameda-sakura/apitest.git
wait_for_apt_lock() {
sudo dpkg --configure -a >/dev/null 2>&1 || true
while sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|| sudo fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
echo '[apt] Waiting for lock to be released...'
sleep 3
done
}
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
wait_for_apt_lock
sudo apt-get update -y
wait_for_apt_lock
sudo apt-get install -y git curl
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y git curl
elif command -v yum >/dev/null 2>&1; then
sudo yum install -y git curl
elif command -v zypper >/dev/null 2>&1; then
sudo zypper -n install git curl
fi
mkdir -p ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts || true
if ! command -v node >/dev/null 2>&1; then
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
wait_for_apt_lock
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
wait_for_apt_lock
sudo apt-get install -y nodejs
elif command -v dnf >/dev/null 2>&1; then
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo dnf install -y nodejs
elif command -v yum >/dev/null 2>&1; then
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install -y nodejs
elif command -v zypper >/dev/null 2>&1; then
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo zypper -n install nodejs
else
echo Unsupported OS >&2; exit 1
fi
fi
if [ -d \"\$APP_DIR/.git\" ]; then
git -C \"\$APP_DIR\" fetch --all
git -C \"\$APP_DIR\" checkout main
git -C \"\$APP_DIR\" pull --ff-only
else
sudo mkdir -p \"\$APP_DIR\"
sudo chown -R ubuntu:ubuntu \"\$APP_DIR\"
git clone \"\$REPO_URL\" \"\$APP_DIR\"
fi
cd \"\$APP_DIR\"
npm ci || npm install
if [ -f nodeapp.service ]; then
sudo install -m 0644 nodeapp.service /etc/systemd/system/nodeapp.service
sudo systemctl daemon-reload
sudo systemctl enable nodeapp
fi
sudo systemctl restart nodeapp || sudo systemctl start nodeapp
sudo systemctl status nodeapp --no-pager || true
"
# 🔍 HTTPテスト(/test を叩く)
- run:
name: Test http://163.43.218.xx:8080/test
command: |
URL="http://163.43.218.xx:8080/test"
echo "Waiting 3 seconds before test..."
sleep 3
code=$(curl -fsS -o /dev/null -w "%{http_code}" --connect-timeout 3 "$URL" || true)
if [ "$code" = "200" ]; then
echo "OK: $URL/test returned 200"
exit 0
else
echo "Test failed for $URL (status:$code)" >&2
exit 1
fi
workflows:
deploy:
jobs:
- deploy-nodeapp
# ここで自動リトライ回数を指定
max_auto_reruns: 2
// server.js (http版 + /test失敗)
const http = require('http');
const port = process.env.PORT || 8080;
const server = http.createServer((req, res) => {
if (req.url === '/test') {
const fail = Math.random() < 0.5;
res.writeHead(fail ? 500 : 200, {'Content-Type':'application/json; charset=utf-8'});
res.end(JSON.stringify({
ok: !fail,
message: fail ? 'intentional failure for test' : 'success'
}));
return;
}
// デフォルト /
res.writeHead(200, {'Content-Type':'text/plain; charset=utf-8'});
res.end('Hello from Node.js on port ' + port + '\n');
});
server.listen(port, '0.0.0.0', () => {
console.log(`Server running at http://0.0.0.0:${port}/`);
});
config.ymlではhttp://163.43.218.xx:8080/testにcurlを実行し200が戻ればテストが成功しパイプラインが完了します。そのエンドポイントは50%の確率で200,500どちらかが戻る様になっています。
config.ymlの max_auto_reruns: 2によりパイプラインが失敗しても2回までリトライ処理が行われます。

Discussion