Mac版Jenkinsでビルドが止まる!キーチェーンロック起因のハング解消法

深夜2時に走るはずだった自動リリースビルドが、翌朝になっても終わっていない。ログを追うと、最後の行で数時間前から時が止まっていました。CPUは静かで、エラーも出ていない。ただ「何かを待っている」状態です。原因はGUIダイアログでした。人のいない時間帯に「キーチェーンのパスワードを入力してください」という入力待ち画面が、無人のはずのCIジョブの前に立ちはだかっていたのです。

k.watanabeです。Mac版JenkinsでCIが無応答のまま止まる不具合を追った記録を書きました。原因はcodesignより前のgit checkout段階でキーチェーンがロックし直されていたことで、iOS向けと同じ解錠方式に統一して解決しています。

事の概要

Mac向けアプリのビルドをJenkinsで無人実行していたところ、キーチェーンがロック状態のまま次のビルドに入ると、ソース取得(git checkout)の段階でGUIダイアログが出現し、ビルドが完全に停止する不具合がありました。原因はビルド終了時の再施錠処理と、macOS標準の自動ロック設定が重なっていたことです。iOS向けパイプラインですでに運用していた解錠方式に統一することで解決しました。

macOSキーチェーンとJenkins連携の基礎

macOSの「ログインキーチェーン」は、パスワードや証明書、秘密鍵をOSレベルで管理する認証情報保管庫です。コード署名(codesign)はこのキーチェーンに格納された署名用証明書を参照しますし、プライベートリポジトリへのGit認証も、SSHキーやトークンをキーチェーン経由で扱うケースがあります。

キーチェーンには「ロック」と「解錠」という状態があり、ロックされている状態でアクセスが発生すると、macOSはパスワード入力を求めるGUIダイアログを表示します。加えて、macOSには一定時間操作がないと自動的にキーチェーンを再ロックする「自動ロック設定」があり、これが有効だと解錠しても時間経過で再びロックされてしまいます。GUIを持たないSSH経由の無人実行では、このダイアログに応答する手段がなく、ビルドはそこで固まります。

JenkinsfileはMac向けビルドのパイプライン定義ファイルで、Checkout・ビルド・署名・成果物アップロードといった一連の工程をコードとして記述したものです。今回の修正はこのJenkinsfile内の、キーチェーン操作に関わる箇所が対象になりました。

なぜcodesignだけでなくcheckoutでも詰まるのか

今回の環境では、ビルド終了ごとにキーチェーンを施錠し直す処理が組み込まれていました。これ自体は、証明書や秘密鍵を長時間解錠状態のまま放置しないための意図だったと見られます。しかし、この明示的な再施錠に加えて、macOS標準の自動ロックも並行して機能していました。

結果として、次のビルドが始まった直後、最初に実行されるgit checkoutの時点でキーチェーンは確実にロックされている状態になっていました。checkoutはコード署名を伴わない単純なソース取得処理ですが、キーチェーンへのアクセスが発生する構成だったため、ここでもGUIダイアログの対象になっていたのです。

この症状が厄介だったのは、無人実行を前提にしたCIパイプラインが、応答不能なGUI待ちで丸ごと止まる点でした。エラーとしてログに残らず、ビルドキューに次のジョブが詰まっていく形で顕在化するため、誰かがダッシュボードを見て気づくまで復旧しません。求める成功基準はシンプルで、GUI入力を一切要求せず、CI機が無人で全工程を完走できることでした。

codesign対策だけでは不十分だった

この問題に取り組み始めた当初は、「キーチェーンの解錠を気にすべきはcodesignを実行する瞬間だけだ」という前提で調査を進めていました。コード署名は明示的にキーチェーンへアクセスする処理なので、真っ先に疑われる箇所です。しかし実際にログを追跡すると、ダイアログが表示されるタイミングはcodesignのステップより前、checkoutの段階でした。

「署名時だけ気をつければいい」という思い込みが、調査を一度遠回りさせています。checkoutという一見キーチェーンと無関係に見える工程が、実は同じロック状態の影響を受けていたことが分かった時点で、対策の対象範囲を見直す必要が出てきました。

📦 旧状態(Before)

  • ビルド終了ごとにキーチェーンを再施錠
  • macOS標準の自動ロックも並行して有効
  • 次ビルドのcheckoutが必ずロック状態と衝突
  • GUIダイアログでビルドが無応答のまま停止

✅ 新状態(After)

  • Checkout前に一度だけキーチェーンを解錠
  • 自動ロック設定を無効化
  • ビルド終了時は施錠せず解錠状態を維持
  • 解錠失敗時はビルドを即失敗させる

iOSパイプラインと同じ方式への統一

採用した方針は次の3点です。Checkoutより前の段階で一度だけキーチェーンを解錠すること、自動ロックを無効化すること、そしてビルド終了時には施錠しないことです。最後の点は「専用CI機だから解錠状態を維持してよい」という前提条件の上に成り立っています。共用の作業マシンであれば別の判断が必要になったはずです。

この方式を選んだ最大の理由は、iOS向けビルドパイプラインですでに同じ考え方が運用されていた点にあります。プラットフォームごとに異なるキーチェーン管理ロジックを維持するより、同一機構に揃えて保守対象を減らす方を選びました。最初から独自の仕組みを新設する案もありましたが、既存の実績があるものに寄せる判断のほうが合理的だと考えました。

もう一つ加えたのが、解錠に失敗した場合は即座にビルドを失敗させる設計です。原因不明のままGUI待ちで固まり続けるより、明示的に失敗させて調査対象を絞り込むほうが復旧の手間が少なくなります。これはいわゆるフェイルファストの考え方で、症状が「止まる」ではなく「落ちる」に変わるだけで、担当者が異常に気づく速度は大きく変わります。

なお、Jenkins向けのXcode Integrationプラグインの公式ドキュメントでも、macOSの制約上セッションごとに異なるキーチェーンを扱うことができず、同時にキーチェーン操作を行うジョブが正しく動作しない可能性が明記されています[1]。キーチェーン管理を一つの方式に集約する判断は、こうしたmacOS側の制約とも整合しています。

解錠タイミングと自動ロック無効化の処理フロー

実際の処理は、分岐を含む一連の流れとして整理できます。Checkout前に解錠を試み、成功すれば自動ロックを無効化してcheckout以降の工程に進みます。失敗した場合はその場でビルドを失敗させ、後続の処理には進みません。ビルド終了時はキーチェーンを再施錠せず、解錠状態のまま次のビルドに引き継ぎます。

成功
失敗
次のビルドへ引き継ぎ
ビルドジョブ開始
キーチェーン解錠試行
自動ロック設定を無効化
ビルドを即失敗させる
git checkout実行
codesign等 後続処理
ビルド終了 施錠せず解錠状態を維持

この変更は該当するJenkinsfile内の、キーチェーン操作に関わる箇所に加えられました。Jenkins上でキーチェーンのロック解除を扱う場合、Xcode Integrationプラグインの`KeychainUnlockStep`のように、開発者プロファイルのインストールとロック解除を担う専用ステップを使う構成が一般的です[2]。ただし、こうした解錠ステップを使っていても、Xcodeが対象キーチェーンを正しく認識せず、`security default-keychain -s`のようなコマンドを別途明示的に実行しなければならない事例が報告されています[3]。解錠処理を入れたつもりが実際には反映されていない、というギャップが起こり得る点は、同種の構成を組む際に留意すべきところです。

現状の制約・未対応項目

この対策には前提条件があります。ビルド終了時にキーチェーンを施錠しない運用は、そのマシンが専用のCI機であることを前提にしています。人が日常的にログインして作業したり、他の用途と共用したりするマシンでは、証明書や秘密鍵を解錠状態のまま放置するリスクが無視できません。

共有マシンや、複数のジョブが同一のキーチェーンに同時にアクセスする環境への適用は未検証です。前述のとおり、macOSはセッションごとに異なるキーチェーンを扱う構成に制約があるとされており[1]、並行ジョブが同じキーチェーンを取り合う状況では、今回とは別の競合が起きる可能性があります。専用機以外への横展開は、この点を含めて別途検討が必要です。

💬 現場の声

担当エンジニアより:

「これまで Mac 版ビルドで、ログインキーチェーンがロックされていると、コード署名(codesign)だけでなくソース取得(git checkout)の段階でも「キーチェーンのパスワードを入力してください」という GUI ダイアログが出て、無人の Jenkins ビルドがそこで止まる(ハングする)ことがありました。原因は、ビルド終了ごとにキーチェーンを施錠し直していた上に自動ロックも効いていたため、次のビルドの最初の Checkout が必ずロック状態に当たっていたことです。対応として iOS パイプラインと同じ方式に統一しました。Checkout より前に一度だけ解錠し、自動ロックを無効化、ビルド終了時には施錠しない(専用 CI 機なので解錠のまま維持)という形です。あわせて解錠に失敗したときはビルドを即失敗させ、原因不明のハングを防ぐようにしています。」

よくある質問

Q. 共有のビルドマシンでも同じ方式を適用できますか。
今回の方式はビルド終了後もキーチェーンを解錠状態に保つ点が肝になっており、専用CI機であることが前提です。共有マシンでは他ジョブや他ユーザーが同じ解錠状態を利用できてしまうため、そのままの適用は推奨できません。

Q. キーチェーンを解錠したままにしておくことにセキュリティ上の懸念はありませんか。
専用CI機に用途を限定し、外部からのログインを制限するといった運用条件とセットで成立する対策です。マシンの利用範囲が広がる場合は、解錠状態を維持する設計自体を見直す必要があります。

まとめ

最終的に特定した原因は、ビルド終了ごとの再施錠処理とmacOS標準の自動ロックが重なり、次のビルド開始直後のcheckoutが常にロック状態と衝突していたことでした。当初はcodesignの実行タイミングだけを疑っていましたが、実際にはそれより前のcheckoutの段階で詰まっており、調査対象を広げて初めて全体像が見えています。

対策としては、iOS向けパイプラインですでに実績のあった解錠方式にMac向けビルドも統一し、Checkout前の一度きりの解錠、自動ロックの無効化、終了時に再施錠しないという構成にまとめました。あわせて解錠失敗時は即座にビルドを失敗させ、無応答のハングという分かりにくい症状を、明示的なエラーに変えています。専用CI機であることを前提条件とした割り切りではありますが、同じ境界でつまずくチームが減れば幸いです。

🔗 参考リンク

  1. README.md
  2. KeychainUnlockStep (Xcode integration 2.0.17-565.v1c48051d46ef API)
  3. Loading…