RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

祈らないライブラリアップデート(フロントエンド編)

Dependabot x GitHub Actions x Sentry

はじめに:アップデートできていますか?

作ったきりで一度もライブラリをアップデートしていない、そんなプロダクトも多いのではないでしょうか。新規ライブラリの利用が古いライブラリへの依存により実現しないことなど、損失が発生するケースは多々あります。脆弱性対策のアップデートをして、新たな不具合を発生させていては本末転倒です。それらを理解していても、必要に迫られるまでアップデートは後回しにされることが多いと思います。しかし、アップデートが必要になった時には大規模アップデートとなり、エラーと隣り合わせの状態で、祈りながらリリースされることも多いのではないでしょうか?

本記事では Dependabot、GitHub Actions、Sentry を利用してアップデートを半自動化し、実現可能な工数で祈らず継続的にアップデートする手法を提案します。

本記事のまとめ

以下の 3 ステップでの継続的なアップデートを提案します。

  • 情報を集める:Dependabot を設定する
  • アップデートを検証する:ビルド差分を比較する
  • 監視する:Sentry でエラーを検知する

情報を集める:Dependabot を設定する

まず、アップデート情報を定期的に取得する必要があります。これに関しては優れた解決策がすでに用意されています。筆者のチームでは GitHub 公式の Dependabot を利用することにしました。Dependabot は更新を通知してくれるだけではなく、リリースノートが記載されたアップデート Pull Request の作成まで行ってくれます。

Bump TypeScript

公式 https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates

アップデートを検証する:ビルド差分を比較する

Dependabot が Pull Request を作成してくれるとしても、全ての Pull Request のリリースノートを読み影響範囲を特定しテストを行うとすると、検証にかなり多くの工数を割くことになります。

そこで、コードベースでアップデートを検証する仕組みを整え、一部のアップデートについては自動検証を可能にしました。

フロントエンドではユーザーに配信するファイル(ビルド成果物)が全く同じであれば、リグレッションが発生しないことの証明になります。さらに、多くのビルド環境では Tree shaking (Dead Code Elimination) の仕組みがあり、ライブラリの利用していない部分の変更に関しては、ビルド成果物には反映されません。つまり、実際には多くのライブラリアップデートはビルド成果物をコードベースで比較し差分がないことをもって検証を完了できるのです。*1

筆者のチームでは Dependabot の作成した Pull Request に対して、アップデート前後のビルド成果物を比較する CI を設定し、それが通過していればそのままリリースに進める運用としました。実際にチームのプロダクトでは 120 件あったマイナーアップデートの内、73 件は差分が生じておらず、人力での検証をスキップしてリリースできました。

実際には GitHub Actions で以下のように実装しました。*2

name: Dependabot diff

on:
  push:
    branches:
      - 'dependabot/npm_and_yarn/**'

jobs:
  diff:
    timeout-minutes: 10
    if: github.actor == 'dependabot[bot]'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: '16'

      # アップデート後のビルド
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.ref }}
      - name: Install
        run: |
          npm ci
      - name: Build
        run: |
          npm run build
          mv build /tmp/head

      # アップデート前のビルド
      - uses: actions/checkout@v3
        with:
          ref: main
      - name: Install
        run: |
          npm ci
      - name: Build
        run: |
          npm run build
          mv build /tmp/base

      # 比較
      - name: Diff
        run: |
          diff -r /tmp/head /tmp/base

監視する:Sentry でエラーを検知する

実際に運用してきた中で、リリースノートを読み影響範囲を調査し修正を加えたとしても、エラーがないことの確証を得られないケースは多くありました。また、Tree shaking 可能な形で配信されておらずアップデートの度に差分が生じてしまうライブラリも多いです。そこで、そのようなアップデートに関しては、社内環境にリリースして1週間程度監視した後、新規エラーが検出されていないことを確認できれば社外向けにリリース可とする運用にしました。

エラー検知には Sentry を使用しました。設定すればアプリケーション内で発生したエラーを検知して送信、集計、レポートを作成することまで自動化することができます。

参考: docs.sentry.io techblog.cartaholdings.co.jp

おわりに

以上、祈らないアップデートを提案しました。筆者のチームでは、実際にこれらの仕組みを導入してから半年以上継続的にアップデートが行われています。一部メジャーバージョンの更新を除き、ほとんどのライブラリが最新になっています。

継続的なアップデートを!

*1:Dev dependencies も成果物に影響を与えないことが多いです。アップデートで知らないうちに開発環境を壊してしまうと困るので、しっかり Formatter や Linter も CI で実行するようにしています。

*2:pull_request をトリガーにした場合ビルド対象を base head の変数で参照可能です。しかし、全ての Pull Request に反応して Action が起動されスキップしたログが残るのが気になったので、トリガーはブランチ名フィルタを指定することにしました。