ゲスト投稿 — David Davidov(Posh創業エンジニア)
これは、Posh(イベントを通じて主催者がコミュニティを作り、スケールし、マネタイズできるリアル体験のソーシャルマーケットプレイス)で創業エンジニアを務めるDavid Davidovによるゲスト投稿です。
数週間にわたる長いアプリ審査サイクルやタイムセンシティブな手動リリースに悩まされた結果、我々のチームはモバイルのCI/CDパイプラインを再構築し、可能な限り手動作業を排除する投資を行いました。参考までに、当社のアプリはiOSとAndroidで何百万ものユーザーにサービスを提供しているため、ストアへのリリースを安全かつ確実に、かつ予定通りに行うことは常に最優先事項でした。その投資は実を結び、リリース速度の改善はApp StoreのEntertainmentカテゴリでNo.1アプリを獲得する成長を支えるのに役立ちました。
Poshが大手と競う中で判明した課題
モバイルアプリの開発を始めてから2年以上、ストア経由のリリースという手動プロセスがずっと嫌いでした。我々はExpo Updatesを知っていましたが、fingerprintingやEAS Workflowsのpre-packaged jobs以前は、OTAアップデートの自動化はどこか不安定でエラーの余地が大きいと感じていました。
直近2年間で直面した主な痛点は次のとおりです。
- Linear上のタスクが特定のアプリバージョンにいつリリースされたかの可視化がない。
- 週替わりでRelease Trainマネージャーをまわす必要があり、その人がその週の変更セットのリリース責任を負っていた ← これが最大のボトルネック
- リリースプロセスへのE2Eテストの自動統合
- 維持が大変な不安定なGitHub Actionsでビルドや提出をキックしていた
- エンジニアのPRが新しいネイティブ変更を導入したかどうかを理解する手段がない
これらすべての痛点が、モバイル上での開発をクリエイティブで高速な体験というよりは雑務のように感じさせていました。Expoのfingerprinting、workflows、OTAアップデートに関する進化を改めて見直した後、私はリリースシステムを根本から再設計し、重要な手動承認ポイントは残しつつ可能な限り自動化することにしました。この自動化により、我々は週次の手動リリースからほぼ毎月のストアリリースと週に複数回のOTAアップデートへと移行できました。
解決したかったこと
- PRで導入された変更をインストール・テストする簡単な方法、かつそのPRがネイティブの変更を導入したかどうかを理解できること
- EASビルドを過剰にトリガーしないシステム。テストを開始する準備ができたときだけ起動する
- OTAアップデートを段階的に導入し、ステージングでテスト可能にすること(masterにマージされた変更が自動的に本番にデプロイされないようにする)
- ネイティブ変更がmasterに入ったときだけ新しいバージョンブランチを作る(常にブランチを増やす必要を減らす)
- 本番に新しいバージョンがリリースされたときに、チームに通知してそのバージョンに含まれる変更を自動で伝えることを排除する
以下では、本番リリースにかける時間を減らすために我々が行ったことを紹介します。
前提条件
- Betoの「アプリ環境を分割する」ビデオを見ることを推奨します。これは特定のupdate channels / app environmentsを設定する際に必要になります。チュートリアルの最後には1台のデバイスにdevelopment、preview/staging、productionビルドをインストールできるようになります。これによりビルド間の切り替えが簡単になり、アプリのテストが容易になります。
- プロジェクトに対してExpo UpdatesをExpo Fingerprintsで設定してください。
Automation 1: PRプレビューの設定
これはCI/CDワークフローに必須ではありませんが、特にagenticなワークフローを好むチームでは変更のレビューを速めるのに役立ちます。この記事を読んで刺激を受け、私はQRコードでPRにコメントしてdevelopmentアプリに新しい変更をインストールできるEAS workflowを作成しました。コードの詳細には深入りしませんが、このワークフローでいくつか変更した点は次の通りです。
- fingerprintが見つからなければアプリをビルドしない(developmentにOTAできないため)。クレジット消費が膨らむと思ったのでこの判断にしました。将来的にエージェントを使ったパッケージ更新を自動化したくなれば変更するかもしれません。
- 新しいネイティブコードを導入するPRには
needs new build ラベルを付与する。これはGitHub PRのフィルタリング用です(ラベルは私のお気に入りです)。
- Expoの例にあるコメントをカスタムに編集。いくつかの手順、当該fingerprintの最近のビルドへのリンク、トラブルシューティング手順、大きめのQRコードを追加しました(画面で目立つように)。
PR Review [object Object]
PR Review workflow [object Object]
これにより、DevinやClaudeに特定箇所の修正を依頼したときにすぐにアプリをフラッシュして何が変わったかを把握できます。外出先でもレビューして出荷できるようになりました🏃♂️✨
Automation 2: OTAブランチ
いくつかのPRをmasterにマージしたとします。それらのPRが自動的に本番へデプロイされるのは避けたいです。OTAブランチを作ることで、各PRがOTAをトリガーする代わりに複数の変更をまとめてOTAをバッチ化する機会を得られます。
OTAブランチでは2つのワークフローが必要です。
例は次のようになります(YAML):
name: OTA Preview
on:
push:
branches: ['ota_update_*']
jobs:
fingerprint:
name: Fingerprint
type: fingerprint
environment: preview
publish_preview_android:
name: Publish Android preview OTA
needs: [fingerprint]
type: update
environment: preview
params:
platform: android
branch: preview
publish_preview_ios:
name: Publish iOS preview OTA
needs: [fingerprint]
type: update
environment: preview
params:
platform: ios
branch: preview
OTA Update branch [object Object]
PR preview workflow [object Object]
OTAブランチ内の差分は基本的にchangelogの更新だけにするべきです。例:
- Sample JS Changes white again
- Sample JS Changes green again
- Sample JS Changes
- Sample JS Changes
- Sample JS Changes
- Sample JS Changes
最後に、changelogの使い方に関しては後の自動化セクションで再度触れます。
Automation 3: Versionブランチ
PRで新しいネイティブ変更が入ったとします。その場合、進行中のOTAブランチをクローズ(無効化)して、新しいversionブランチを立ち上げることを期待します。デプロイされるまでそのブランチを使います。これがエンジニアチームにとって、ストア向けの新しいリリースを作る必要があるかどうかの真実のソースになります。
VersionブランチでもOTAと同様に2つのワークフローを用意します。
例(YAML):
name: Version Branch Builds
on:
pull_request:
types: [labeled]
concurrency:
cancel_in_progress: true
group: ${{ workflow.filename }}-${{ github.ref }}
jobs:
build_production_android:
name: Build Production Android
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: production
params:
platform: android
profile: production
build_production_ios:
name: Build Production iOS
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: production
params:
platform: ios
profile: production
build_preview_android:
name: Build Preview Android
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: preview
params:
platform: android
profile: preview
build_preview_ios:
name: Build Preview iOS
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: preview
params:
platform: ios
profile: preview
build_development_android:
name: Build Development Android
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: development
params:
platform: android
profile: development
build_development_ios:
name: Build Development iOS
if: ${{ github.event.label.name == 'build and submit' }}
type: build
environment: development
params:
platform: ios
profile: development
submit_production_android:
name: Submit Production Android
needs: [build_production_android]
type: submit
environment: production
params:
build_id: ${{ needs.build_production_android.outputs.build_id }}
profile: production
submit_production_ios:
name: Submit Production iOS
needs: [build_production_ios]
type: submit
environment: production
params:
build_id: ${{ needs.build_production_ios.outputs.build_id }}
profile: production
submit_preview_android:
name: Submit Preview Android
needs: [build_preview_android]
type: submit
environment: preview
params:
build_id: ${{ needs.build_preview_android.outputs.build_id }}
profile: preview
submit_preview_ios:
name: Submit Preview iOS
needs: [build_preview_ios]
type: submit
environment: preview
params:
build_id: ${{ needs.build_preview_ios.outputs.build_id }}
profile: preview
notify_testers:
name: Notify QA Team
needs: [build_preview_android, build_preview_ios, submit_preview_android, submit_preview_ios]
environment: preview
steps:
- name: Send Slack message
run: |
# Notify your QA team that new preview builds are available.
# Include whatever your team needs, such as build numbers,
# install links, release notes, or testing instructions.
comment_builds:
name: Post Build Links
needs: [ build_production_android, build_production_ios, build_preview_android, build_preview_ios, build_development_android, build_development_ios ]
environment: production
steps:
- name: Post comment on PR
run: |
# Post a PR comment with links to the generated EAS builds.
# The implementation depends on how your CI environment exposes
# repository, pull request, and authentication context.
workflow graph [object Object]
PRには新しいバージョン番号を反映したchangelogとmobile appのpackage.jsonの変更が入るはずです。手動の部分はこれで提出して審査に出し、リリースボタンを押すことだけになります。バンドル以外の変更が含まれる可能性があるため、我々はその部分を自動化しない方針です。
Automation 4: masterワークフロー
この段階で、masterワークフローは次を担うべきです。
- PRがマージされるたびにOTAブランチを作成・リベースする
- PRがマージされるたびにversionブランチを作成・リベースし、重複するOTAブランチを閉じる
- OTAまたはversionブランチがマージされたときにGitHub上で新しいリリースを作成する
例(YAML):
name: Production Deploy
on:
push:
branches: ['main']
concurrency:
cancel_in_progress: true
group: ${{ workflow.filename }}-${{ github.ref }}
jobs:
fingerprint:
name: Fingerprint
type: fingerprint
environment: production
get_android_build:
name: Check Android build
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
profile: produc
(注: 上記は抜粋です。実際のファイルでは更に多くのステップとエラーハンドリングがあります。)
これらの自動化を組み合わせることで、我々はリリースにかかる人的コストを大幅に削減し、頻繁なOTAと計画されたストア提出の両方を安全に実行できるようになりました。興味があれば、具体的なワークフローの全文やスクリプト例も共有します。