Début juin, nous avons empilé Xcode CI et OpenClaw Gateway sur le même Mac M4 au Canada, en tenant deux semaines avec launchd Nice=-5 et une concurrence de compilation limitée. La troisième semaine, l’équipe produit a commencé à dire dans l’IM : « le Gateway rame encore ». Les logs de build étaient verts, mais le health check curl passait de 50 ms à plus de 400 ms aux heures de pointe. Nous avons réduit la concurrence, vidé DerivedData, monté temporairement en 24 Go — le problème est passé de « deux fois par jour » à « une fois », sans disparaître.
Ce n’était pas une mauvaise config, mais le plafond du modèle same-host. Les ports ne se marchent pas dessus (20300 vs 18789), les cœurs CPU suffisent — le combat se joue sur la mémoire unifiée et le swap. Cet article prolonge le co-hébergement et le tuning launchd : quand passer en dual-node, comment dessiner la topologie, comment basculer le trafic sans casser les Channels. Mémoire et builds : documentation Apple sur le cache de compilation Xcode ; réseau : guide d’installation Tailscale.
En bref : la ligne de partage, c’est l’isolation des ressources, pas « acheter une autre machine ».
-
Quatre signaux durs — un seul suffit
≥50 builds/jour, ≥3 pipelines parallèles, Gateway face aux utilisateurs finaux, ou ≥3 swap Critical/semaine — continuer à tuner Nice ne fait que reporter.
≥50/jour
-
Dual-node minimal : build + Gateway dédié
Nœud build 24 Go+ et gros SSD ; nœud Gateway 16 Go pour ~500 Mo en charge. Deux M4 via Tailscale ; exposition publique minimale.
16 Go Gateway
-
Migration = état + DNS, pas réinstall
rsync de la config, réutiliser les tokens, bascule MagicDNS Tailscale ; le build ne s’arrête pas, blue/green Gateway ≤15 minutes.
Blue/green ≤15 min
1. Pourquoi le same-host « sature d’un coup »
Beaucoup d’équipes démarrent avec un Mac cloud pour trois rôles : xcodebuild, OpenClaw Gateway sur 18789, VNC occasionnel pour le trousseau. À faible charge, 16 Go semblent larges. Avec plus de branches, des tests UI parallèles et des Channels passés du pilote interne à l’IM prod, la courbe mémoire passe de « dent de scie » à « plateau » — dix minutes après la fin d’un build, les pages compressées restent au-dessus de 8 Go et le heap Gateway continue en swap.
Le tuning same-host (moins de parallélisme, Nice plus haut, vider le cache) réduit la probabilité de pics qui se chevauchent, pas les pics eux-mêmes. Quand builds APAC en journée et pic IM nord-américain le soir se recoupent presque toujours, vous ne optimisez plus des paramètres — vous pariez sur l’ordonnanceur. Apple Silicon n’a pas de VRAM séparée ; Xcode et le Gateway Node.js partagent le même pool physique. Dès memory_pressure en Warn, les utilisateurs sentent le jitter ; la CI voit des timeouts et des échecs de signature sporadiques.
Séparer n’est pas un mythe de « scale-up », c’est séparer des pics non compressibles : les builds uniquement sur le nœud A, la RAM Gateway jamais évincée par xcsbuildd. Même logique que dans GitHub Actions runner macOS auto-hébergé sur Mac cloud : runner dédié sans mélange bureau — ici on sépare Gateway et CI.
Pour les équipes FR/EU avec SLA documentés, le déclencheur n’est souvent pas le premier swap, mais le troisième ticket « Gateway lent » en une semaine alors que les dashboards CI restent verts. À ce stade, le same-host n’économise plus — il cache de l’indisponibilité perçue.
Expérience utile : mesurez le P95 Gateway uniquement pendant les jobs xcodebuild actifs. Si la latence triple à octuple alors que le CPU reste sous 70 %, ce n’est pas le réseau — c’est la contention RAM. Chez nous, la mémoire compressée restait >6 Go dix minutes après la fin du build ; le heap Node.js du Gateway ne pouvait pas rester résident. Chaque baisse de -jobs ou hausse de Nice réduisait le débit CI sans sauver le SLA Gateway. C’est le piège classique du same-host : deux équipes s’optimisent l’une contre l’autre sur la même machine.
2. Trois topologies : same-host, dual-node, triple-node
Classer avant d’acheter :
- T0 same-host : un M4 pour Xcode Server / runner auto-hébergé + Gateway. <30 builds/jour, Gateway interne, latence occasionnelle acceptable.
- T1 dual-node (focus) : nœud A CI seul (24 Go+, gros SSD) ; nœud B Gateway seul (16 Go). Tailscale ou LAN datacenter ; Channels/Dashboard → B, déclencheurs build → A.
- T2 triple-node : ajout d’un hôte signature/upload ou tests parallèles. >150 builds/jour ou séparation dure TestFlight/compile ; T1 suffit pour la plupart des PME.
Conclusion asymétrique : ce n’est pas le M4 qui manque, c’est deux classes de SLA dans le même pool RAM. Le Gateway veut 99,9 % de réponse stable ; la CI veut du débit. Le same-host compromet les deux avec un seul SLA.
3. Same-host vs dual-node vs upgrade : comparer
| Dimension | Continuer le tuning Nice / moins de parallèle | Monter à 32 Go Plus de RAM, même topo | Split dual-node CI + Gateway séparés |
|---|---|---|---|
| Cause racine | Atténue le chevauchement | Relève le plafond RAM | Isole deux SLA |
| Latence Gateway | Jitter avec les builds | Mieux, pas zéro | <100 ms possible en build |
| Coût mensuel (ordre) | Le plus bas | Moyen (upgrade) | Moyen (+ petit 16 Go) |
| Complexité ops | Faible | Faible | Moyenne (+1 hôte, Tailscale) |
| Phase adaptée | <30 builds/jour | 2 jobs parallèles, GW interne | ≥50/jour ou GW externe |
| Signal | Seuil | Observation | Faux positif |
|---|---|---|---|
| Fréquence build | ≥50/jour | Logs CI / compteur runner | Exclure archive locale manuelle |
| Parallèlisme | ≥3 builds complets | xcodebuild -jobs, profondeur file | Lint léger ne compte pas |
| Audience Gateway | Utilisateurs finaux / Channels 7×24 | SLA produit | Webhook interne seul peut attendre |
| Pression mémoire | ≥3 Critical/semaine | Logs memory_pressure | Fuite ponctuelle à corriger d’abord |
| Disque | >50 Go/mois + I/O wait élevé | df -h + iostat | Nettoyer archives avant juger |
4. Scénarios : matrice de décision
- Binôme, <20 builds/jour, Gateway perso : rester T0, tuning launchd suffit.
- Builds APAC, Gateway pour utilisateurs IM : T1 direct. Follow-the-sun garantit presque toujours le chevauchement.
- Runner GHA cloud, Gateway tout neuf : runner sur A, Gateway sur B ; ne pas monter 18789 sur la machine runner.
- Upload TestFlight vs compilation sur disque : T1 d’abord ; T2 hôte signature si besoin — ne pas confondre avec ce runbook.
- Budget une seule machine : prioriser le SLA Gateway, builds de nuit ou moins de parallèle — compromis, pas architecture long terme.
Si votre post-mortem montre que le P95 Gateway ne monte que pendant xcodebuild, ce n’est pas le réseau — c’est la contention RAM. T1 règle cela sans pause pipeline.
5. Stacks recommandés (cumulables)
- Dual-node minimal : Hashvps Canada M4 24 Go (A, CI) + M4 16 Go (B, Gateway) + tailnet Tailscale +
openclaw doctorhebdo. Upgrade standard depuis T0. - Hybride CI : A avec runner auto-hébergé GitHub Actions ; B Gateway + Channels. Orchestration GitHub, exécution macOS, Gateway hors pics build. Aligné sur la « souveraineté d’environnement » dans marché build macOS 2027.
- Observabilité : sonde latence Gateway sur B (
curltoutes les 5 s) + snapshotmemory_pressuresur A après chaque build ; deux semaines suffisent pour le ROI split auprès de la direction.
6. Pièges avant migration
- Arrêter la CI avant de déplacer le Gateway : coûteux ; blue/green Gateway, CI continue.
- Réinstaller au lieu de rsync : perte
~/.openclaw, tokens, appairage Channels → reconnexion générale. - Deux IP publiques sur 18789 : pendant le cutover, Tailscale ou DNS interne — jamais deux Gateways sur les Channels.
- Supprimer trop tôt le Gateway sur le build : fenêtre rollback : ancien processus vivant, DNS seul change.
- Horloge et certificats : dérive NTP >30 s → échecs token sporadiques ; jour J
sudo sntp -sS time.apple.com.
7. Runbook : dual-node en sept étapes
Hypothèse : mac-ci-01 CI+Gateway ; nouveau mac-gw-02 (16 Go) Gateway seul. Tailscale installé (voir runbook ops OpenClaw).
Étape 1 : baseline (veille)
Capturer P50/P95 Gateway, nombre de builds, distribution memory_pressure. Exporter openclaw status et liste Channels.
# 延迟采样 60 次
for i in $(seq 1 60); do
curl -o /dev/null -s -w "%{time_total}\n" http://127.0.0.1:18789/health
sleep 5
done | sort -n | awk '{a[NR]=$1} END{print "p50="a[int(NR*0.5)],"p95="a[int(NR*0.95)]}'
memory_pressure
vm_stat | head -8
Étape 2 : nouvel hôte + Tailscale
Sur mac-gw-02 : mise à jour macOS, Homebrew, Node, Tailscale ; ping vers mac-ci-01 <5 ms. Pas de xcodebuild sur B.
Étape 3 : rsync état Gateway (fenêtre maintenance)
# 在原机 mac-ci-01 执行;先停 Gateway 避免写入分裂 sudo launchctl unload /Library/LaunchDaemons/com.openclaw.gateway.plist rsync -avz --delete \ ~/.openclaw/ \ builder@mac-gw-02.tailnet-abc.ts.net:~/.openclaw/ # 同步 launchd plist scp /Library/LaunchDaemons/com.openclaw.gateway.plist \ builder@mac-gw-02.tailnet-abc.ts.net:/tmp/
Étape 4 : démarrer Gateway sur le nouvel hôte
sudo cp /tmp/com.openclaw.gateway.plist /Library/LaunchDaemons/ sudo launchctl load /Library/LaunchDaemons/com.openclaw.gateway.plist openclaw doctor curl -s http://127.0.0.1:18789/health sudo lsof -iTCP:18789 -sTCP:LISTEN
Étape 5 : trafic — MagicDNS ou reverse proxy
Hostname Gateway d’équipe (ex. gateway.tailnet-abc.ts.net) vers le nouvel hôte ; mobile et Channels sur le nouveau nom MagicDNS. Ne pas redémarrer le Gateway sur l’ancien hôte pendant le cutover.
Étape 6 : alléger le nœud build
Après validation Channels sur B : décharger Gateway launchd sur A, remonter parallèle CI à 5–6 (24 Go).
# mac-ci-01:确认已无流量打到 18789 后
sudo launchctl unload /Library/LaunchDaemons/com.openclaw.gateway.plist
sudo mv /Library/LaunchDaemons/com.openclaw.gateway.plist \
/Library/LaunchDaemons/com.openclaw.gateway.plist.bak
defaults write com.apple.dt.Xcode \
IDEBuildOperationMaxNumberOfConcurrentCompileTasks 6
Étape 7 : observer 48 h + rollback
Conserver backup .openclaw et plist.bak sept jours. Si P95 >200 ms ou Channels down : DNS retour, ancien plist — file CI intacte. Détails : onboarding OpenClaw Mac distant.
8. FAQ
Q1. 16 Go seul — rôles logiques sans second hôte ?
Ne remplace pas un split physique. Décaler dans le temps (builds la nuit) aide peu ; follow-the-sun ou Channels 7×24 recréent les pics. La séparation logique sert à prouver le besoin d’achat en mesurant la latence après déplacement Gateway.
Q2. Tailscale obligatoire ?
Non, mais fortement recommandé. LAN fournisseur, WireGuard ou tunnel SSH fonctionnent ; Tailscale apporte MagicDNS, ACL, peu d’ops. Deux M4 Hashvps Canada : RTT souvent <2 ms — suffisant pour webhooks build B → A.
Q3. Dual-node vs un seul 32 Go ?
Souvent proche : 24 Go build + 16 Go petit hôte vs 32 Go solo. L’enjeu est un SLA Gateway démontrable ; les équipes externes chiffrent les minutes d’indisponibilité, pas seulement le loyer.
Q4. Runner GHA hébergé — Mac dédié quand même ?
Souveraineté d’environnement. macOS hébergé à la minute pour les pics ; Mac cloud auto-hébergé dès >50 builds/jour fixes avec contrôle keychain/DerivedData. Gateway reste indépendant du modèle runner.
Q5. Rollback le plus rapide ?
DNS retour + launchctl load sur l’ancien hôte. Pas de gros builds le jour du rollback ; health + un message Channel, puis CI. Double copie d’état jusqu’à métriques stables.
Q6. Quelles métriques convainquent la direction ?
Deux semaines suffisent. Sur l’ancien setup, enregistrez P95 Gateway et événements memory_pressure Critical ; après migration, les mêmes mesures sur hôtes séparés. Si le P95 en pic de build passe de 400 ms à <100 ms et que Critical tombe à zéro sur l’hôte Gateway, le ROI est plus clair qu’un upgrade CPU. Documentez aussi le temps de build gagné grâce à plus de tâches de compilation parallèles sur 24 Go sans risque Gateway.
9. Conclusion
Séparer le Mac M4 CI n’est pas un échec, c’est l’étape naturelle après un tuning same-host réussi : build et Gateway sont tous deux nécessaires, mais plus dans la même barrette. Quatre signaux, T1 pour isoler les SLA, sept étapes blue/green — la ligne de partage, c’est l’isolation, pas le nombre de machines.
Si vous êtes coincés entre « Gateway lent » et « build ne doit pas s’arrêter », un hôte Gateway 16 Go dédié corrige plus que du Nice. Builds sur cloud 24 Go, agents et utilisateurs sur 18789 stable — topologie quasi-prod abordable pour petites équipes en 2026.
Dual-node : deux Mac mini cloud
Après séparation, le nœud build veut 24 Go et gros SSD pour DerivedData ; le Gateway 16 Go suffit en 7×24 faible latence. Hashvps Canada M4 bare metal, IPv4 dédiée, SSH/VNC prêts — deux machines même région, faible latence Tailscale, point de départ T1 souvent meilleur qu’un dur 32 Go sur un seul hôte.
Vous planifiez le passage same-host → dual-node ? Hashvps Mac mini M4 cloud est un excellent second nœud — Voir les offres et séparer CI et Gateway proprement.