JenkinsとfastlaneでiOSビルド環境を構築@Mac mini


前置き

AndroidをEC2インスタンス(Linux)のJenkinsでビルドしていたので、同じようにiOSもビルドしたい。
しかしAndroidはソース書いてビルドもしているのでなんとかJenkinsの設定もできるが、iOS開発はほとんどわからない。自分の開発環境にiOS開発環境をセットアップしたぐらいだ。

そんな状態のほとんどAndroid開発しかしたことない自分でもfastlaneを使えばiOSも簡単にビルド自動化ができたので、Jenkins × fastlane × Mac miniの構成をご紹介します。

構成

iOSのビルドにはMacが必要なので、そのままLinuxのインスタンス上のJenkinsでは出来ない。
そのため、JNLPというJavaのサービスを利用してEC2のJenkinsとローカルの机の上のMac miniちゃんを連携してビルドできるようにする。

  • iOSをビルドするMac miniにはJenkinsはセットアップする必要は無い。fastlaneを使ってiOSのビルドに詳しくなくても楽チン
  • ビルド自体の他に以下も行ってCI/CD(CDだけかな?)を回して、社内でipaの検証サイクルを効率化できた。
    • ビルドしたらipaをS3にアップロードしてリンクをBacklogにコメントする
    • ビルド中に何かエラーになったらslackのチャンネルに通知してすぐ気づけるように

Macへfastlaneのセットアップ

Rubyのセットアップ

  • fastlaneはRubyのgemで提供されているのでRubyが必要
  • rbenvでRuby2.5.1をシステム全体にセット (バージョンは適当)

fastlaneのセットアップ

上記ドキュメントを参照してプロジェクトのルート(xxxxx.xcodeprojが存在するディレクトリ)で

$ fastfile init

によりFastfile, Appfileを作成してソース管理する

fastlaneのgemインストール

$ bundle install —path vendor/bundle

でGemfileに記述してプロジェクト毎にセットしていると毎回のビルドでfastlaneをインストールしないといけないので、以下でシステムに直接インストールする

$ gem install fastlane

ビルド時のタイムアウトとリトライを調整する

デフォルトの設定だと以下ビルド情報を表示するコマンドでタイムアウトとリトライの上限を超えてたまにエラーになってしまう場合がある。

xcodebuild -showBuildSettings -workspace ./xxxxx.xcworkspace -scheme xxxxx

デフォルトだと10秒と4回の上限のようなので、Fastfileに以下のように設定しエラーとならないように

ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "180"
ENV["FASTLANE_XCODEBUILD_SETTINGS_RETRIES"] = "10"

JenkinsへのMac miniのslave登録

EC2インスタンスで起動しているJenkinsへローカルの机の上にあるMac miniをslaveとして登録する。

  • Jenkinsの管理 -> ノードの管理 -> 新規ノード作成 -> Permanent Agentを選択してOK
  • 以下必要な項目を記入して保存
メモ
同時ビルド数 1 2にすると並行して2つ同時にビルドできる?
リモートFSルート Users/jenkins/jobs/ Mac側のルートディレクトになる場所を指定する
ラベル mac-mini(識別するラベル) ビルドするジョブを作った時にこのラベル名を指定してビルドするマシンを指定する
用途 「このマシンを特定ジョブ専用にする」を選択 iOSのビルドしかしないのでこの設定にする
起動方法 「 Launch agent via Java Web Start 」を選択 この方法がJavaのJNLPという通信で↓のjarファイルを実行して通信を開始する
可用性 「keep this agent online as much as possible 」を選択

登録後にagent.jarがダウンロードできるのでMac miniにセットして、
表示される以下コマンド(Jenkinsが作成してくれる)を実行し、Jenkinsと同期状態(ビルド実行状態が待機中)となれば成功

java -jar /Users/jenkins/jobs/agent.jar -jnlpUrl http://xxx.xxx.xxx/computer/mac-mini/slave-agent.jnlp -secret [シークレットコード] -workDir "/Users/jenkins/jobs" -failIfWorkDirIsMissing

※ 設定で上記の項目を変更した場合、agent.jarとコマンドも変更になるので都度ダウンロードとコマンドの変更を行う

Jenkinsマスター(EC2インスタンスで起動している)へのポート設定

このJNLPのサービスがJenkinsと通信を行うため、JenkinsのJNLPに使用するポートの固定とEC2インスタンスに設定しているセキュリティグループのポート設定を行う

  • ポート固定
    • Jenkinsの管理 -> グローバルセキュリティの設定 -> Agents
      • -> TCP port for JNLP agents を固定、40790 を指定
      • -> Agent protocols で Java Web Start Agent Protocol/4 (TLS encryption) を指定
  • セキュリティグループ Mac miniのIPアドレス(インターネット向け)のJNLPのポート(40790)を通すようにする。
タイプ プロトコル ポート範囲 ソース 説明
カスタム TCP ルール TCP 40790 xxx.xxx.xxx.xxx/32 port for iOS build MacMini to Master Jenkins

MacMiniの自動起動設定

MacMini起動時にJNLP起動コマンドを自動で実行するようにlaunchctlに登録する
Fastlaneやaws s3コマンドへパスが通っていないと実行できないので、plistへPATHもセットしておく
- 設定ファイル

/Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist

システム環境設定 -> 省エネルギー -> スケジュールでMacの自動起動、終了を行うようにする
平日の09:30起動、21:00終了としている

メモ

JenkinsでMacMiniの状態がオフラインになることがある。

Jenkinsのビルド実行状態 -> mac-mini がオフラインになり以下のエラーが表示される

コネクションが切断されました。java.nio.channels.ClosedChannelException

回避策として
Macのシステム環境設定 -> 省エネルギー -> ディスプレイがオフのときにコンピュータを自動でスリープさせないのチェックを入れているが、またオフラインになる場合があるかもしれないので、
その場合はターミナルで以下、launchctlを再起動する

  • launchctlを止める方法
launchctl unload  /Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist
  • launchctlを起動する方法
launchctl load  /Users/jenkins/Library/LaunchAgents/com.xxxx.com.ci.plist
  • Jenkins本体が落ちた場合
    • Jenkins本体のEC2がスポットインスタンスの価格高騰などで落ちた場合、Jenkins本体が再び上がったら自動的に接続するのでMac mini側は何もしなくてよい
    • Jenkins本体が上がってしばらくしてもオフラインの場合は、Mac mini側を再起動してみる。

aws S3へのアップロード

  • python3とawscliのインストール aws s3 のコマンドでビルドしたipaをs3へアップロードする awsアカウントのcredentialをセットしてawsコマンドを使えるようにする(省略)

backlogへのリンクのポスト

BacklogのAPIを実行できるRubyのgemをシステムにインストールしてbacklogのチケットへコメントするスクリプトを実装
https://github.com/emsk/backlog_kit

$ gem install backlog_kit

slack通知

ビルドエラーの際、slackのチャンネルへ通知する
Fastfileのslackアクションにエラー時に通知するように設定できる

プロビジョニングが更新されたら

Mac miniのプロビジョニングファイルはfastfileに記載した自動更新オプションにより自動で更新されるはずだが、念の為以下のプロビジョニングファイルを削除しておく。(ビルド時に新しく取得しなおすように)

~/Library/MobileDevice/Provisioning Profiles/
  • fastfileのプロビジョニング自動更新オプション
xcargs: "-allowProvisioningUpdates",
  • キャッシュが原因でビルドが失敗していると思ったら 以下パス直下のファイルを全て消してみる。
/Users/jenkins/Library/Developer/Xcode/DerivedData

fastfileで以下のようにしてクリアしていれば、毎回のビルドで消えているはずだが、ビルド失敗するようなら自分で消してみる

  # delete Xcode Derrived Data
  clear_derived_data

※↑のプロビジョニングファイルも含めて毎回ビルド毎に rm するようにしたらいいかもしれない

fastlaneのアップデート

ひんぱんにアップデートされ、iOSの新バージョンリリース後にビルドがうまくいかなくなった場合は、リリースノートを見ると対応されていることがあったので、バージョンアップ手順は確認しておきたい。

$ gem list | grep 'fastlane'
commander-fastlane (4.4.6)
fastlane (2.126.0)
  • 以下コマンドでアップデート
$ sudo gem install fastlane
  • アップデート後のバージョン確認 -> 2.134.0になった
$ gem list | grep 'fastlane'
commander-fastlane (4.4.6)
fastlane (2.134.0, 2.126.0)
  • 元のバージョンに戻したい場合、以下でいけるはず
$ gem install fastlane -v “2.126.0”