← К дневнику

Когда делить Mac M4 CI? Модель решения и миграция на dual-node (2026)

OpenClaw · 2026.06.23 · ~11 мин

Узел сборки Mac M4 CI и dual-node OpenClaw Gateway

В начале июня мы посадили Xcode CI и OpenClaw Gateway на один канадский M4 и протянули две недели — с launchd Nice=-5 и ограниченной параллельностью сборки. На третьей неделе в IM пошли жалобы: «Gateway снова тормозит». Логи сборки были зелёными, а health check curl в пике уезжал с 50 мс выше 400 мс. Мы снижали параллельность, чистили DerivedData, временно подняли тариф до 24 ГБ — проблема стала «раз в день» вместо «дважды», но не исчезла.

Это не ошибка конфигурации, а потолок модели same-host. Порты не конфликтуют (20300 vs 18789), ядер CPU хватает — борьба идёт за unified memory и swap. Статья продолжает совместное размещение и tuning launchd: когда нужен dual-node, как нарисовать топологию и переключить трафик без поломки Channels. Память и сборки: документация Apple по кэшу сборки Xcode; сеть: руководство по установке Tailscale.

Коротко: водораздел — в изоляции ресурсов, а не в «купить ещё одну машину».

  • Четыре жёстких сигнала — достаточно одного

    ≥50 сборок/день, ≥3 параллельных пайплайна, Gateway для конечных пользователей или ≥3 Critical swap за неделю — дальнейший Nice лишь откладывает.

    ≥50/день

  • Минимальный dual-node: build + выделенный Gateway

    Узел сборки 24 ГБ+ и большой SSD; узел Gateway 16 ГБ на ~500 МБ постоянной нагрузки. Два M4 через Tailscale; в интернет — только нужные входы.

    16 ГБ Gateway

  • Миграция = состояние + DNS, не переустановка

    rsync конфига, те же токены, переключение MagicDNS Tailscale; сборка не останавливается, blue/green Gateway ≤15 минут.

    Blue/green ≤15 мин

1. Почему same-host внезапно «не тянет»

Многие команды стартуют с одного облачного Mac на три роли: xcodebuild, OpenClaw Gateway на 18789, иногда VNC для связки ключей. При низкой нагрузке 16 ГБ кажутся с запасом. С ростом веток, параллельных UI-тестов и Channels из внутреннего пилота в прод-IM кривая памяти из «пилы» становится «плато» — через десять минут после сборки сжатые страницы всё ещё >8 ГБ, heap Gateway уходит в swap.

Тюнинг same-host (меньше параллельности, выше Nice, очистка кэша) снижает вероятность наложения пиков, но не убирает сами пики. Когда дневные сборки APAC и вечерний IM-пик в Северной Америке почти наверняка пересекаются, вы уже не оптимизируете параметры — вы играете в рулетку с планировщиком. У Apple Silicon нет отдельного VRAM; Xcode и Node.js Gateway делят один физический пул. После memory_pressure Warn пользователи чувствуют джиттер; CI видит редкие таймауты и сбои подписи.

Разделение — не миф «масштабирования», а разведение несжимаемых пиков: сборки только на узле A, RAM Gateway никогда не вытесняется xcsbuildd. Та же логика, что в самостоятельном macOS runner GitHub Actions на облачном Mac — выделенный runner без смешения с рабочим столом; здесь разделяют Gateway и CI.

Для команд с формальными SLA триггер часто не первый swap, а третий тикет «Gateway тормозит» за неделю при зелёных дашбордах CI. Тогда same-host перестаёт экономить — он прячет воспринимаемый простой.

Полезный эксперимент: измеряйте P95 Gateway только во время активных job xcodebuild. Если задержка растёт в 3–8 раз при CPU <70 %, это не сеть — это конкуренция за RAM. У нас compressed memory держалась >6 ГБ ещё десять минут после сборки; heap Node.js Gateway не мог стабильно оставаться resident. Каждое снижение -jobs или повышение Nice урезало пропускную способность CI, не спасая SLA Gateway. Классическая ловушка same-host: две команды оптимизируют друг против друга на одной машине.

Кто уже крутит self-hosted runner на облачном Mac, знает правило «без рабочего стола и без Gateway на runner». Dual-node split — то же правило, только Gateway здесь не побочный сервис, а прод-критичный. Отличие от «просто взять 32 ГБ»: больше RAM поднимает потолок для обоих пиков сразу, но не разводит их. Если пик сборки по-прежнему ест 18–22 ГБ, Gateway остаётся с остатком — а при параллельных UI-тестах остатка мало.

2. Три топологии: same-host, dual-node, triple-node

Сначала классификация, потом закупка:

  • T0 same-host: один M4 для Xcode Server / self-hosted runner + Gateway. <30 сборок/день, внутренний Gateway, терпимые всплески задержки.
  • T1 dual-node (фокус): узел A только CI (24 ГБ+, большой SSD); узел B только Gateway (16 ГБ). Tailscale или LAN ЦОД; Channels/Dashboard → B, триггеры сборки → A.
  • T2 triple-node: отдельный хост подписи/upload или параллельных тестов. >150 сборок/день или жёсткое разделение TestFlight/compile; большинству команд хватает T1.

Асимметричный вывод: дело не в слабости M4, а в двух классах SLA в одном пуле RAM. Gateway нужен 99,9 % стабильного отклика; CI нужна пропускная способность. Same-host компромиссит оба одним SLA.

T1 dual-node: сборка + выделенный Gateway Узел A · CI сборка M4 24ГБ+ · 1ТБ SSD xcodebuild / Runner DerivedData локально :20300 Xcode Server Узел B · Gateway выделенный M4 16ГБ · 256ГБ OpenClaw Gateway Channels / Dashboard :18789 стабильная задержка Tailscale <5ms SSH инженера / триггер CI → A; IM / mobile / agent → B
Минимальная dual-node: пики сборки и SLA Gateway физически разведены

3. Same-host vs dual-node vs апгрейд: сравнение

Пути масштабирования Mac M4 CI (2026)
Измерение Продолжить тюнинг Nice / меньше параллели Один хост 32 ГБ Больше RAM, та же топология Split dual-node CI + Gateway раздельно
Корневая причинаСмягчает наложение пиковПоднимает потолок RAMИзолирует два SLA
Задержка GatewayДжиттер со сборкамиЛучше, не ноль<100 мс возможно при сборке
Месячная стоимость (грубо)МинимумСредне (апгрейд)Средне (+ малый 16 ГБ)
Сложность opsНизкаяНизкаяСредняя (+1 хост, Tailscale)
Подходящая фаза<30 сборок/день2 параллельных job, внутр. GW≥50/день или внешний GW
Чеклист сигналов split (один критерий → T1)
Сигнал Порог Наблюдение Ложное срабатывание
Частота сборок≥50/деньЛоги CI / счётчик runnerИсключить ручной локальный archive
Параллельность≥3 полных сборкиxcodebuild -jobs, глубина очередиЛёгкий lint не считается
Аудитория GatewayКонечные пользователи / Channels 7×24SLA продуктаТолько внутренний webhook может подождать
Давление памяти≥3 Critical/неделюЛоги memory_pressureСначала устранить разовую утечку
Диск>50 ГБ/мес + высокий I/O waitdf -h + iostatСначала очистить archives
Апгрейд не заменяет split
32 ГБ same-host подходит для «2 параллельных job + внутренний Gateway». Если Gateway уже внешний, больше RAM сдвигает джиттер с трёх раз в день на один — пользователи всё равно скажут «снова тормозит».

4. Сценарии: матрица решений

  • Дуэт, <20 сборок/день, Gateway только для себя: остаться на T0, хватит tuning launchd.
  • Сборки APAC, Gateway для IM-пользователей: сразу T1. Follow-the-sun почти гарантирует наложение пиков.
  • Облачный runner GHA, Gateway только запущен: runner на A, Gateway на B; не вешать 18789 на машину runner.
  • Загрузка TestFlight vs компиляция за диск: сначала T1; при необходимости T2 хост подписи — не путать с этим runbook.
  • Бюджет на одну машину: приоритет SLA Gateway, сборки ночью или меньше параллели — компромисс, не долгосрочная архитектура.

Если post-mortem показывает рост P95 Gateway только во время xcodebuild, это не сеть — это конкуренция за RAM. T1 решает это без остановки пайплайна.

Для распределённых команд с follow-the-sun: даже если ночные archive идут в Северной Америке, Gateway днём обслуживает agent-сессии и IM-Channels. Вероятность совпадения release-сборки с прод-инцидентом растёт с каждой feature-веткой. T0 работает, пока Gateway «best effort»; как только Product привязывает SLA отклика в Confluence, вы фактически обязаны в T1 — просто без второй машины.

Возражение «бюджет на одну машину»: приоритет SLA Gateway, сборки в ночное окно. Это переход, не целевая архитектура. За 4–8 недель обычно снова те же тикеты из-за роста частоты коммитов. Планируйте второй узел 16 ГБ под Gateway с самого начала; ROI — в меньшем support-времени, а не только в абсолютной аренде.

5. Рекомендуемые стеки (комбинируются)

  1. Минимальный dual-node: Hashvps Canada M4 24 ГБ (A, CI) + M4 16 ГБ (B, Gateway) + tailnet Tailscale + еженедельно openclaw doctor. Стандартный апгрейд с T0.
  2. Гибрид CI: A с self-hosted runner GitHub Actions; B Gateway + Channels. Оркестрация в GitHub, исполнение на macOS, Gateway вне пиков сборки. Согласуется с «суверенитетом среды» в рынке macOS-сборок 2027.
  3. Наблюдаемость: зонд задержки Gateway на B (curl каждые 5 с) + снимок memory_pressure на A после сборки; двух недель данных хватает для ROI split перед руководством.

6. Типичные ошибки перед миграцией

  • Сначала остановить CI, потом переносить Gateway: дорого; blue/green Gateway, CI работает.
  • Переустановка вместо rsync: потеря ~/.openclaw, токенов, pairing Channels → все перелогиниваются.
  • Два публичных IP на 18789: во время cutover Tailscale или внутренний DNS — никогда два Gateway на Channels.
  • Слишком рано удалить Gateway с build-хоста: окно отката: старый процесс жив, меняется только DNS.
  • Часы и сертификаты: дрейф NTP >30 с → sporadic сбои токенов; в день миграции sudo sntp -sS time.apple.com.

7. Runbook: dual-node за семь шагов

Допущение: раньше mac-ci-01 с CI+Gateway; новый mac-gw-02 (16 ГБ) только Gateway. Tailscale установлен (см. ops runbook OpenClaw).

Шаг 1: baseline (накануне)

Зафиксировать P50/P95 Gateway, число сборок, распределение memory_pressure. Экспорт openclaw status и списка Channels.

bash — baseline: задержка Gateway и давление памяти
# 延迟采样 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

Шаг 2: новый хост + Tailscale

На mac-gw-02: обновление macOS, Homebrew, Node, Tailscale; ping к mac-ci-01 <5 мс. На B не запускать xcodebuild.

Шаг 3: rsync состояния Gateway (окно обслуживания)

bash — синхронизация конфига OpenClaw на Gateway-хост
# 在原机 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/

Шаг 4: запуск Gateway на новом хосте

bash — загрузка сервиса и проверка health
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

Шаг 5: трафик — MagicDNS или reverse proxy

Командный hostname Gateway (напр. gateway.tailnet-abc.ts.net) на новый хост; mobile и Channels на новое имя MagicDNS. Не перезапускать Gateway на старом хосте во время cutover.

Шаг 6: разгрузить узел сборки

После проверки Channels на B: снять Gateway launchd с A, вернуть параллельность CI 5–6 (24 ГБ).

bash — убрать Gateway с build-хоста
# 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

Шаг 7: наблюдать 48 ч + откат

Хранить backup .openclaw и plist.bak семь дней. При P95 >200 мс или падении Channels: DNS назад, старый plist — очередь CI не трогать. Детали: onboarding OpenClaw на удалённом Mac.

На этапе наблюдения сборки специально не останавливают: нужно убедиться, что build-узел держит повышенную параллельность, а P95 Gateway на B остаётся плоским. Сравните baseline из шага 1 с теми же часовыми окнами после миграции. Типичный успех: Critical memory_pressure на A может ещё случаться, но только во время сборок — больше не коррелирует с задержкой Gateway на том же хосте, потому что общего хоста больше нет.

Если команда сомневается в стоимости второй машины, посчитайте не только аренду, но и время инженеров на расследование «Gateway тормозит при зелёных CI-логах». Один такой инцидент в неделю быстро съедает экономию от отказа от dual-node.

8. FAQ

В1. Только 16 ГБ — логические роли без второго хоста?

Не заменяет физический split. Сдвиг по времени (сборки ночью) помогает ненадолго; follow-the-sun или Channels 7×24 снова создадут пики. Логическое разделение полезно, чтобы доказать закупку второй машины по задержке после переноса Gateway.

В2. Обязателен ли Tailscale?

Нет, но настоятельно рекомендуется. LAN провайдера, WireGuard или SSH-туннель тоже подойдут; Tailscale даёт MagicDNS, ACL, мало ops. Два M4 Hashvps Canada: RTT часто <2 мс — хватает для build webhook с B на A.

В3. Dual-node vs один хост 32 ГБ?

Часто близко: 24 ГБ build + 16 ГБ малый хост vs 32 ГБ solo. Важен доказуемый SLA Gateway; внешние команды считают минуты простоя, а не только аренду.

В4. Хостинговый runner GHA — всё равно свой Mac?

Суверенитет среды. Хостинговый macOS по минутам для пиков; self-hosted облачный Mac при стабильных >50 сборок/день с контролем keychain/DerivedData. Gateway независим от модели runner.

В5. Самый быстрый откат?

DNS назад + launchctl load на старом хосте. В день отката без массовых сборок; сначала health + одно сообщение Channel, потом CI. Двойная копия состояния до стабильных метрик.

В6. Какие метрики убеждают руководство?

Хватит двух недель. На старой схеме фиксируйте P95 Gateway и события memory_pressure Critical; после миграции — те же метрики на раздельных хостах. Если P95 в пике сборки падает с 400 мс до <100 мс, а Critical на Gateway-хосте обнуляется, бизнес-кейс яснее любого CPU-апгрейда. Зафиксируйте и «сэкономленное время сборки»: больше параллельных compile tasks на 24 ГБ без риска для Gateway.

9. Итог

Разделение Mac M4 CI — не провал, а следующий этап после успешного same-host tuning: сборка и Gateway оба нужны, но уже не в одной планке RAM. Четыре сигнала, T1 для изоляции SLA, семь шагов blue/green — водораздел в изоляции, а не в числе машин.

Если вы застряли между «Gateway тормозит» и «сборку нельзя останавливать», выделенный Gateway-хост 16 ГБ ближе к решению, чем ещё один Nice. Сборки на облаке 24 ГБ, агенты и пользователи на стабильном 18789 — доступная quasi-prod топология для малых команд в 2026. Это не роскошь, а способ перестать тушить одни и те же инциденты каждую неделю.

Dual-node: два облачных Mac mini

После разделения узлу сборки нужны 24 ГБ и большой SSD под DerivedData; Gateway хватает 16 ГБ для 7×24 с низкой задержкой. Hashvps Canada M4 bare metal, выделенный IPv4, SSH/VNC из коробки — две машины в одном регионе, низкая задержка Tailscale, удобный старт T1, часто лучше жёсткого апгрейда до 32 ГБ на одном хосте.

Планируете переход same-host → dual-node? Облачный Mac mini M4 Hashvps — сильная вторая машина Смотреть тарифы и развести CI с Gateway чисто.

Hashvps · Mac Cloud

Разделите сборку и Gateway — стабильнее

CI 24GB + Gateway 16GB, bare-metal macOS, выделенный IP, Tailscale.

Смотреть тарифы
Акция