iOSビルド用にMacをJenkinsのエージェントとして追加する方法
MacをJenkinsに追加するには、コントローラーに接続する永続ノードを作成します。MacにはJava 21をインストールし、-webSocketオプション付きでagent.jarを実行します。このコマンドをLaunchAgentで包み、自動ログインをオンにします。これで再起動後もエージェントが再接続します。最後にノードにmacosのラベルを付け、iOSのステージをそこに送ります。
最初に必要なもの
- MacからHTTPSで到達できるJenkinsのコントローラー。
- 管理者権限、Homebrew、XcodeのあるApple silicon Mac。
- 適切なJava。2026年10月に読んだJenkinsのJavaサポートポリシーでは、LTS 2.555.1以降はJava 21または25が必要です。このルールはコントローラーだけでなくエージェントにも当てはまります。
- 最後の手順のための、画面共有によるMacのデスクトップセッション。
1. MacにJavaをインストールする
brew install openjdk@21 /opt/homebrew/opt/openjdk@21/bin/java -version
HomebrewはこのJDKをkeg-onlyでインストールするので、PATHには入りません。どこでも上のフルパスを使ってください。そうすれば、あとで新しいJDKが入っても、エージェントはJava 21のままです。
2. コントローラーでノードを作成する
- 「Jenkinsの管理」、「ノード」、「新規ノード」の順に開き、「Permanent Agent」を選びます。
- 同時ビルド数:1。Jenkins自身のドキュメントが、ノードあたり1つのexecutorを最も安全な設定としています。Xcodeのビルドは、それだけで全コアを使います。
- リモートFSルート:
/Users/YOUR-USER/jenkins。 - ラベル:
macos xcode。 - 用途:このノードに一致するラベル式のジョブだけをビルドする。これでLinuxのジョブがMacに来なくなります。
- 起動方法:コントローラーに接続してエージェントを起動する。
保存します。ノードのページに、実行コマンド、エージェント名、長い16進数のシークレットが表示されます。シークレットはエージェント名に結びついています。漏れた場合、Jenkinsはその名前を再利用しないよう指示しています。
3. agent.jarをダウンロードして手動でテストする
Jenkinsは、コントローラーに合ったagent.jarを/jnlpJars/agent.jarで配布しています。シークレットはファイルに保存し、@付きで渡します。こうすればプロセス一覧に表示されません。
mkdir -p ~/jenkins && cd ~/jenkins curl -sO https://jenkins.example.com/jnlpJars/agent.jar echo 'PASTE-THE-SECRET' > secret-file chmod 600 secret-file /opt/homebrew/opt/openjdk@21/bin/java -jar agent.jar \ -url https://jenkins.example.com/ \ -name mac-mini-1 \ -secret @secret-file \ -workDir "$HOME/jenkins" \ -webSocket
ノードのページが接続済みに変わるはずです。Control Cで止めます。-webSocketを付けると、エージェントはHTTPS接続を1本だけ使います。付けない場合は、コントローラーの別のインバウンドTCPポートも必要です。
4. LaunchAgentからエージェントを動かす
launchdはログイン時にエージェントを起動し、終了したら再起動します。これはMacのデスクトップ上のターミナルから実行してください。ヒアドキュメントがホームフォルダのパスを埋めます。
mkdir -p ~/Library/LaunchAgents
cat > ~/Library/LaunchAgents/local.jenkins-agent.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>local.jenkins-agent</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/openjdk@21/bin/java</string>
<string>-jar</string><string>$HOME/jenkins/agent.jar</string>
<string>-url</string><string>https://jenkins.example.com/</string>
<string>-name</string><string>mac-mini-1</string>
<string>-secret</string><string>@$HOME/jenkins/secret-file</string>
<string>-workDir</string><string>$HOME/jenkins</string>
<string>-webSocket</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>LANG</key><string>en_US.UTF-8</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>30</integer>
<key>StandardOutPath</key><string>$HOME/jenkins/agent.log</string>
<key>StandardErrorPath</key><string>$HOME/jenkins/agent.log</string>
</dict>
</plist>
EOF
plutil -lint ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl print gui/$(id -u)/local.jenkins-agent | head -20このplistはplutil -lintで確認済みです。なぜLaunchDaemonではなくLaunchAgentなのでしょうか。デーモンはどのログインセッションの外でも動きます。シミュレータとログインキーチェーンはセッションの中にあります。GitLabとBuildkiteも、自社のMac用エージェントについて同じ理由を挙げています。
plistは~/Library/LaunchAgentsに置いてください。macOS 27では、/Library/LaunchAgentsにplistがあると、起動のたびに自動ログインが壊れることを確認しました。詳しくはヘッドレスMacでの自動ログインにあります。
5. 再起動後も動くようにする
「システム設定」の「ユーザとグループ」で、エージェントのアカウントの自動ログインをオンにします。Macがスリープしないようにします。そして再起動し、ノードのページを確認します。
sudo pmset -a sleep 0 sudo shutdown -r now # then, after it is back: tail -n 20 ~/jenkins/agent.log
6. iOSのステージを含むJenkinsfile
pipeline {
agent { label 'macos' }
options { timeout(time: 30, unit: 'MINUTES') }
stages {
stage('Test') {
steps {
sh 'xcodebuild -version'
sh 'rm -rf build/TestResults.xcresult'
sh "xcodebuild test -project MyApp.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' -resultBundlePath build/TestResults.xcresult"
}
}
}
post {
always {
sh 'ditto -c -k --keepParent build/TestResults.xcresult build/TestResults.xcresult.zip || true'
archiveArtifacts artifacts: 'build/TestResults.xcresult.zip', fingerprint: true
}
}
}result bundleはフォルダなので、アーカイブの前にdittoでzipにします。どのMacでもzipを開き、bundleをダブルクリックすればXcodeで見られます。
1つのエージェントに2つのバージョンのXcodeがある場合は、DEVELOPER_DIRでパイプラインごとに選びます。Appleのxcode-selectのmanページによると、これはシステム全体の選択を変えずに上書きします。environmentブロックで設定すれば、Mac上のほかのジョブに影響しません。
environment {
DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer'
}このブロックはpipelineの中、agentの隣に置きます。sudo xcode-select -sでグローバル設定を変えても動きますが、マシン上のすべてのジョブが一度に変わります。
よくあるエラーと対処法
- エージェントは接続するのに、再起動後に消える。誰もログインしていないので、LaunchAgentが起動していません。自動ログインを確認してください。
- SSH経由だと
launchctl bootstrapがドメインのエラーで失敗する。デスクトップのターミナルから実行してください。GitLabとBuildkiteも、自社のLaunchAgentについて同じエラーを記載しています。 - エージェントが起動を拒否し、Javaに言及する。Jenkinsは起動時にJavaのバージョンを確認します。サポートポリシーのバージョンに合わせてください。
xcodebuild: error: Existing file at -resultBundlePath。ワークスペースはビルドの間も残ります。Jenkinsfileのように、先に古いbundleを削除してください。
専用Macが役立つ理由
永続エージェントは、ワークスペースとXcodeのDerivedDataをビルドの間も保持します。その見返りが差分ビルドです。当社のベンチマークでは、Xcode 26.6でWikipediaのiOSアプリを使い、3回の中央値を取りました。小さな変更後の再ビルドは、ウォームなM6で27秒でした。新規のGitHubホスト型macos-26 runnerでは、同じジョブに269秒かかりました。
これが不要な場合
まだJenkinsを使っていないなら、iOSアプリ1つのために始めるのはやめましょう。Mac runnerのあるホスト型サービスのほうが保守は少なく済みます。GitHubのmacOSの単価$0.062/分(2026年9月に確認)なら、月額固定$139のMacは月2,242分ほどで元が取れます。それより少なければ、従量課金のほうが安くなります。また、コントローラーが固定IPからのエージェントしか受け付けないなら、MacRunは向きません。当社には固定IPがありません。
MacRunのMacでのJenkinsは、その他のCIシステムのページで簡潔に説明しています。ステージに何を入れるかは、iOS CI/CDパイプラインのガイドで扱っています。
よくある質問
JenkinsのMacエージェントはSSHとインバウンドのどちらで起動すべきですか?
+
Macからコントローラーには届くが、逆方向には届かない場合、インバウンドが使えます。-webSocketを付ければ、コントローラーへのHTTPSだけで済みます。
macOS上のJenkinsエージェントにはどのJavaが必要ですか?
+
コントローラーと同じ系統です。2026年10月に読んだJenkinsのサポートポリシーでは、LTS 2.555.1以降はJava 21または25が必要です。
Macエージェントのexecutorはいくつにすべきですか?
+
まずは1つにしてください。Jenkinsはノードあたり1つのexecutorを最も安全な設定としています。Xcodeのビルドは、それだけで全コアを使います。
再起動後にJenkinsのMacエージェントが再接続しないのはなぜですか?
+
LaunchAgentは、そのユーザーがログインしないと動きません。そのアカウントの自動ログインをオンにし、plistは~/Library/LaunchAgentsに置いてください。