【実務・中級編】npm/pnpmにおける「CIビルド時のネットワーク障害」を防ぐ:レジストリのリトライ戦略とタイムアウト設定 – ビルド・パッケージ管理ツール生産性向上バイブル

CIの「npm install」で消耗するな:ネットワークのゆらぎを制するレジストリ戦略とアーキテクチャ最適化

テックリードとして現場を見渡すと、多くのチームが「ビルドがたまに落ちる」「npm installがタイムアウトする」というノイズに貴重な開発時間を奪われている。これらは単なる「運」や「回線のせい」ではなく、パッケージマネージャのデフォルト設定が、現代の不安定なCI/CD環境に対してあまりに無防備であることが原因だ。

本稿では、npm/pnpmの内部構造を理解し、堅牢なCI環境を構築するための「攻撃的防御」の手法を伝授する。

—

1. なぜCIでインストールは失敗するのか?(プロトコルレベルの視点)

npmやpnpmが通信を行う際、デフォルトでは「短気な」タイムアウト値と「非力な」リトライ戦略しか持っていない。パブリックレジストリ(registry.npmjs.org)の負荷が高い瞬間や、CIノードとレジストリ間の微細なパケットロスが発生すると、即座にコネクションが切断される。

ここで重要なのは、「待つこと」ではなく「しぶとく再接続すること」である。

.npmrc によるリトライ戦略の最適化

`.npmrc` に以下の設定を投入せよ。これは単なるおまじないではない。TCP接続の維持と、サーバー側の過負荷に対する適応戦略だ。

接続タイムアウトを60秒へ延長(デフォルトは30秒程度と短い)
fetch-timeout=60000

リトライ回数を増やす(デフォルトは2。ネットワークが荒い環境なら5は必須)
fetch-retries=5

リトライ間隔を指数関数的にバックオフさせる(デフォルトは1分、秒単位で制御)
fetch-retry-mintimeout=10000
fetch-retry-maxtimeout=60000

ネットワーク帯域が細いCI環境での同時並列数を制限(デフォルトは低スペックなCIでは多すぎる)
max-sockets=10

なぜこれで解決するのか?
`fetch-retry-mintimeout` を調整することで、サーバーが一時的に過負荷状態(503 Service Unavailable)にある際、即座に再試行して共倒れするのを防ぐ。指数バックオフにより、CI環境のネットワーク帯域を占有せずに、確実にパッケージをプルできるようになる。

—

2. Failoverの実装:レジストリを多重化する「透過的切り替え」

単一のレジストリに依存するのはリスクでしかない。特にグローバル展開するプロダクトでは、npmレジストリの特定のリージョンがダウンすることは珍しくない。

pnpmを使っているならば、`pnpm-workspace.yaml` や `.npmrc` でこれを制御できるが、最も強力なのは「ローカルプロキシ(Verdaccio等)をキャッシュとして挟み、上流を複数構成にする」ことだ。

推奨構成:Verdaccioを利用したCIキャッシュ戦略

CIのYAML(GitHub Actions等)で、以下のようにレジストリを動的に解決する環境変数を注入する。

CI設定例: レジストリを環境変数で制御
env:
# プライベートレジストリを優先し、到達不能ならパブリックへフォールバックする構成にする
NPM_CONFIG_REGISTRY: ${{ secrets.REGISTRY_URL }}

現場の知見:
GitHub Actionsなどでは、`actions/setup-node` の `registry-url` を使うのが定石だが、それだけでは足りない。「キャッシュのヒット率」と「ネットワーク耐性」の両立を目指すなら、CIのランナー上で一度メインのレジストリが死んだ際に、即座に別のミラー(CloudsmithやJFrog Artifactory、あるいはnpm公式)へ自動スイッチするスクリプトを `pre-install` フックに仕込むのが、真のテックリードの仕事だ。

—

3. 生産性を極限まで高める「隠れた」テクニック

pnpmの `–frozen-lockfile` は「必須の礼儀」

チーム開発において、ローカルとCIでバージョンが微妙にズレることは、もっともデバッグコストが高い。

CI環境での実行コマンド
pnpm install –frozen-lockfile –prefer-offline

  • `–frozen-lockfile`: ロックファイルと `package.json` が不一致ならエラーを吐いて停止する。
  • `–prefer-offline`: 可能な限りローカルキャッシュを利用し、ネットワークトラフィックを最小化する。

開発者体験(DX)を爆速にするショートカット

CLIを叩く時間を減らすために、私は `package.json` にエイリアスを定義するのではなく、シェル(zsh/fish)の関数として定義を共有している。

.zshrc に記述し、チーム全体で共有
alias pi=’pnpm install’
alias pif=’pnpm install –frozen-lockfile’ # CI用コマンドをローカルで即座に検証

「神」プラグイン:`npm-check-updates` (ncu)

依存関係の更新を追うのは骨が折れる。`ncu` は単なる更新チェックではない。CIに組み込むことで、「破壊的変更を含むメジャーアップデートをCIで検知し、安全に拒絶する」ためのゲートキーパーとして機能する。

破壊的変更があるパッケージをCIで警告する戦略
ncu –deep –reject-version “>=2.0.0”

—

結論:ネットワーク障害を「前提」とした設計を

CIのビルドが落ちるたびに「またか」と溜息をつくのはもう終わりにしよう。

1. タイムアウトとリトライのパラメータを、ネットワーク環境に合わせて厳密に調整する。
2. 単一障害点(レジストリ)を排除し、多重化またはプロキシを導入する。
3. `–frozen-lockfile` を強制し、CIとローカルの再現性を完璧にする。

これらを行うだけで、開発チームが「ビルドを通すための無駄な待機時間」から解放され、本来注力すべき「コードの品質向上」に全リソースを割くことができるようになる。

あなたのプロダクトのデプロイ速度を左右するのは、コードの書き方だけではない。「ビルドパイプラインというインフラの強靭さ」こそが、世界最高峰のエンジニアリングチームを創る鍵なのだ。今すぐ `.npmrc` を開き、この設定をコミットしてほしい。その一歩が、チームの生産性を劇的に変えるはずだ。

タイトルとURLをコピーしました