Oakmini Engineering Notes

Eine iOS-CI/CD-Pipeline auf Mac mini M4 Cloud-Hosts aufbauen

DevOps & CI/CD ·ca. 4 Min. Lesezeit

iOS CI/CD-Pipeline auf einem Mac mini M4 Cloud-Host

Sobald ein Team eine bestimmte Größe überschreitet, wird „auf wessen Rechner der Build eigentlich funktioniert" schleichend zum Engpass. Nachdem wir unsere iOS-CI/CD-Pipeline vollständig auf Oakminis Mac mini M4 Cloud-Hosts verlagert haben, ist die Wartezeit für Builds merklich gesunken und Signatur-Zwischenfälle sind fast verschwunden. Dieser Beitrag beschreibt die Fallstricke beim Aufbau der Pipeline von Grund auf sowie das Fastfile und die Caching-Strategie, auf die wir uns letztlich festgelegt haben.

Warum ein Cloud Mac mini M4 für CI/CD

Ein Xcode-Build ist eine klassische Aufgabe, die „rechenintensiv und stark von der OS-Version abhängig" ist — ein lokaler, improvisierter Runner lässt sich damit kaum dauerhaft stabil betreiben. Wir haben uns für Cloud-Mac-Knoten aus drei Gründen entschieden:

  • Reproduzierbare Spezifikation: Das Image fixiert Xcode- und Kommandozeilen-Tool-Versionen, sodass es nie passiert, dass nach einem stillen OS-Update nur noch der Rechner einer einzelnen Person kompiliert.
  • Elastische Kapazität: Vor einem Release lassen sich für Regressionstests kurzfristig mehrere zusätzliche Knoten parallel starten und danach wieder freigeben — Hardware muss nicht dauerhaft für seltene Spitzen bereitstehen.
  • Rund um die Uhr verfügbar: Der geringe Leerlaufverbrauch des M4-Chips erlaubt Nacht-Builds und geplante Sicherheits-Scans im Dauerbetrieb, ohne sich um Stromkosten oder Lüftergeräusche zu sorgen.

Unser erster Fehler: den „Cloud-Host" nur als entfernten Monitor zu behandeln und ihn lediglich für manuelles Packaging zu nutzen. Der eigentliche Nutzen zeigte sich erst, nachdem er in die CI-Trigger-Kette eingebunden war.


CI/CD-Pipeline-Design

Aufteilung in Stufen

Eine vollständige Release-Pipeline haben wir in vier Stufen unterteilt:

  1. Checkout und Abhängigkeitsauflösung (SPM / CocoaPods)
  2. Unit-Tests und statische Analyse (parallel ausgeführt)
  3. xcodebuild archive sowie Code-Signierung
  4. Upload zu TestFlight und Benachrichtigung des Bereitschaftskanals

Hooks und Trigger-Bedingungen

Jeder Merge in main löst automatisch die Stufen 1-2 aus; die Stufen 3-4 laufen nur, wenn ein release/*-Tag gesetzt wird, damit kleine Alltags-Commits nicht das Signatur-Kontingent verbrauchen. Beim lokalen Debuggen einer Fastlane-Lane brechen wir meist mit Ctrl+C einen kompletten Lauf ab und wiederholen nur den fehlgeschlagenen Schritt — das spart überraschend viel Wartezeit.

Beispiel-Fastfile
lane :ci_archive do
  cocoapods
  run_tests(scheme: "Oakmini")
  build_app(
    scheme: "Oakmini",
    export_method: "app-store",
    output_directory: "./build"
  )
  upload_to_testflight(skip_waiting_for_build_processing: true)
end

Das Skript selbst ist nicht kompliziert; entscheidend ist, dass es auf derselben Image-Baseline jedes Mal das gleiche Ergebnis liefert — genau deshalb protokollieren wir die Image-Version bei jedem Build, um später unterscheiden zu können, ob ein Fehlschlag an der Umgebung oder am Code lag.


Caching und Abhängigkeitsverwaltung

Ist das Caching nicht richtig konfiguriert, frisst die Zeit für den Download von Abhängigkeiten den gesamten Geschwindigkeitsvorteil des Cloud-Builds wieder auf. Hier der Vergleich, den wir nach drei Monaten im Produktivbetrieb zusammengestellt haben:

Cache-ElementOhne CacheMit Image-Cache
CocoaPods-Installationca. 4-6 Minutenca. 30 Sekunden
SPM-Abhängigkeitsauflösungca. 3 Minutenca. 15 Sekunden
DerivedData inkrementeller Buildnahezu Vollbuildüber 80 % Trefferquote

Ein paar Begriffe vorab kurz geklärt:

DerivedData
Der Cache-Ordner von Xcode für inkrementelle Build-Artefakte; seine Wiederverwendung über Builds hinweg verkürzt spätere Compile-Vorgänge deutlich.
SPM-Cache
Der lokale Cache des Swift Package Manager für Paket-Quellen und Auflösungsergebnisse, der einen erneuten Remote-Fetch bei jedem Build vermeidet.
Image-Baseline
Der sofort startklare Systemschnappschuss des Cloud-Hosts mit fixierter Xcode-/CLT-Version und vorgewärmtem Abhängigkeits-Cache.
Team prüft die Cache-Trefferquote des Cloud-Builds auf einem Dashboard
Die Cache-Trefferquote als eigenes Panel zu verfolgen macht Regressionen viel leichter erkennbar als nur die Gesamt-Build-Zeit zu beobachten

Häufige Probleme eingrenzen

Am leichtesten übersieht man: Ein abgelaufenes Zertifikat schlägt nicht sofort fehl, sondern erst im letzten Schritt beim Upload zu TestFlight — bis dahin hat die gesamte Pipeline schon zehn Minuten und mehr im Leerlauf verbracht. Wir prüfen die Zertifikatsgültigkeit inzwischen bereits in Stufe 1, damit ein Fehlschlag früh und mit Alarm erfolgt.

Anfangs haben wir Cache-Verfall mit der groben Methode „jede Nacht den gesamten Cache löschen und komplett neu bauen" verhindert. Später sind wir auf eine Invalidierung nach Image-Version umgestiegen (Wiederverwendung, solange die Version gleich bleibt; Löschung nur bei einem Image-Upgrade) — das hält die Geschwindigkeit und verhindert zugleich, dass ein verunreinigter Cache wochenlang unbemerkt Probleme verursacht.

„Vertrau nicht einem grünen CI-Lauf; vertrau zehn grünen Läufen in Folge." — ein Satz aus unserem Bereitschaftshandbuch, den wir besonders in den ersten zwei Wochen nach dem Umzug auf einen neuen Cloud-Knoten wiederholen.

Häufig gestellte Fragen

Wie unterscheidet sich die Sicherheit eines Cloud Mac mini von einem selbst betriebenen Mac-Runner?

Der Cloud-Anbieter pflegt das Basis-OS-Image, aber die Zugriffskontrolle für Signaturzertifikate und Provisioning-Profile bleibt Aufgabe des Teams; Zertifikate nur als Laufzeit-Umgebungsvariablen einschleusen, nie ins Image backen, und die Keychain vor Rückgabe des Knotens leeren.

Kann die kostenlose Xcode-Cloud-Quote dieses gesamte Setup ersetzen?

Für kleine Teams reicht Xcode Cloud völlig aus; sobald mehr parallele Builds, eigene Skripte oder ein projektübergreifend geteilter Cache gefragt sind, bietet eine selbst betriebene Pipeline auf Cloud-Mac-Knoten mehr Flexibilität und eine klarere Kostenkontrolle.

Wie sollten Zertifikate isoliert werden, wenn mehrere Projekte denselben Pool an Cloud-Knoten teilen?

Am besten bekommt jedes Projekt eine eigene Keychain bzw. ein eigenes Keychain-Profil, das beim Build-Start importiert und am Ende wieder gelöscht wird — so verschmutzen die Zertifikate verschiedener Projekte nicht dieselbe systemweite Keychain.

Dedizierter physischer Mac-mini-Knoten erforderlich

Verfügbare Konfigurationen, Knoten und feste Abrechnungszeiträume anzeigen und die Schritte des Artikels in einer dauerhaft nutzbaren Cloud-Mac-Umgebung umsetzen.

Mac mini jetzt mieten