newmo 技術ブログ

技術で地域をカラフルに

Renovateをメモリ不足なクラウド版から、GitHub ActionsのSelf-host版へ移行した

newmo では依存パッケージの更新に Renovate を使っています。これまでは Mend が提供するクラウド版(Mend-hosted の GitHub App)を利用していました。しかし Kernel out-of-memory エラーで Renovate がまともに動かなくなりました。

この記事では、なぜ動かなくなったのか、そしてクラウド版から GitHub Actions 上で動かす Self-host 版へどう移行したのかを紹介します。

過去に書いた Renovate と pnpm catalog で依存を管理する話 の続編にあたる内容です。

先に結論

  • クラウド版 Renovate がメモリ制約(3GB)で Kernel out-of-memory を起こし、PR を作れなくなった
  • メモリ・実行時間・環境変数の制約を回避するため、GitHub Actions 上で動かす Self-host に移行した
  • NODE_OPTIONS=--max-old-space-size=4096 でヒープ上限を広げ、OOM は解消した
  • ARM ランナーの平日 3 回/日運用で、実コストは月 $4 程度に収まった
  • self-approve や conflict の rebase など、クラウド版が裏で吸収していた処理は専用ワークフローで補う必要があった

何が起きたか

きっかけは Renovate が PR を作らなくなったことです。Mend のダッシュボードを確認すると、ジョブが Kernel out-of-memory エラーで失敗し続けていました。

DEBUG: Executing command (branch="renovate/dev-sdk")
{
  "command": "pnpm install --lockfile-only --recursive --ignore-scripts --ignore-pnpmfile"
}

ログは pnpm install の途中で途切れており、lockfile の更新時にメモリを使い切ってカーネルに kill されている、という状況でした。

newmo-app は Go・TypeScript・Swift・Kotlin・Terraform などを 1 つのリポジトリに収めたモノレポです。フロントエンドだけでも pnpm workspace 配下に多数のパッケージがあり、pnpm install --lockfile-only のピークメモリが大きくなります。同様の事例は Renovate 本体の Discussion でも報告されていました。

原因調査と、クラウド版の限界

Discussion では Gradle wrapper の更新が疑われていました。Gradle 系の manager を順番に止めて切り分けましたが、いずれも効果はありませんでした。

結局、特定の manager ではなく pnpm install のメモリ使用量そのものがクラウド版の上限を超えていました。Node.js のヒープ上限を広げれば回避できそうですが、クラウド版ではその制約に突き当たります。

クラウド版 Renovate には次の制約があります。

項目 クラウド版の制限
メモリ 3GB
実行時間 30 分
環境変数 任意の env を渡せない

各プランの制限は Mend Renovate Cloud-hosted の overview に記載があります。上記は Community Cloud(無料プラン)の値です。

NODE_OPTIONS="--max-old-space-size=4096" のような環境変数を渡してヒープ上限を引き上げたくても、クラウド版では環境変数を注入する手段がありません。pnpm 側にメモリ使用量を制限するオプションも特にないため、クラウド版の枠内では打つ手がない、という結論になりました。

実行時間も切実で、OOM を回避できたとしても 21 分 30 秒(30 分制限)まで迫っており、モノレポの成長を考えると遠からず実行時間でも頭打ちになる見込みでした。

解決方針: GitHub Actions で Self-host する

Renovate は renovatebot/github-action を使うと GitHub Actions 上で Self-host できます。これを選んだ理由は次の通りです。

  • GitHub Actions のランナーはクラウド版(3GB)より大きなメモリを使え、NODE_OPTIONS も自由に渡せる
  • 実行時間の上限がクラウド版の 30 分より大きく、timeout-minutes で必要に応じて調整できる
  • 既存の CI が GitHub Actions なので、認証やシークレット管理の仕組みを流用できる

コストも事前に試算しました。GitHub Actions の課金は分単位なので、実行頻度がそのままコストになります。ここでは 1 回の実行を 25 分と仮定しています。

頻度 回数/日 ubuntu-latest($0.008/min) ubuntu-arm64($0.005/min)
1 時間ごと 24 回 $144/月 $90/月
4 時間ごと 6 回 $36/月 $22.50/月
1 日 1 回 1 回 $6/月 $3.75/月

計算式: 単価 × 25分 × 回数 × 30日

実際には平日 4 時間ごと(1 日 3 回)で十分なので、ARM ランナーを使えば月 $10 弱に収まる試算です。

移行後の実測値も載せておきます。次は GitHub Actions の使用状況メトリクスから集計した、移行後ひと月分のコストです。

ワークフロー 役割 実行時間 実行回数 ランナー 概算コスト
renovate.yaml Renovate 本体 769 min 63 ARM($0.005/min) $3.85
renovate-approve.yaml automerge の自動承認 40 min 39 SLIM($0.002/min) $0.08
renovate-config.yaml 設定の dry-run 検証 32 min 15 ARM($0.005/min) $0.16
合計 841 min 117 約 $4.1/月

試算では月 $10 弱でしたが、実測では月 $4 程度に収まりました。コストの 9 割以上は Renovate 本体(renovate.yaml)で、769 分 / 63 回 = 平均 12 分強と、クラウド版の 30 分制限に対して余裕があります。承認は SLIM ランナー($0.002/min)で動かしているため、回数の割にコストはほぼ無視できる水準です。

アーキテクチャ

Self-host 版は大きく 3 つのワークフローで構成されています。

  1. Renovate の実行(renovate.yaml
  2. automerge 対象 PR の自動承認(renovate-approve.yaml
  3. conflict の手動解消(@newmo-kun fix conflict

Renovate の実行

クラウド版の GitHub App と違い、Self-host では「誰として PR を作るか」を自分で用意する必要があります。newmo では newmo-kun という GitHub App を作り、その権限で Renovate を動かしています。

%%{init: {"theme": "forest", "themeVariables": {"lineColor": "#ffffff"}}}%%
flowchart TD
    A[GitHub Actions<br>.github/workflows/renovate.yaml] --> B[GCP認証<br>Workload Identity]
    B --> C[Secret Managerから<br>GitHub App秘密鍵を取得]
    C --> D[newmo-kun GitHub App<br>トークンを生成]
    D --> E[renovatebot/github-action<br>でRenovate実行]
    E --> F[PRの作成/更新]

ワークフローの本体は次のようになっています。認証は Workload Identity Federation で GCP に対して行い、GitHub App の秘密鍵は Secret Manager から取得します。このあたりは既存 CI の仕組みをそのまま流用できました。

# .github/workflows/renovate.yaml
on:
  workflow_dispatch:
    # 手動実行(dry-run / log-level を指定できる)
  schedule:
    # cron は UTC 基準。UTC 0:00 / 4:00 / 8:00 = JST 9:00 / 13:00 / 17:00(平日)
    - cron: "0 0,4,8 * * 1-5"

jobs:
  renovate:
    timeout-minutes: 60
    # コスト削減のため ARM ランナーを使用 ($0.005/min vs $0.008/min)
    runs-on: ${{ vars.RUNNER_ARM_DEFAULT }}
    steps:
      # ...(GCP 認証 → Secret Manager → GitHub App トークン生成は省略)...
      - uses: renovatebot/github-action@v44.2.6
        with:
          renovate-version: ${{ env.RENOVATE_VERSION }}
          configurationFile: .github/renovate.json5
          token: ${{ steps.github-app-token.outputs.token }}
        env:
          # pnpm lockfile 更新時の OOM 対策(大規模 monorepo でヒープ上限に達するため)
          NODE_OPTIONS: "--max-old-space-size=4096"
          RENOVATE_PLATFORM: github
          RENOVATE_PLATFORM_COMMIT: "true"
          # リポジトリキャッシュ有効化
          RENOVATE_REPOSITORY_CACHE: enabled
          # 対象リポジトリを明示指定
          RENOVATE_REPOSITORIES: '["your-org/your-repo"]'

このうち NODE_OPTIONS: "--max-old-space-size=4096" が OOM への直接の対応です。クラウド版では環境変数を渡せず設定できませんでしたが、Node.js のヒープ上限を 4GB に広げた結果、pnpm install --lockfile-only の OOM は再発していません。renovate.json5 自体(パッケージのグルーピングや automerge の設定)はクラウド版からそのまま引き継いでいるので、利用者から見た挙動はほぼ変わりません。

スケジュールの考え方

定期実行は平日の 4 時間ごと(JST 9:00 / 13:00 / 17:00)に設定しました。一方で renovate.json5 側の schedule は、ワークフローの起動時刻より少し広めの範囲を指定しています。

スケジュールが 2 段階あるのは、GitHub Actions の cron と Renovate 自身のスケジュール判定が独立しているためです。cron は「いつワークフローを起動するか」だけを決めます。Renovate は起動後に renovate.json5schedule を見て「いま更新してよい時間か」を改めて判定します。

つまり cron の起動時刻が schedule の帯にカバーされる時だけ更新が走ります。帯が狭いと、起動しても skip される時刻が出ます。

cron の起動時刻が renovate.json5 の schedule の帯にカバーされる時だけ更新が走ることを示すタイムライン図

// .github/renovate.json5(抜粋)
npm: {
  schedule: ["* 9-18 * * 1-5"],
  // ...
}

Automerge: self-approve できない問題

newmo では minor / patch の多くのパッケージを automerge 対象にしています。automerge には PR の approve が必要です。しかし GitHub では、PR を作成したアカウントが自分自身の PR を approve できません(self-approve 不可)。たとえば newmo-kun が作った PR は、同じ newmo-kun では承認できません。

そこで、承認専用に newmo-kun-approver という別の GitHub App を用意し、approve だけを担当させています。

%%{init: {"theme": "forest", "themeVariables": {"lineColor": "#ffffff"}}}%%
flowchart TD
    A[RenovateがPRを作成<br>automerge: true] --> B[newmo-kun-approver App<br>がPRを自動承認]
    B --> C[CIがグリーンになる]
    C --> D[GitHub Auto-merge<br>で自動マージ]

承認ワークフローは、次の 3 条件をすべて満たす PR だけを approve します。

# .github/workflows/renovate-approve.yaml(抜粋)
if: |
  github.event.sender.login == 'newmo-kun[bot]' &&
  contains(github.event.pull_request.labels.*.name, 'dependencies') &&
  contains(github.event.pull_request.body, '**Automerge**: Enabled')

ここで 1 つハマりどころがありました。approve 自体は成功しているのに、GitHub の表示が approved にならない(緑のチェックが付かない)という現象です。原因は承認用 GitHub App に対象リポジトリの contents: write 権限がなかったことでした。権限を付与したところ、期待通り approved 扱いになりました。

conflict の解消: @newmo-kun fix conflict

クラウド版では PR がコンフリクトすると Renovate が自動で rebase してくれました。Self-host では rebase のためだけにスケジュール頻度を上げると無駄が多くなります。

そこで、PR のコメントに @newmo-kun fix conflict と書く方式にしました。その PR のブランチだけを対象に Renovate を実行して conflict を解消します。

%%{init: {"theme": "forest", "themeVariables": {"lineColor": "#ffffff"}}}%%
flowchart TD
    A["PRコメント<br>@newmo-kun fix conflict"] --> B[GitHub Actions]
    B --> C[コメントに👍リアクション]
    C --> D{ブランチ名を判定}
    D -->|通常のPR| E[base ブランチをマージ]
    D -->|Renovate PR<br>renovate| F[RENOVATE_CHECKED_BRANCHES でrebase]
    E --> G[結果をpush]
    F --> G

Renovate PR の場合は RENOVATE_CHECKED_BRANCHES に対象ブランチを渡して rebase します。必要なときだけ手動で叩けるので、スケジュールを上げずに済んでいます。

移行時にハマったこと

クラウド版とは前提が違うため、いくつか移行特有の落とし穴がありました。記録しておきます。

  • No repositories foundRENOVATE_REPOSITORIES で対象リポジトリを明示指定する。Self-host では --autodiscover に頼らず '["your-org/your-repo"]' を指定する必要がある。
  • platformCommit property has been changedRENOVATE_PLATFORM_COMMIT: "true" を明示的に指定する。
  • approve がマージ条件の承認としてカウントされない → 承認用 GitHub App に contents: write を付与する。前述の通り。
  • 既存 PR を引き継げない → 移行時に残った PR は手動でマージ/クローズする。Self-host 版はクラウド版が作った PR を引き継げない。branchPrefix を変えれば新規 PR として作り直せるが、運用がわかりにくくなるためそのままにした。
  • クラウド版と Self-host 版の二重更新で競合する → 公式の Mend-hosted App をアンインストールする。

まとめ

Kernel out-of-memory をきっかけに、Renovate をクラウド版から GitHub Actions の Self-host 版へ移行しました。ポイントは次の通りです。

  • クラウド版はメモリ 3GB・実行時間 30 分・環境変数を渡せないという制約があり、モノレポの成長で限界に達した
  • Self-host にすることで NODE_OPTIONS=--max-old-space-size=4096 を渡せるようになり、OOM が解消した
  • ARM ランナーの平日 3 回/日運用で、実コストは試算(月 $10 弱)を下回る月 $4 程度に収まった
  • self-approve できない・conflict の rebase など、クラウド版が裏で吸収していた処理は自前のワークフローで補った

Self-host は運用の責務が増える一方、メモリや実行時間といったリソースを自分でコントロールできるようになります。今後はリポジトリキャッシュの永続化による実行時間の短縮や、rebase 運用の改善を進めていく予定です。

newmoではエンジニアを積極的に採用中です。興味がある方は、キャリアサイト https://careers.newmo.me/ をご覧ください。