本記事はBASEアドベントカレンダー2024の13日目の記事です。
はじめに
BASE株式会社のなかの Pay ID という事業チームでエンジニアリングマネージャーをしている北川です。 Pay ID では、BASE で作られたショップでのより良いお買い物体験を提供するため、ショッピングアプリ「Pay ID」やあと払い決済「Pay ID あと払い」といったプロダクトを開発しています。
本日公開のPay IDテックリードの記事もぜひご覧ください。
今年もこれまでより良いプロダクトをユーザーに届けるべく、リリースを積み重ねてきましたが、とりわけモバイルアプリのリリースにおける自動化の取り組みについて紹介したいと思います。
1. リリース用の一時チャンネルを作ってもらう
現在 Pay IDアプリでは、明確なルールはないものの2週間に1度程度の頻度でリリースをしています。
リリースの際にはリリース時に必要な作業(動作確認やストア申請)の状況共有やチーム内外のコミュニケーションのためにそのリリース専用の一時的なチャンネルを作成しています。
その際に Slack のワークフローを使って、リリースのチャンネルを作るようにしています。
実際には Slack ワークフローで完結しておらず、Slack から Zapier に送信し、Zapier 経由でチャンネルを作成しています。
Zapier 経由で実行しているのは、ワークフロー作成当時には単独でチャンネルを作成することが難しかったことと、チャンネル作成と同じタイミングでリリース用の GitHub Pull Request を作成しているためです。
2. リリース用のPull Requestを作ってもらう
Pay IDアプリでは iOS・Android ともに CI には Circle CI をメインで利用していて、補助的に一部 GitHub Actions も併用しています。
iOS と Android で多少の違いはありますが基本的には素朴な GitFlow をベースにしており、何かしらのフィーチャをリリースする際は開発ブランチからリリースブランチを作成し、リリースブランチからメインブランチと開発ブランチに向けて Pull Request を作成します。
チャンネルを作成するタイミングでリリース用の PR も作って欲しいため、 Zapier 経由で Circle CIのワークフローをキックしています。
Zapier には Webhooks by zapier という機能が用意されており、オーソドックスなWeb API リクエストを実行することができます。
これを使って、 Circle CI の pipeline API に POST リクエストを送信しワークフローを開始します。
Circle CI の設定例としては、以下のように pipeline API にリクエストされたパラメータを取得して各ジョブに渡すようにすることで、任意のバージョンのリリース PR を作成することができます。
prepare_release: # API経由で実行する場合にだけ workflow_dispatch パラメータを付与するようにする when: << pipeline.parameters.workflow_dispatch >> jobs: - make_release_branch: version_name: << pipeline.parameters.release_version_name >> filters: branches: only: - development
3. テスト用のアプリを配布してもらう
Pay IDアプリでは社内向けの配布に DeployGate を利用しています。
配布操作に関するAPIが提供されており、 fastlane 向けの action や Android 向けの gradle プラグインも用意されているため、リリースのワークフローへの組み込みも容易です。
リリース時にしていることとしては、以下の2つです。
- リリースブランチに push された場合にアプリを配布する
- 任意のタイミングでアプリを配布する
前者については、配布ページという特定のバージョンのアプリがインストール可能な配布URLを作ることができる機能があり、「今回のリリース用の配布ページ」を作ることでリリース前の動作確認などに活用しています。
後者は先の例と同じように Circle CI のAPIを利用し、CI の中で DeployGate にアップロードしています(Android の例)。
Circle CI の中で渡されたパラメータを組み合わせてテストアプリをアップロードします。
deploygate: description: "DeployGateにアップロードする" parameters: deployment: type: string default: "Production" deployment_note: type: string default: "" steps: - attach_workspace: at: ~/project - run: *install_keystore - run: name: DeployGateへのアップロード command: | DEPLOYGATE_MESSAGE="<<parameters.deployment_note>> $COMMIT_MESSAGE / $(echo $CIRCLE_SHA1 | cut -c -7)" \ ./gradlew uploadDeployGateAab<<parameters.deployment>>
4. Androidで段階リリース中の場合は教えてもらう
こちらはまだ試験導入のステータスのものです。
Pay ID Androidアプリでは、Google Play Storeの段階的な公開機能を利用して、公開直後のアプリに問題が発覚した場合に公開ステータスを変更できるよう運用しています。
具体的には、99.9999% 状態で公開することで実質全ユーザーを対象にアップデートを公開することができます。Google Play Storeの現在の仕様として、公開の割合が 100% ではない段階的な公開の場合は、公開を停止することができます。もし問題が発覚し公開を停止した場合、現在完全に公開されているバージョンが最新となるため、新たに問題のあるバージョンをインストールするユーザーを抑制することが可能です。
この運用をする場合、次のリリースをする際には現在公開しているバージョンを99.9999%から100%にする必要があるのですが、その確認がメンバーの努力によるところとなっていました。
上記の問題に直面していた際、同じ課題への対処をされている下記の記事を見つけ感銘を受けました。
https://hack.nikkei.com/blog/app_release_status_bot/
この記事を参考に、アプリをGoogle Play StoreにアップロードするためのCIで公開ステータスを確認するようにし、まだ段階リリース中のものがあれば検知できるようにすることで仕組み化することができました。
今回の場合は CI のなかに組み込みたかったので、 GAS ではなく fastlane 経由で Google Play Publisher API を利用するようにしました。
元々、 fastlane には supply という Play Store へアップロードしたり、track の情報を参照するためのクライアントは存在するのですが、リリースステータスを参照するというピンポイントの action は用意されていません。そこで module を拡張しリリースとそのステータスを取得するようにしています。
まずは以下のように supply module を拡張します。
module Supply class Reader def track_release_statuses track = Supply.config[:track] client.begin_edit(package_name: Supply.config[:package_name]) # retrieve the track name, status, and fraction release_statuses = client.track_releases(track).map do |release| { name: release.name, status: release.status, fraction: release.user_fraction } end client.abort_current_edit if release_statuses.empty? UI.important("No release found in track '#{track}'") else UI.success("Found '#{release_statuses.join(', ')}' release statuses in track '#{track}'") end release_statuses end end end
次に新規の action を定義します。ここでは仮に GooglePlayTrackReleaseStatusesAction
と名づけ、 fastlane/actions/google_play_track_release_statuses.rb
に配置します。
module Fastlane module Actions class GooglePlayTrackReleaseStatusesAction < Action # Supply::Options.available_options keys that apply to this action. OPTIONS = [ :package_name, :track, :key, :issuer, :json_key, :json_key_data, :root_url, :timeout ] def self.run(params) require 'supply' require 'supply/options' require 'supply/reader' Supply.config = params release_statuses = Supply::Reader.new.track_release_statuses || [] return release_statuses.compact end def self.available_options require 'supply' require 'supply/options' Supply::Options.available_options.select do |option| OPTIONS.include?(option.key) end end def self.is_supported?(platform) platform == :android end # そのほかドキュメントなどは割愛 end end end
あとはこれを Fastfile から呼び出すだけで、100%に満たないリリースが存在するときに CI で検知することができるようになります。
desc "Check release statuses" lane :check_statuses do |params| release_statuses = google_play_track_release_statuses( track: 'production', ) # 一つでもリリースが完了していない場合は失敗とする if release_statuses.any? { |status| status[:status] != 'completed' } UI.user_error!('リリースが完了していないトラックがあります') end end
5. リリースから数日後にクラッシュレポートのリマインドをしてもらう
これはただのリマインダーの設定ですが、リリース直後には問題として発生しなくとも、リリース後数日にクラッシュなどが増えている場合もあるため導入しています。
iOS・Android ともにアプリを公開するとアプリストアからメールが届くため、そのメールを起点に Zapier 経由でリマインダーを Slack に投稿します。
ただしこの仕組みは App Store Connect や Google Play Console の仕様変更によって比較的壊れやすいので注意が必要です。
まとめ
Pay IDアプリでは Zapier や Circle CI などを組み合わせて、アプリのリリース時のオペレーションやその後の確認などを自動化しています。
これらは日々の課題や気づきから少しづつ改良を加えているもので、今後もより良い仕組みに改善しつつユーザーに価値を届けていきたいと思います。
最後に、Pay ID では一緒にプロダクトを作るメンバーを募集しています。
興味をお持ちいただけた方はぜひこちらの採用情報をご確認ください。
明日は tanden さんと oliver さんの記事になります。お楽しみに。