Wenn eine nächtliche Archivierung mittendrin abbricht und am nächsten Morgen nur ein Timeout protokolliert ist, während sich der Mac nach dem erneuten Aufbau der Remotedesktop-Verbindung normal bedienen lässt, liegt die Ursache nicht zwangsläufig im Netzwerk. Bei unbeaufsichtigter CI auf einem Cloud-Mac müssen zunächst vier Zustände unterschieden werden: Systemruhezustand, Bildschirmruhezustand, Beendigung der Remotesitzung und Beendigung des Runner-Prozesses. Sie sehen von außen ähnlich aus, erfordern jedoch Korrekturen an völlig unterschiedlichen Stellen.
Zuerst die Ebene des Abbruchs bestimmen
Bevor Einstellungen geändert werden, sollten die letzte Ausgabe des fehlgeschlagenen Jobs, sein Exit-Code und die Startzeit des Hosts erfasst werden. Bricht das Protokoll mitten in einem Build-Befehl ab, ohne dass sich die Startzeit des Systems geändert hat, sind Ruhezustand und Prozesslebenszyklus zu untersuchen. Hat sich die Startzeit geändert, muss der Vorfall als Neustart analysiert werden. Parameter zur Verhinderung des Ruhezustands dürfen einen solchen Neustart nicht kaschieren.
| Beobachtung | Zuerst prüfen | Typische Schlussfolgerung |
|---|---|---|
| Bildschirmausgabe erlischt, SSH und Build laufen weiter | displaysleep |
Nur die Bildschirmausgabe befindet sich im Ruhezustand |
| SSH-Verbindung und Job brechen gleichzeitig ab | sleep, Energieprotokoll |
Das System ist möglicherweise in den Ruhezustand gewechselt |
| Job endet nach dem Schließen des Terminals | Startmethode des Runners | Der Prozess ist an eine interaktive Sitzung gebunden |
| Startzeit des Hosts hat sich geändert | Neustartprotokoll, Jobprotokoll | Das System wurde neu gestartet |
Zunächst sollte eine Baseline erfasst werden, ohne das System zu verändern:
mkdir -p "$HOME/ci-audit"
date > "$HOME/ci-audit/power-baseline.txt"
pmset -g custom >> "$HOME/ci-audit/power-baseline.txt"
pmset -g assertions >> "$HOME/ci-audit/power-baseline.txt"
pmset -g sched >> "$HOME/ci-audit/power-baseline.txt"
sysctl -n kern.boottime >> "$HOME/ci-audit/power-baseline.txt"
pmset -g custom zeigt die Einstellungen für die verschiedenen Stromversorgungsarten. pmset -g assertions listet Prozesse auf, die den Ruhezustand derzeit verhindern. Mit pmset -g sched lassen sich bereits vorhandene geplante Ereignisse erkennen. Eine einzelne Zeile wie sleep 0 reicht nicht für eine belastbare Schlussfolgerung aus. Konfigurationsprofil und aktuell wirksame Assertions müssen gemeinsam betrachtet werden.
Bildschirm- und Systemruhezustand unterscheiden
Ein Mac mini besitzt keinen Akku, daher ist vor allem die Konfiguration für den Netzbetrieb relevant. Der Bildschirmruhezustand unterbricht keine Kompilierung. Für CI muss die virtuelle Bildschirmausgabe deshalb nicht dauerhaft aktiv bleiben. Entscheidend für unbeaufsichtigte Jobs sind der Systemruhezustand und die Frage, ob der Runner als funktionsfähiger Prozess weiterläuft.
Auf einem dedizierten Knoten, der ausschließlich für kontinuierliche Builds eingesetzt wird, kann nach dem Sichern der ursprünglichen Ausgabe der Systemruhezustand im Netzbetrieb deaktiviert werden:
sudo pmset -c sleep 0 disksleep 0 powernap 0
pmset -g custom
displaysleep wird hier nicht geändert, da ein abgeschalteter Bildschirm keinen Einfluss auf Builds im Hintergrund hat. Nach Ausführung des Befehls muss die Konfiguration erneut ausgelesen werden. Eine erfolgreiche Befehlsausführung allein ist noch kein Abnahmenachweis. Wird der Mac zusätzlich für interaktive Desktop-Arbeit genutzt, sollten zunächst die Nutzungszeiten des Teams geprüft werden. Bei seltenen CI-Jobs ist es sinnvoller, den Ruhezustand nur für die jeweilige Aufgabe zu blockieren, statt die systemweite Richtlinie dauerhaft zu ändern.
Energieeinstellungen sorgen dafür, dass der Mac weiterläuft. Sie verwandeln einen an das Terminal gebundenen temporären Prozess jedoch nicht automatisch in einen Hintergrunddienst.
Einzelne Builds mit caffeinate ausführen
caffeinate kann für die Lebensdauer eines untergeordneten Prozesses eine Energie-Assertion setzen. Im Vergleich zur globalen Deaktivierung des Ruhezustands ist der Wirkungsbereich klar begrenzt. Das eignet sich besonders für geplante Archivierungen, lang laufende Tests und einmalige Builds von Abhängigkeiten.
Dazu wird ein einheitliches Wrapper-Skript angelegt:
sudo install -d -m 0755 /usr/local/bin
cat <<'EOF' | sudo tee /usr/local/bin/ci-awake >/dev/null
#!/bin/zsh
set -euo pipefail
if (( $# == 0 )); then
exit 64
fi
exec /usr/bin/caffeinate -ims "$@"
EOF
sudo chmod 0755 /usr/local/bin/ci-awake
Im Runner darf nicht zusätzlich ein Operator für die Hintergrundausführung angehängt werden. Stattdessen übernimmt das Wrapper-Skript den Zielbefehl direkt:
/usr/local/bin/ci-awake /usr/bin/xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
build
-i verhindert den Systemruhezustand bei Inaktivität, -m verhindert den Ruhezustand des Datenträgers und -s blockiert den Systemruhezustand im Netzbetrieb. Sobald der Zielbefehl endet, wird die Assertion zusammen mit dem Wrapper-Prozess aufgehoben. Während des Builds lässt sich mit pmset -g assertions prüfen, ob die entsprechende Assertion vorhanden ist.
Den Runner von der Remotesitzung entkoppeln
Wenn ein Build nach dem Schließen der SSH- oder Grafiksitzung weiterhin beendet wird, liegt der Schwerpunkt nicht mehr auf pmset, sondern auf dem übergeordneten Prozess des Runners. Manuell in einem Terminal gestartete Prozesse können beim Sitzungsende ein Signal erhalten. Sie können außerdem fehlschlagen, wenn Arbeitsverzeichnis, Umgebungsvariablen oder der Kontext eines temporären Schlüsselbunds nicht mehr verfügbar sind.
Ein dauerhaft laufender Runner sollte von launchd verwaltet werden. Mindestens die folgenden Punkte sind zu prüfen:
- Das Dienst-Label ist fest definiert, beispielsweise
local.ci.runner, und der Dienst wird nicht nach jeder Anmeldung erneut gestartet. - Das Arbeitsverzeichnis wird als absoluter Pfad angegeben und hängt nicht vom aktuellen Verzeichnis beim Start im Terminal ab.
PATH, Auswahl der Toolchain und Cache-Verzeichnis werden explizit gesetzt und nicht aus der Konfiguration einer interaktiven Shell übernommen.- Standardausgabe und Standardfehlerausgabe werden in getrennte Protokolldateien geschrieben; für beide gilt eine kontrollierbare Rotationsstrategie.
- Beim Empfang eines Beendigungssignals beendet der Runner auch den aktuellen untergeordneten Prozess, damit keine verwaisten Prozesse das Build-Verzeichnis weiter belegen.
Ein Dienst auf Benutzerebene lässt sich folgendermaßen prüfen:
launchctl print "gui/$(id -u)/local.ci.runner"
pgrep -afil 'runner|xcodebuild'
Muss der Dienst auch ohne angemeldeten Benutzer laufen, ist ein systemweiter LaunchDaemon zu verwenden und mit launchctl print system/local.ci.runner zu prüfen. Benutzer- und systemweite Instanz dürfen nicht gleichzeitig aktiv bleiben, da sonst zwei Runner um dasselbe Arbeitsverzeichnis konkurrieren können.
Inbetriebnahme anhand von Nachweisen abnehmen
Nach Abschluss der Konfiguration sollte ein Testjob ausgeführt werden, dessen Laufzeit die Dauer bis zum ursprünglichen Abbruch überschreitet. Während des Tests werden Remotedesktop und SSH-Client bewusst geschlossen, ohne den Runner zu beenden. Nach dem erneuten Verbindungsaufbau sind Exit-Code, Zeitstempel der Artefakte, Dienstprozess und Energie-Assertions zu kontrollieren.
Kommt es erneut zu einem Abbruch, müssen sofort folgende Daten erfasst werden:
date
pmset -g assertions
pmset -g log | tail -n 120
sysctl -n kern.boottime
last reboot | head
Für die abschließende Abnahme müssen vier Bedingungen erfüllt sein: Der Bildschirmruhezustand beeinträchtigt den Job nicht; nach dem Schließen der Remotesitzung läuft der Runner weiter; während des Builds ist die caffeinate-Assertion sichtbar; nach Abschluss des Jobs wird die Assertion automatisch aufgehoben. Schlägt eine dieser Prüfungen fehl, muss die zugehörige Ebene korrigiert werden. Weitere Energieparameter sollten nicht wahllos darübergelegt werden.
Für einen dedizierten Cloud-Mac bei XcodeVM beruht stabile unbeaufsichtigte CI auf zwei klaren Grenzen: Die Energieverwaltung des Systems hält den Knoten in Betrieb, während launchd und der Job-Wrapper dafür sorgen, dass der Build-Prozess nicht von einer interaktiven Sitzung abhängt. Werden beide Bereiche getrennt konfiguriert und mit eigenen Nachweisen geprüft, hinterlässt der nächste Abbruch eine analysierbare Ursache statt lediglich eines unspezifischen Timeouts.
Häufig gestellte Fragen
Verhindert das Abschalten des Display-Ruhezustands auch den Systemschlaf?
Nein. displaysleep betrifft nur die Anzeige. Für laufende CI-Aufträge müssen der Wert sleep und die aktiven Energie-Assertions separat mit pmset geprüft werden.
Soll der Ruhezustand dauerhaft deaktiviert werden?
Auf einem ausschließlich für dauerhafte CI genutzten physischen Knoten kann das sinnvoll sein. Bei sporadischen Builds begrenzt caffeinate die Änderung besser auf die Laufzeit des jeweiligen Auftrags.
Warum endet ein Build nach dem Trennen der Remotesitzung trotzdem?
Häufig läuft der Runner innerhalb einer interaktiven Shell statt als eigenständiger launchd-Dienst. Dann müssen Dienstkontext und Prozesslebenszyklus korrigiert werden, nicht nur die Energieoptionen.
XcodeVM macOS-Cloud-Hosts
Wählen Sie einen exklusiven physischen Server passend zu Ihrer aktuellen Arbeitslast.
Prüfen Sie Arbeitsspeicher, Speicherplatz, Node und Abrechnungszeitraum, bevor Sie mit der Konfiguration fortfahren. Alle Nodes laufen 365 Tage im Jahr stabil; maßgeblich ist die in der Konsole in Echtzeit angezeigte Verfügbarkeit.