Границы сценариев
Cron-веер уместен, если (а) все записи ведёт одна учётная запись эксплуатации, (б) задания короткие и идемпотентны, (в) каждая строка оборачивает одну критическую секцию в flock с жёстким лимитом по часам, (г) команда согласна с джиттером порядка минуты. Как только несколько «сквадов» кладут работу в общий пул, CI конкурирует с людьми за полосу codesign, а нужны приоритет (релиз впереди ночных скриншотов) и справедливость (ни один cron не голодает из‑за другого), одного cron недостаточно: без единой модели конкуренции знание «кого убивать в три ночи» остаётся в головах.
Nomad-стиль (буквальный HashiCorp Nomad или другой оркестратор с теми же идеями) окупается, когда нужно выразить максимум параллелизма по ключу ограничения: «на хосте только одна полоса подписи», «два разрешения SwiftPM, но один архив», — и когда перезапуск и переназначение должны быть декларативными, а не скопированными из десяти bash-файлов. Mac в этой схеме — исполнитель; кластерный менеджер часто живёт off-box. Это не компромисс из слабости, а разделение: обновляйте control plane там, где это скучно, оставляя Xcode там, куда его поставил Apple.
Жёсткая граница: если две job могут трогать один и тот же изменяемый каталог без описанного домена блокировки, вы уже в зоне «очередь + замки», а не «просто cron». Планируя второй узел, заранее решите, как submitter узнаёт, куда отправить задачу: иначе вы получите два независимых veерa cron и удвоение гонок.
Сравнительная таблица моделей очереди
Столбцы — это контракты эксплуатации: что дежурный может восстановить в 02:00 без чтения дюжины crontab и личных wiki.
| Измерение | Cron-веер + обёртки | Nomad-стиль, лёгкая очередь |
|---|---|---|
| Задержка до старта | Ограничена периодом cron (часто 60 с); укорочение периода усиливает стадо процессов. | Очередь или событие; при тёплых воркерах p50 часто < 1 мин. |
| Контроль параллелизма | Вручную: flock, счётчики, Redis — легко разъехаться. |
Декларативно: task groups, count, spread, affinity; потолки в одном месте. |
| Вытеснение / изоляция | Нет, пока не построите сигналы сами; «убить длинную job» — фольклор. | Политики restart/reschedule, классы приоритета, журнал по allocation. |
| Состояние и наблюдаемость | Логи разбросаны; correlation id редко согласованы. | Центральная история job; метки job / alloc. |
| Нагрузка на сопровождение | Дёшево поставить, дорого удерживать при росте команды. | Выше входной порог; меньше сюрпризов при пятом инженере. |
| Типичный отказ | Тихое перекрытие + порча кэша. | Неверно смоделированные ограничения (starvation) — правится в спеке, не в чате. |
Исполняемые конфигурационные фрагменты
Ужесточённый cron: одна полоса, неблокирующий flock -n на занятом замке, стеновые часы через timeout.
# фрагмент crontab — только пользователь buildlane
*/1 * * * * buildlane /usr/bin/flock -n /var/run/meshmac/build.lane.lock \
timeout 45m /usr/local/bin/run-ci-dequeue.sh >>/var/log/meshmac/dequeue.log 2>&1
В начале run-ci-dequeue.sh проверьте глубину очереди: если длина списка Redis, SQLite или другого брокера > 20, завершайтесь с кодом 77 и алертом — не оставляйте клиентов висеть без обратной связи.
Периодический batch в стиле Nomad (иллюстративный HCL; control plane отдельно, внутри скрипта — SSH или runner на Mac):
job "meshmac-mac-build" {
datacenters = ["dc1"]
type = "batch"
periodic {
cron = "*/2 * * * *"
prohibit_overlap = true
}
group "lane" {
count = 1
task "dequeue" {
driver = "exec"
config {
command = "/usr/local/bin/run-ci-dequeue.sh"
}
resources {
cpu = 500
memory = 512
}
restart {
attempts = 3
interval = "30m"
delay = "30s"
mode = "delay"
}
}
}
}
prohibit_overlap = true для периодики, отображающейся на одну физическую полосу архива — это аналог правила «никогда не кладите два полных архива в один корень DerivedData». Реальную нагрузку на Xcode оставьте внутри run-ci-dequeue.sh, чтобы политика жила в одном job spec.
Права и блокировки
Планировщик только расставляет задачи; macOS применяет права. Выделите учётку buildlane (или по команде) для неинтерактивных сборок, отделив её от консольных пользователей; доступ к связке ключей подписи — только для этой учётки. LaunchDaemons от root должны вызывать sudo -u buildlane, а не запускать Xcode от root.
- Гранулярность замков: один файл
.lockна общий ресурс (Pods, npm-кэш, слот Simulator), а не один гигантский замок на весь диск, если вы намеренно не сериализуете всё. - Метаданные очереди: рядом с lock храните JSON с
job_id,commit,runner_name, чтобы дежурный видел, кто должен ещё работать. - Изоляция после вытеснения: при убийстве allocation обеспечьте в
trapудаление частичных.xcarchiveи снятие замков — планировщик повторит попытку, диск не должен зарастать сиротами.
Чеклист приёмки конфликтов сборки
- ☐ Два одновременных dry-run на одном пути checkout дают нет пересечения записей (или пересечение только внутри секции с
flock≤ 120 с). - ☐ CI и ручные скрипты проходят только через один и тот же вход dequeue — нет «тайного» cron, обходящего очередь.
- ☐ Медиана ожидания замка укладывается в опубликованный SLA; алерт при p95 > 15 минут.
- ☐ После kill активной job не остаётся зависшего владельца flock (проверка PID, затем задокументированное снятие lock).
- ☐ После пяти ночных архивов подряд свободно > 15% диска; иначе — разделение полос или дополнительный узел.
Таймауты и параметры отката
Скопируйте в внутренний runbook и подстройте по измеренным p95 длительностям сборки.
| Параметр | База: cron + shell | База: Nomad-стиль |
|---|---|---|
| Стеновые часы job | timeout 45m лёгкая полоса; 90m тяжёлый архив. |
Согласуйте kill_timeout с теми же числами; не оставляйте дефолт 30 с для Xcode. |
| Ожидание замка | flock -w 180 для менеджеров пакетов; при таймауте — отказ с метрикой. |
Ожидание в воркере; перезапуск без busy-loop — минимум delay = "30s". |
| Откат повторов | Экспонента: 30 с, 60 с, 120 с с потолком; множитель джиттера 0,8–1,2. |
reschedule: max_delay = "5m", delay_function = "exponential". |
| Потолок ожидающих | До 20 pending на полосу; отказ с понятным HTTP-кодом или exit. | Дублируйте на submitter и в брокере; Nomad не угадывает ваш SLA. |
Пример джиттера в bash 5+: sleep $((30 + RANDOM % 30)) перед повторной попыткой dequeue. Логируйте attempt, next_backoff_s и reason на каждой ветке — запросы к Grafana останутся простыми.
FAQ
- Обязательно ли ставить Nomad на сам Mac?
- Нет. Частая схема — планировщик на Linux, Mac как удалённый исполнитель. Цель — один декларативный контракт на перекрытие, перезапуск и историю, а не марка бинарника.
- Cron «проще» — почему очередь всё равно ломается?
- Простота установки не равна простоте эксплуатации. Веер без центрального лимита глубины порождает невидимый бэклог; учащение cron только стягивает больше процессов к тем же замкам.
- Где должны жить правила вытеснения?
- В слое планировщика для жизненного цикла аллокаций и в runbook для человеческих сессий (VNC, пара программирования). Не полагайтесь на «все знают, что в полдень архив не гоняем».
- Чем отличается откат в shell от блока reschedule в Nomad?
- Shell-откат — экспонента с джиттером только на идемпотентных шагах. Задержки reschedule в Nomad — между попытками аллокации; держите
max_delayменьше половины таймаута, видимого в CI, чтобы пайплайн падал быстро, а не висел до глобального лимита.
Cron-веер — рабочий хак для одной полосы, если глубина, таймауты и домены flock ведутся как прод-код. Nomad-стиль окупается, когда ключи конкуренции, политика restart и журнал должны соответствовать тому, как команда реально делит время Xcode. В обоих случаях права macOS и домены блокировок не опциональны. Если p95 ожидания не проходит чеклист, масштабируйте узлы, а не наращивайте рискованный параллелизм на одном диске: разнесите архив и интерактив, добавьте второй билдер в пуле MeshMac-класса.
Ознакомьтесь с контекстом пула на главной MeshMac; тарифы и оформление без входа в аккаунт — на странице покупки и планов; SSH, VNC и онбординг — в центре помощи. Для сценариев с несколькими узлами и общими ресурсами сборки выберите пакет с мультиузлом или выделенной сборочной ёмкостью раньше, чем снова поднимать параллелизм на одном хосте.
Сначала узлы и полосы — потом спор о параллелизме
Второй арендованный Mac под codesign или ночные архивы почти всегда выгоднее третьего одновременного xcodebuild на одном томе. Сравнение планов (без логина) · Помощь · Блог.