Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Sansan iOS アプリの CI / CD 事情

お久しぶりです。 Sansan iOS アプリエンジニアの中川です。

前回の記事では SwiftLint × Sider と SwiftFormat を使って、 Swift らしくリファクタリングする話を書きましたが、今回は CI / CD についてお話します。

buildersbox.corp-sansan.com

CI / CD って?

CI / CDは Continuous Integration / Continuous Delivery の略で日本語ではそれぞれ、継続的インテグレーション、継続的デリバリーと呼ばれています。 Kent Beck, Cynthia Andres による書籍 Extreme Programming Explained: Embrace Change にて発表された開発のプラクティスの一つに CI が含まれており、アジャイル開発の浸透とともに広まりました。

Extreme Programming Explained: Embrace Change (XP Series) (English Edition)

Extreme Programming Explained: Embrace Change (XP Series) (English Edition)

以下、原典を読んでまとめた概要です。

なぜ CI を導入するのか?

  • インテグレーションのステップは予測することが出来ませんが、変更前よりも時間がかかることがよくあります。インテグレーションを行うまでの時間が長くなればなるほど、より制約を受け、より予測不能なコストが増えるため、 CI で数時間以内に変更を結合してテストを実施することが大切です。

何を CI で行うのか?

  • コンポーネント間の結合による問題に関する洞察を得るため、毎晩最新のコードをビルドすること。 (夜間ビルド)
  • すべてのコンポーネントの結合を保証するため、週に一度、ビルドすること。 (結合ビルド)
  • 変更を登録すると、その後間もなく、ビルドシステムが変更を認識して、ビルドとテストを開始します。問題がある場合は通知すること。 (非同期的インテグレーション)
  • 作業が完了したら、次の作業に入る前にビルドとテストを開始し、問題なく完了するまで待つこと。 (同期的インテグレーション)

非同期的インテグレーション vs 同期的インテグレーション

  • 非同期的インテグレーションは最も一般的な様式である。
  • 著者は同期的インテグレーションを好むそうです。
    • ビルドとテストが問題なく完了するか待つ時間でペアプログラミングで何をしたのか、どのようにうまく出来たのかについて話す時間として利用します。
    • そうすることですぐに明確なフィードバックを得るタイミングができ、良い緊張感を得ることができます。
    • もし、結合を確認せずに、先に進めて 30 分後に問題が通知された場合、自分が行った変更を思い出し、問題を解決して、中断した作業で何をしていたのか思い出すのに多くの時間を費やしてしまいます。
    • 非同期的インテグレーションは毎日のビルドを大幅に改善したものですが、同期的インテグレーションにあるフィードバックを得る時間が組み込まれていないと述べています。

CI で満たすべき要件

  • やがて起きるシステムの最初のデプロイで大した問題とならないように十分に結合が完備されていること。

CD は CI を拡張したもので CI で実施されるビルド、テストが完了した後に環境へのリリース準備まで実施します。適切に導入すると、エンジニアはテストに合格し、デプロイ準備の整ったビルド成果物を常に得ることができます。

Sansan iOS アプリの CI / CD 事情

前置きはこれくらいにして、 Sansan iOS アプリでの CI / CD 事情をご紹介します。各サービス・ツールの導入方法については割愛し、どのように運用しているのかに焦点を置きます。

CI / CD サービス

Sansan iOS アプリでは Bitrise を採用しています。Bitrise についての概要は公式ドキュメントより引用させていただきます。

Bitriseはモバイルアプリ開発(iOS, Android, Xamarin, …)における継続的インテグレーション・デリバリー(CI/CD) プラットフォームをサービス(PaaS)として提供しています。ソフトウェアプロジェクトの開発・自動化を手助けするためのツール・サービスの集合体です。*1

CI / CD サービスは他にもありますが、私達が Bitrise を採用しているのは iOS のサポートが手厚いことが大きいです。 Bitrise が提供している codesigndoc を利用して、コード署名ファイルの収集とエクスポートをした後、 Bitrise.io へコード署名用のファイルをアップロードするだけで利用可能になるし、プロビジョニングもいくつかの条件を満たせば、自動で行ってくれますし、満たせなくても手動の設定を行うことで簡単に IPA ファイルをエクスポートすることが出来ます。*2

github.com

また、 iOS は OS バージョンと IDE である Xcode が密接に結合しており、 OS バージョンアップがある度に現行の iOS バージョンをエクスポートできる Xcode と次バージョンの iOS バージョンをエクスポートできる Xcode を Mac に用意して対応を行うというのは iOS アプリエンジニアなら誰しも通る道かと思います。

f:id:ynakagawa33:20190630045632p:plain
増殖する Xcode

Bitrise ではスタック*3と呼ばれる機能でこの問題に対応しており、ビルドに利用するスタックを選択することで Xcode のバージョンを切り替えて、 IPA を出力できるようになります。

Sansan iOS アプリでは直近、 Swift5 対応をしていることもあり、 Swift5 対応用のビルドでは Xcode 10.2 がインストールされたスタックを利用し、それ以外のビルドでは Xcode 10.1 がインストールされたスタックを利用するように設定し、 OS や言語アップデートと機能開発を平行に進めても、 CI / CD の恩恵を受けられるような構成しています。

それ以外には充実した公式ドキュメントの bitrise Docs 、コミュニティーや Bitrise コアチームとやり取りができ、過去のナレッジが共有されている Bitrise Discussions とサービス利用する上でのサポートも手厚いし、公式ドキュメントの翻訳も日本語が対応されており、日本へ注力されている点も好感が持てました。

ワークフロー一覧

Sansan iOS アプリでは Bitrise で下記のワークフローを自動化してます。

ワークフロー名 概要
_run_from_repo GitHub でコード管理している bitrise.yml で実行するための Utility workflow*4
primary Pull Request や Push によってトリガーされ、ビルド、テスト、アーカイブとエクスポートを担保し、 IPA ファイルを Bitrise.ioDeployGate へ配信する
primary-swift5 Swift 5 対応用の primary ワークフロー
release BOT によって作られた PR からトリガーされ、ビルド、テスト、アーカイブとエクスポートを担保し、 IPA ファイルを Bitrise.ioApp Store Connect へ配信する
delivery-dev QA チームやサーバーサイドチームに対して開発環境に向けた Sansan iOS アプリを用意するためのワークフロー。 Slack Slash Command*5 または、手動ビルド*6から配信したいブランチを指定して実行する。ビルド、アーカイブとエクスポートした IPA ファイルを Bitrise.ioDeployGate へ配信する
delivery-stg QA チームやサーバーサイドチームに対してステージング環境に向けた Sansan iOS アプリを用意するためのワークフロー。 Slack Slash Command または、手動ビルドから配信したいブランチを指定して実行する。ビルド、アーカイブとエクスポートした IPA ファイルを Bitrise.ioDeployGate へ配信する
delivery-prod QA チームやサーバーサイドチームに対して本番環境に向けた Sansan iOS アプリを用意するためのワークフロー。 Slack Slash Command または、手動ビルドから配信したいブランチを指定して実行する。ビルド、アーカイブとエクスポートした IPA ファイルを Bitrise.ioDeployGate へ配信する

各ワークフローで利用しているステップ

Bitrise 公式やユーザコミュニティによるステップの設定方法については各ステップの説明をご確認ください。ここでは Sansan iOS アプリの各ワークフローでどのステップが利用されているのかを紹介します。ステップは各ワークフローでの実施順で並んでいます。

ステップ名 primary release delivery-{env}
Activate SSH key (RSA private key)
Git Clone Repository
Script (リポジトリにある bitrise.yml を利用して、ビルドを実行する *7 )
Send a Slack message (通知用チャンネルへビルド開始通知) - -
Script (ワークフロー毎に切り替えたい設定を環境設定経由で設定する*8 )
Script (リポジトリにある Bootstrap スクリプトを実行する)
Script (Bitrise.io の Secrets *9にある環境変数を設定に注入する)
Script (手動ビルドで渡されたカスタム環境変数*10で設定を上書きする
Certificate and profile installer
Script (App Group の ID をリリース用のものに置き換え) - -
Xcode Test for iOS - -
Xcode Archive & Export for iOS
Deploy to Bitrise.io - Apps, Logs, Artifacts
Deploy to iTunes Connect - Application Loader - -
DeployGate Upload -
Script (DeployGate の配布ページへの QR コードを作るために利用しているツールのインストール) -
Script (DeployGate Upload ステップで得られた結果を他ステップで利用するために環境変数へ追加する) -
Create install page QR code (DeployGate の配布ページへの QR コードを作るために利用) -
Send a Slack message (QR コード共有用チャンネルへ通知) -
Send a Slack message (ビルド失敗時のみ*11通知用チャンネルへ通知) -
Send a Slack message (通知用チャンネルへビルド完了通知) - -

Tips: Bitrise の Utility workflow は便利

先ほど紹介した表を見ると、全ワークフローで共通のステップがあったり、1 つのワークフローでしか実行しないステップがあったりと Sansan iOS アプリのワークフローは複雑です。このまま、各ワークフローで個別に定義をしているとあるワークフローを変更した際にすべてのワークフローを変更しなければならなくなり、変更漏れや変更ミスによってワークフローが壊れてしまうかもしれません。

このような問題に私達は Utility workflow を活用して解決しています。 Utility workflow とは _setup のようなアンダースコア始まりで命名されたワークフローのことで Utility workflow として定義をするとbitrise run コマンドで直接実行できなくなります。 Utility workflow は通常のワークフロー内に before_runafter_run として bitrise.yml に定義されます。

Bitrise Workflow Editor*12 でも対応されており、 GUI で簡単に Utility workflow を利用できるようになっています。

  1. 通常のワークフローを作る
  2. アンダースコア始まりで命名した Utility workflow を作る
  3. Bitrise Workflow Editor で Add Workflow before / Add Workflow after / Rearrange で通常のワークフロー内で Utility workflow を実行したい順に並び替える

f:id:ynakagawa33:20190630092109p:plain
Bitrise.io で Utility workflow を利用する

Utility workflow 一覧

それでは、先ほどの複雑なワークフローからどのような単位で Utility workflow を定義しているのか説明します。

Utility workflow 名 ステップ名一覧 概要
_run_from_repo Activate SSH key (RSA private key)
Git Clone Repository
Script (リポジトリにある bitrise.yml を利用して、ビルドを実行する)
Bitrise.io でリポジトリにある bitrise.yml を実行する
_shared_preparation Script (リポジトリにある Bootstrap スクリプトを実行する)
Script (Bitrise.io の Secrets にある環境変数を設定に注入する)
Script (手動ビルドで渡されたカスタム環境変数で設定を上書きする
Certificate and profile installer
全ワークフローで共通の準備を行う
_release_preparation Script (App Group の ID をリリース用のものに置き換え) release ワークフローのみでリリース用の準備を行う
_test Xcode Test for iOS primary ワークフローのみでテストを行う
_shared_delivery Xcode Archive & Export for iOS
Deploy to Bitrise.io - Apps, Logs, Artifacts
全ワークフローで共通のデリバリーを行う
_deploygate_deploy DeployGate Upload primary, deploy-{env} ワークフローで DeployGate へデリバリーを行う
_appstore_delivery Deploy to iTunes Connect - Application Loader release ワークフローで AppStore へデリバリーを行う
_notify_deploygate_distribution_info Script (DeployGate の配布ページへの QR コードを作るために利用しているツールのインストール)
Script (DeployGate Upload ステップで得られた結果を他ステップで利用するために環境変数へ追加する)
Create install page QR code (DeployGate の配布ページへの QR コードを作るために利用)
Send a Slack message (QR コード共有用チャンネルへ通知)
primary, deploy-{env} ワークフローで DeployGate の配布情報を Slack で通知する
_notify_on_failed_only (ビルド失敗時のみ通知用チャンネルへ通知) primary, deploy-{env} ワークフローでビルド失敗時のみ通知用チャンネルへ通知する
_notify Send a Slack message (通知用チャンネルへビルド完了通知) release ワークフローのみでビルド結果に関わらず通知用チャンネルへ通知する

最終的なワークフロー定義

Utility workflow を活用して、以下のようになりました。ステップのみで定義していたものと比べて、複雑さが減り、共有されている Utility workflow の変更はどれか 1 つを変更すれば良いようになりました。

ステップ名、 Utility workflow 名 primary release delivery-{env}
_run_from_repo
Send a Slack message (通知用チャンネルへビルド開始通知) - -
Script (ワークフロー毎に切り替えたい設定を環境設定経由で設定する)
_shared_preparation
_release_preparation - -
_test - -
_shared_delivery
_deploygate_deploy -
_appstore_delivery - -
_notify_deploygate_distribution_info -
_notify_on_failed_only -
_notify - -

最後の一言

今回の記事では CI / CD の概要から Sansan iOS アプリの CI / CD の現状を紹介しました。 みなさまの CI / CD ライフをより良いものにするひとつのきっかけになってくれれば幸いです。

*1:公式ドキュメントより引用 : Bitriseとは何ですか? - bitrise Docs

*2:充実したコード署名に関する公式ドキュメント。コード署名だけで 12 個もドキュメントがある : Bitrise上でのiOSコード署名 - bitrise Docs

*3:スタックに関する公式ドキュメント : 利用可能なスタック - bitrise Docs

*4:Utility workflow に関する公式ドキュメント : Workflows in YAML - bitrise Docs

*5:Slack Slash Command で Bitrise のビルドを実行する方法に関する公式ドキュメント : Slack webhookの追加 - bitrise Docs

*6:手動ビルドのやり方に関する公式ドキュメント : 手動でビルドを開始する - bitrise Docs

*7:Use bitrise.yml from repository - bitrise Docs

*8:Exposing env vars and using them in another step - bitrise Docs

*9:秘密と環境設定 - bitrise Docs

*10:手動でビルドを開始する - bitrise Docs

*11:Enable/Disable a step (optionally, based on a condition) - bitrise Docs

*12:github.com

© Sansan, Inc.