GitHub Actions Unleash Feature Flag

GitHub Actionsを使ってFeature Flagの削除漏れを通知する仕組みを作りました

GitHub Actions Unleash Feature Flag

こんにちは。SUZURI事業部の@kromiiiです。

今月は月初に出したこの記事に加えて、二本目の記事となります。

今回は、GitHub Actionsを使ってFeature flagの削除漏れを通知する仕組みを構築した話をします。

Feature flag について

Feature flagとは、ソフトウェアの機能をコードの変更やデプロイなしに動的に有効・無効にできる開発手法です。代表的なツールとしてはLaunchDarklyFirebase Remote Configがありますが、ペパボではオープンソースのFeature flagソフトウェアであるUnleashをよく使用しています。

SUZURIでのfeature flagの導入については、過去のテックブログに詳しく書いてありますので、ぜひご覧ください。

技術的負債への懸念

Feature flagを導入することで多くのメリットがありますが、使い終わったFeature flagをコード上に残しておくと「技術的負債」になりやすいです。具体的には以下のような問題が発生します。

  • flagを参照するたびにFeature flagサーバーに余計なリクエストが発生する
  • Feature flagサーバーへの接続ができなくなるとデフォルト値が使用され、一時的に意図しない挙動になることがある

実際にSUZURIでも、過去にUnleashサーバーとの接続ができなくなったことが原因で本番環境で障害が発生した事例がありました。そのため、安定稼働したflagはコードから削除し、Unleashのサーバーからアーカイブすることが公式に推奨されているベストプラクティスとなっています。

これまでの対策

この問題に対する一般的なアプローチは、定期的にUnleashの管理画面を見に行ってinsightsというステータスをチェックすることです。ここを見れば、使われていないflag(potentially stale)がどの程度あるのか、そしてそれが全体のflagのうちどの程度を占めているのかがわかります。

insights

とはいえ、こうした作業をチーム全体で習慣化させるのは難しく、やる気のある人に任せる状態になりがちです。

さらにもう一歩進んだ事例として、サイバーエージェントさんの事例があります。これはFeature flagを使っている箇所にあらかじめコメントを埋め込むことで、CIを通して技術的負債になる前に使われていないフラグを警告する仕組みです。このアプローチを使えば、flagが負債化する前にエンジニアが気づいてコードを修正できますが、Ruby言語に対応していない点と、あらかじめコメントを埋め込まないと発動しない点が、私たちの環境では少し難点かなと思いました。

提案手法

UnleashはAPI機能も提供しており、API経由でflagの状態を取得できます。これを使えば、コメントを埋め込むことなくstaleになったflag(=使われていないflag)を取得できると考え、まずはCLIツールを作成しました。

使用方法は以下の通りです。

$ ./stale-flag-detector
Stale flags:
- unleash-ai-example-stale
- another-stale-flag

$ ./stale-flag-detector を実行すると、環境変数に設定されたUnleash APIを参照してstaleなflagをターミナルに出力します。

詳しい使い方は私の個人ブログをご覧ください。

特に難しいことはしていませんが、工夫ポイントとしてはpotentially staleなフラグについてはそのままだと情報が返ってこないので、created_atの時刻から現在時刻を引いて対応しました。

これをGitHub Actionsに組み込むことで、定期的にstaleなflagをチェックし、開発チームに通知することが可能になります。

name: Stale Flag Detector

on:
  schedule:
    - cron: '0 0 * * 1'
  workflow_dispatch:

jobs:
  stale-flag-detection:
    permissions:
      issues: write
      contents: read
    runs-on: normal
    steps:
      - uses: actions/checkout@v3
      - name: Get latest release
        id: get_release
        shell: bash
        run: |
          OS=$(echo $RUNNER_OS | tr '[:upper:]' '[:lower:]')
          ARCH=$(echo $RUNNER_ARCH | tr '[:upper:]' '[:lower:]')
          if [ "$ARCH" == "x64" ]; then
            ARCH="amd64"
          fi
          RELEASE_INFO=$(curl -s https://api.github.com/repos/kromiii/stale-flag-detector/releases/latest)
          echo "API レスポンス:"
          echo "$RELEASE_INFO"
          if [ -z "$RELEASE_INFO" ]; then
            echo "エラー: GitHub APIからのレスポンスが空です"
            exit 1
          fi
          ASSET_URL=$(echo "$RELEASE_INFO" | tr -d '\000-\037' | jq -r ".assets[] | select(.name | contains(\"$OS\") and contains(\"$ARCH\")) | .browser_download_url")
          if [ -z "$ASSET_URL" ]; then
            echo "エラー: $OS-$ARCH に一致するアセットが見つかりません"
            exit 1
          fi
          echo "asset_url=$ASSET_URL" >> $GITHUB_OUTPUT
          echo "version=$(echo "$RELEASE_INFO" | jq -r .tag_name)" >> $GITHUB_OUTPUT
      - name: Download stale flag detector
        run: |
          sudo apt-get update && sudo apt-get install -y wget
          wget ${{ steps.get_release.outputs.asset_url }} -O stale-flag-detector.tar.gz
          tar -xzf stale-flag-detector.tar.gz
          chmod +x stale-flag-detector
      - name: Run stale flag detector
        env:
          UNLEASH_API_ENDPOINT: ${{ secrets.UNLEASH_API_ENDPOINT }}
          UNLEASH_API_TOKEN: ${{ secrets.UNLEASH_API_TOKEN }}
        run: |
          # stale flag detectorを実行し、結果を環境変数に設定
          OUTPUT=$(./stale-flag-detector)
          if [[ "$OUTPUT" == "No stale flags"* ]]; then
            echo "古いフラグは検出されませんでした。処理を終了します。"
            exit 0
          fi
          echo "STALE_FLAGS<<EOF" >> $GITHUB_ENV
          echo "$OUTPUT" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
      - name: Create Issue
        if: env.STALE_FLAGS != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          STALE_FLAGS: ${{ env.STALE_FLAGS }}
        run: |
          ISSUE_BODY=$(cat << EOF
          # Stale Flag Detector レポート

          以下のUnleash Flagがstaleになっていることが検出されました:

          ## 検出されたstale flags

          ${{ env.STALE_FLAGS }}

          ## アクション項目

          1. 上記のflagの使用状況を確認してください。
          2. 必要なflagは更新し、不要なflagは削除してください。
          3. このissueを解決する前に、すべてのstale flagに対処したことを確認してください。

          ご質問やご不明な点がありましたら、このissueにコメントしてください。
          EOF
          )
          
          curl --request POST \
          --url https://[GHE_URL]/api/v3/repos/${{ github.repository }}/issues \
          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
          --header 'content-type: application/json' \
          --data '{
            "title": "Staleになっている Unleash Flag のレポート",
            "body": "'"${ISSUE_BODY//$'\n'/\\n}"'",
            "labels": ["stale-flag", "maintenance"]
          }'
        shell: bash

このGitHub Actionsワークフローの主な機能と特徴は以下の通りです:

  1. スケジュール実行:毎週月曜日の午前0時に自動実行されます。また、手動でも実行可能です。
  2. 最新リリースの取得:stale-flag-detectorの最新バージョンをGitHubリリースから取得します。
  3. 環境に応じたバイナリのダウンロード:ランナーのOS(Linux/macOS/Windows)とアーキテクチャ(amd64/arm64)に合わせて適切なバイナリをダウンロードします。
  4. Unleash APIとの連携:環境変数を通じてUnleash APIのエンドポイントとトークンを安全に利用します。
  5. stale flagの検出:stale-flag-detectorを実行し、使用されていないフラグを検出します。
  6. GitHubイシューの作成:検出されたstale flagの情報を含むイシューを自動生成します。イシューにはラベルも付与され、チームの注意を引きやすくなっています。

このワークフローにより、定期的かつ自動的にstaleなflagを検出し、開発チームに通知することができます。これによって、feature flagの管理を効率化し、技術的負債の蓄積を防ぐことができます。 Issue を立てる時は、本当は既存のGitHub Actionsを使いたかったのですが、会社でGHEを使っている都合上、curlを直接叩く形式にしています。

このActionsによって作成されたイシューの例がこちらです。

issue

ペパボではGHEとslackが連携されているのでこんな感じでSlack通知も来ます。

slack

このような取り組みを通じてSuzuriのunleashのhealth rateは16%から62%まで改善しました。

取り組み前

pre

取り組み後

post

100%を目指すと、運用よりもルールに縛られてしまう感が出てしまうので、それぞれで落とし所を見つけるのが実際のところだと思います。

今後の方向性

今後の方向性として、以下の2点を考えています。

  1. Open Feature への対応
  2. LLMによる自動修正の実現

1については、現在Unleashなど複数のFeature flagの仕様を共通化したOpenFeatureというものが作られており、これに対応したいと考えています。

2については、単に通知するだけでなく、修正までできたらいいということで、これをLLMにやらせられないかと考えています。

しかし、コードを書き換えさせる部分の精度がうまくいかず、なかなか苦労しています。

まとめ

今回は、GitHub Actionsを使ってFeature flagの削除もれを通知する仕組みを構築しました。

この仕組みを導入することで、定期的にstaleなflagを検出し、開発チームに通知することができます。

また、今後はOpenFeatureへの対応とLLMによる自動修正の実現を目指していきます。

というわけで、皆さんも使われていないUnleash flagを定期的に掃除することで、健康なfeature flag lifeを送りましょう!