Mirrativ Tech Blog

株式会社ミラティブの開発者(バックエンド,iOS,Android,Unity,機械学習,インフラ, etc.)によるブログです

Renovateをセルフホスト with Google Cloud & Go

こんにちは、バックエンド基盤チームの藤井脩紀です。 バックエンド基盤チームは、バックエンドエンジニアの生産性向上やコスト削減を目的に、エンジニア主導で課題を発見・解決している部署です。

このチームではCIの整備や依存関係の更新なども担当しており、その一環としてRenovateというソフトウェアのセルフホストを行ったのでそれについてお話しさせてください。

目次

Renovateとは

Automated dependency updates. Multi-platform and multi-language.

Renovateは依存関係の自動更新に用いられるオープンソースのソフトウェアです。

競合としてはGitHubにビルトインされたDependabotなどが挙げられます。

セルフホストに至った背景

元々はDependabotを利用していたのですが対象リポジトリの容量が大きすぎることが原因とみられる問題で動かなくなってしまい、GitHubに問い合わせを行いつつ試行錯誤してみたのですが解決には至らずGitHub AppのRenovateに移行して運用を行っていました。

しかしながら先日そちらも同じく容量不足で動かなくなってしまい、より大容量のマシンで実行するためセルフホストすることにしました。

セルフホスト

ここからは本題のセルフホスト設定について説明していきます。

Renovateは1回分の処理を終えたら終了するタイプのアプリケーションでセルフホストする場合はcronなどを活用して任意の頻度で定期実行することになります。つまりホストを常時起動させておく必要はなくインスタントな環境で実行可能なので費用面は抑えやすくなっています。

ミラティブのバックエンドではGoogle Cloudを活用しているのでRenovateを実行するCloud BuildをCloud Schedulerで定期実行するという構成をとりました。 ただしCloud Build上ではbashで完結しているためGoogle Cloud以外で実行する場合にも参考になると思われます。公式のドキュメントにはCircleCIなどの設定例が載っています。

Self-Hosting Examples - Renovate Docs

それ以外の部分ではソースコードはGitHubで管理しており、Renovateにより依存の更新を行う対象の言語はGoになっています。 以降では設定例を示すような形で簡易的に解説していきます。

GitHubの下準備

まず下準備としてGitHubのアクセストークンを発行するために以下の権限設定でGitHub Appを作成します。 (その他の設定はほぼデフォルトでWebhookは利用しないのでActiveのチェックを外しました。)

  • Repository permissions
    • Administration: Read-only
    • Checks: Read and write
    • Commit statuses: Read and write
    • Contents: Read and write
    • Dependabot alerts: Read-only
    • Issues: Read and write
    • Metadata: Read-only
    • Pull requests: Read and write
    • Workflows: Read and write
  • Organization permissions
    • Members: Read-only

次に作成したAppをOrganizationなどにインストールしてRenovateを実行したいリポジトリへのアクセス権限を与えます。 (もしgo.mod内にGitHubで管理するプライベートモジュールが存在する場合はそちらへのアクセス権限も必要になります。)

これによって得られる以下が以降の設定で必要となります。

  • GitHub Appの秘密鍵
  • GitHub AppのクライアントID
  • GitHub AppのインストールID

GitHub Appの作成方法の詳細は割愛するので公式のドキュメントを参照してください。

GitHub App の登録 - GitHub Docs

なお指定した権限は以下に従ったものになります。

GitHub and GitHub Enterprise Server - Renovate Docs

Cloud Buildの設定

RenovateをCloud Build上で実行するための設定例を説明していきます。

構成ファイルを準備

以下の2つのファイルを保持するリポジトリを作成します。

  • script/create-github-token.sh
  • cloudbuild.yaml

ただし script/create-github-token.sh は見通しをよくするためにファイルを分けているだけで cloudbuild.yaml の1ファイルに統合可能です。 よってリポジトリを用意せずにトリガーの編集画面からインラインで設定することも可能です。

script/create-github-token.sh

GitHub APIを叩いてアクセストークンを発行するためのスクリプトです。

JWTの発行部分はGitHubの公式ドキュメントを参考にしています。

#!/usr/bin/env bash

set -euo pipefail

client_id=$1
installation_id=$2
pem=$(cat $3)

now=$(date +%s)
iat=$((${now} - 60))
exp=$((${now} + 600))
b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }
header_json='{
    "typ":"JWT",
    "alg":"RS256"
}'
header=$(echo -n "${header_json}" | b64enc)
payload_json="{
    \"iat\":${iat},
    \"exp\":${exp},
    \"iss\":\"${client_id}\"
}"
payload=$(echo -n "${payload_json}" | b64enc)
header_payload="${header}"."${payload}"
signature=$(openssl dgst -sha256 -sign <(echo -n "${pem}") <(echo -n "${header_payload}") | b64enc)
jwt="${header_payload}"."${signature}"

res=$(curl -sSL --retry 3 \
    -X POST "https://api.github.com/app/installations/${installation_id}/access_tokens" \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer ${jwt}" \
    -H "X-GitHub-Api-Version: 2022-11-28")
token=$(echo $res | jq -r '.token // empty')
if [ -z "${token}" ]; then
    echo "failed to create an installation access token: ${res}" 1>&2
    exit 1
fi
echo "${token}"
cloudbuild.yaml

Cloud Buildの構成ファイル本体です。 スクリプト部分の解説は割愛して環境変数のみ説明します。

その他の設定は以下のドキュメントを参照してください。

Self-Hosted configuration - Renovate Docs

steps:
  - id: "renovate"
    name: "renovate/renovate:39.185.7"
    entrypoint: "bash"
    args:
      - -c
      - |
        set -ueo pipefail
        echo "$${GITHUB_APP_PRIVATE_KEY}" > /tmp/github-app-private-key.pem
        export RENOVATE_TOKEN=$(./script/create-github-token.sh ${_GITHUB_APP_CLIENT_ID} ${_GITHUB_APP_INSTALLATION_ID} /tmp/github-app-private-key.pem)
        renovate-entrypoint.sh
    env:
      - 'RENOVATE_REPOSITORIES=noi/renovate-self-hosting'
      - 'RENOVATE_ONBOARDING=false'
      - 'LOG_LEVEL=debug'
      - 'GOPRIVATE=github.com/noi/'
    secretEnv: ["GITHUB_APP_PRIVATE_KEY"]
availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/github-app-renovate-private-key/versions/latest
      env: GITHUB_APP_PRIVATE_KEY
options:
  logging: CLOUD_LOGGING_ONLY
  • RENOVATE_REPOSITORIES
    • Renovateの対象とするリポジトリの一覧
    • カンマ区切りで複数指定可能
  • RENOVATE_ONBOARDING
    • Renovateの対象とするリポジトリにrenovate.jsonを追加するPull Requestを作成するか否かのフラグ
    • renovate.jsonが存在する場合にRenovateの対象となります
    • 自前で用意するのでfalseとしましたがデフォルト値のtrueのままでもかまいません
  • LOG_LEVEL
    • デフォルトだとあまり情報が出ないのでセルフホストする場合はdebugにしておいた方が問題が発生したときに原因が特定が容易になりそうです
  • GOPRIVATE
    • go.modにプライベートモジュールが存在する場合に指定(RenovateというよりGoの設定)

Cloud Buildに馴染みのない方は $${GITHUB_APP_PRIVATE_KEY} のように $ が2つになっているのを不思議に思うかもしれません。これはCloud Buildには代入変数というものがあり、$ が1つだと環境変数ではなくこの代入変数として扱われてしまうためです。そこで、$$ と書くことで環境変数だと明示することができます。なお代入変数は実行時に値に置き換わる環境変数とは違い実行前に値に置き換わる変数です。

シークレットの登録

Secret Managerに github-app-renovate-private-key という名前でGitHub Appの秘密鍵を登録します。

Cloud Buildトリガーの作成

以下の設定でトリガーを作成します。

  • Event
    • Manual invocation
  • Configuration
    • cloudbuild.yaml
  • Substitution variables
    • _GITHUB_APP_CLIENT_ID(GitHub AppのクライアントID)
    • _GITHUB_APP_INSTALLATION_ID(GitHub AppのインストールID)
  • Service account
    • 以下のロールを付与したサービスアカウントを指定
      • Cloud Build Editor
      • Secret Manager Secret Accessor
      • Logs Writer(cloudbuild.yamloptions.logging: CLOUD_LOGGING_ONLY を指定していない場合はCloud Storageを扱うロールも必要)

Cloud Schedulerの設定

作成したトリガーの編集画面にスケジューラーの作成ボタンがあるのでそこから設定します。

設定例:

平日のみ4時間おきに実行する設定。

  • Frequency
    • 0 */4 * * 1-5
  • Timezone
    • Japan Standard Time (JST)
  • Service account
    • 以下のロールを付与したサービスアカウントを指定
      • Cloud Build Editor
      • Service Account User

以上でセルフホスト自体の設定は完了です。

リポジトリの個別設定

次にRenovateの対象とするリポジトリの設定について説明していきます。

基本的には renovate.json を追加するだけなので設定例を示します。(なおJSON5もサポートされているのでコメントを書きたい場合などには renovate.json5 とすることも可能です。)

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "mergeConfidence:all-badges"
  ],
  "enabledManagers": ["gomod"],
  "labels": ["dependencies"],
  "packageRules": [
    {
      "matchDatasources": ["go"],
      "addLabels": ["go"],
      "postUpdateOptions": [
        "gomodTidy",
        "gomodUpdateImportPaths"
      ]
    },
    {
      "matchDatasources": ["golang-version"],
      "addLabels": ["go"]
    }
  ],
  "branchPrefix": "noi-renovate/",
  "branchPrefixOld": "noi-renovate/"
}

いくつか簡易的に説明しますが、詳細は以下のドキュメントを参照してください。

Configuration Options - Renovate Docs

  • extends
    • プリセットの設定を継承するためのオプション
    • config:recommended はそのまま推奨設定
    • mergeConfidence:all-badges はプルリクエストの説明にいくつかのバッジを表示するための設定
      • GitHub AppのRenovateではデフォルトで有効なのですがセルフホストだと無効なので明示的に設定しています
  • enabledManagers
    • Renovateはデフォルトだと対応している全てのパッケージマネージャの依存更新が有効なのでGoのみを対象にしています
  • packageRules
    • 細かい説明は割愛しますがGoの依存更新時に go mod tidy を実行したりパッケージ名が変更されている場合にimportを自動修正するための設定です
    • matchDatasourcesが gogolang-version の2種類ありますがそれぞれ以下のような場合にマッチします
      • go はgo.modのrequireディレクティブで指定された通常の依存更新の場合
      • golang-version はgo.modのtoolchainディレクティブで指定されたGo自体のバージョン更新の場合
  • branchPrefix & branchPrefixOld
    • Renovateが作成するブランチ名にはデフォルトだと renovate/ というプレフィックスがつきますがこれを変更するのがbranchPrefixです
    • Renovateは自身が作成したプルリクエストかどうかをこのプレフィックスから判断します
    • GitHub Appからセルフホストに移行するとユーザーが変わってしまう関係で移行前に作成されたプルリクエストの自動更新が動かなくなる場合があるためプレフィックスを変更することで移行前のプルリクエストを無視して新しく作り直すようにしています
    • branchPrefixOldの方は変更前のプレフィックスでクローズされたプルリクエストを再作成しないために用いられるオプションですが移行前と完全に独立させた方が分かりやすいと思いこちらも変更しました
    • つまり移行でない場合はプレフィックスを変更する必要はありません

renovate.jsonが追加された状態でRenovateが実行されると以下のような依存更新のプルリクエストを作成してくれます。

また初回実行時にはDependency Dashboardというイシューが作成され、そこでRenovateの作成したプルリクエストの一覧を確認したりいくつかの操作が可能になります。

参考までにここまでで説明したような設定のサンプルリポジトリを載せておきます。(cloudbuild.ymlとrenovate.jsonを両方を1つのリポジトリにまとめています。)

GitHub - noi/renovate-self-hosting at blog1-1

余談:自動生成コードを利用しているがそれをリポジトリに含まない場合の問題

ここまでで説明したような設定で基本的には問題ないのですが、ミラティブのバックエンドのリポジトリが自動生成コードをgitignoreしていることが要因となり期待通りのプルリクエストが作成されないという事象が発生しました。 具体的には、自動生成コードのみによってimportされているパッケージが go mod tidy によって削除されてしまったり、未生成の状態では存在しないパッケージがあると後述する go get に失敗してgo.modとgo.sumが不整合な状態になるという問題です。

おそらくこの問題を回避する正攻法は自動生成コードもリポジトリに含むようにする以外にないのですが無理やり回避する方法を見つけたのでおまけとして紹介します。 ただし望ましい方法ではありませんし、ミラティブでも自動生成コードをリポジトリに含むことを検討しているので推奨はしません。

まず、renovate.jsonに "goGetDirs": ["example.com/mod@none"] を追加します。 Renovateは go get -d -t ./... を実行するのですが「未生成の状態では存在しないパッケージ」があるとエラーになってしまいます。 goGetDirsはこの ./... の部分を置き換えるオプションなので go get -d -t example.com/mod@none として存在しないパッケージを削除する意味のない処理にすることでエラーを回避できます。(@none をつけたパッケージを go get すると削除してくれる。)

次に、postUpdateOptionsをpostUpgradeTasksに置き換えてコード生成コマンドなどを差し込み自前で go get -d -t ./...go mod tidy を行います。 postUpgradeTasksはセルフホスト専用のオプションで依存更新の後に任意のコマンドを差し込むためのものです。後ということはgo.modの変更後に実行されるので一旦 git stash してからコード生成して git stash pop するみたいな一工夫も必要になります。なお実行するコマンドはホワイトリスト式なのでcloudbuild.ymlに環境変数RENOVATE_ALLOWED_COMMANDSを追加する必要もあります。

実際の細かい設定の紹介は割愛するのでもしも興味があれば以下のサンプルリポジトリをご覧ください。

GitHub - noi/renovate-self-hosting at blog1-2

まとめ

本記事ではRenovateのセルフホストについて解説しました。今回はGoogle Cloudでの設定例を紹介しましたが別のプラットフォームへの置き換えも容易かと思いますのでどなたかの参考になれば幸いです。

RenovateはDependabotと比較して使用感という観点ではさほど大きな差異は感じていないのですが設定オプションが豊富でより細かい設定が可能という印象です。ただ逆に豊富すぎてどんなことができるのか把握しきれていないという側面もあるので運用していく上で理解を深めていきたいと思っています。

We are hiring!

ミラティブではバックエンドエンジニアを含め複数のポジションで力を貸してくださる方々を募集しています!そして、ミラティブの技術関連の情報は公式Xアカウント(@mirrativ_tech)にて随時発信していますので、ぜひフォローよろしくお願いします!

www.mirrativ.co.jp

hrmos.co

mirrativ.notion.site

speakerdeck.com