Интент — принять решение, а не «почитать в теории». На общем удалённом Mac (self-hosted runner) bundle install распадается на два независимых потока: загрузка .gem с индекса и клонирование git для gem с :git / GitHub. Трансграничный канал добавляет RTT, лимиты соединений и дрейф Gemfile.lock. Ниже — сравнимые строки для зеркал, BUNDLE_JOBS и BUNDLE_RETRY, схем vendor/cache и ключей кеша CI, а также порогов повторов. Страница открывается без входа; начните с списка статей блога и главной MacPull. Смежные материалы: Composer и Packagist в CI, Gradle и Maven — кеш на удалённом Mac, Git, npm и Homebrew в трансграничной CI.
  • Две скорости в одном job: параллель по HTTP не ускоряет узкий git; совместный рост BUNDLE_JOBS и числа pipeline на одном egress даёт случайные таймауты.
  • Зеркало без runbook: при падении корпоративного прокси команда не знает, отключать ли mirror.https://rubygems.org и за сколько секунд переключаться на резерв.
  • Lock «почти тот же»: различия PLATFORMS, патча Ruby или строки BUNDLED WITH превращают CI в генератор скрытого обновления зависимостей.

Сценарии и узкие места

Если в логе долго висит Fetching gem metadata или Fetching source index, сначала проверьте путь к rubygems.org и зеркалу. Если доминируют строки Git error или долгие git clone, приоритет — аутентификация, глубина клона и лимиты одновременных git-операций, а не очередное удвоение BUNDLE_JOBS.

Решение по умолчанию: классифицируйте каждый gem как «индекс + .gem» или «git-источник», зафиксируйте целевые версии Ruby и Bundler в документации репозитория и в образе CI, затем подбирайте числа из следующих разделов. Один раз за прогон меняйте только один параметр — иначе регрессии по сети неотличимы от регрессий по lockfile.

Симптом Вероятная причина Первый шаг
429 / reset по HTTPS к индексу Лимит на стороне зеркала или слишком агрессивный параллелизм нескольких job Снизить BUNDLE_JOBS до 3–4; ограничить одновременные pipeline; проверить зеркало
Таймауты только на git gem Длинный канал, большой репозиторий, отсутствие shallow-стратегии GIT_TERMINAL_PROMPT=0; уменьшить параллелизм git; кеш по SHA коммита в lock
Install прошёл, тесты падают «как будто другая версия» Дрейф разрешения без строгого режима или разный Bundler bundle lock --check в gate; --deployment / --frozen в CI

Параллелизм и пул соединений: BUNDLE_JOBS, BUNDLE_RETRY и пороги повторов

BUNDLE_JOBS задаёт параллелизм внутри одного bundle install. На трансграничном канале выгода часто упирается в полосу и в число устойчивых TCP-сессий. BUNDLE_RETRY сглаживает кратковременные обрывы; повторы на уровне workflow (GitHub Actions retry, GitLab retry:) задавайте согласованно с BUNDLE_RETRY, чтобы не умножать ожидание бессмысленно.

Профиль runner BUNDLE_JOBS (старт) BUNDLE_RETRY Повторы job (разумный максимум) Backoff (сек)
Общий Mac, 4 vCPU, 1 активный pipeline 3–4 3–4 1–2 2, 4, 8 (до ~60 с суммарно, затем эскалация)
Выделенный runner, 8 vCPU, стабильный egress 6–8 3 1 2, 4, 8
Несколько pipeline на одном NAT / прокси 2–3 на job 4–5 2–3 4, 8, 16 при частых 5xx
Monorepo с множеством git gem 2–4 3–5 2 Клоны дороже HTTP — backoff на уровне job предпочтительнее «бесконечного» retry внутри Bundler

Переменные окружения и команды (bash, подставьте секреты CI):

export BUNDLE_JOBS=4
export BUNDLE_RETRY=3
export GIT_TERMINAL_PROMPT=0
# Пример GitHub HTTPS (токен — только из секретов пайплайна)
export BUNDLE_GITHUB__COM="x-access-token:${GITHUB_TOKEN}"

bundle config set --local deployment true
bundle config set --local path vendor/bundle
bundle lock --check
bundle install --jobs "${BUNDLE_JOBS}" --retry "${BUNDLE_RETRY}"

Для соседних стеков на том же хосте сверьтесь с FAQ по стабильности pull (Git, Homebrew, npm) — суммарная нагрузка на диск и DNS влияет на воспроизводимость даже при «правильных» числах Bundler.

Трансграничные зеркала и стратегия Git-источников

Корпоративное или региональное зеркало rubygems снижает RTT к индексу и к .gem, но добавляет требования к актуальности индекса и к аудиту доверия. Для gem из Git фиксируйте транспорт (HTTPS с токеном или SSH с deploy key) и не смешивайте стили в одном репозитории без документации.

Маршрут Ключевая настройка Когда выбирать Риск
Прямой rubygems.org По умолчанию Runner в регионе с коротким путём до CDN 429 и джиттер на длинном канале
Зеркало bundle config set --global mirror.https://rubygems.org https://gems.example.com Трансграничный основной трафик Задержка обновления индекса; политика доверия URL
GitHub HTTPS BUNDLE_GITHUB__COM или ~/.netrc Приватные форки и org-гемы Срок токена, scope, IP allowlist
Git по SSH GIT_SSH_COMMAND, known_hosts Deploy key без хранения PAT в логах Конкуренция за ssh-agent при высоком параллелизме job

Пример зеркала и минимизации объёма git (проверьте политику безопасности shallow):

bundle config set --global mirror.https://rubygems.org https://gems.example.com
git config --global fetch.depth 1

bundle install --jobs 4 --retry 3

Дрейф Gemfile.lock, vendor/cache и ключи кеша CI

Ключ кеша должен включать как минимум хеш Gemfile.lock и версию Ruby (желательно с патчем). При фиксированной версии Bundler добавьте её в ключ или закрепите в образе. vendor/bundle удобен для быстрого восстановления; vendor/cache после bundle package даёт режим, близкий к офлайн-установке на нестабильных каналах.

Стратегия Каталог артефакта Фрагмент ключа CI Комментарий
Только path install vendor/bundle hashFiles('Gemfile.lock') + ruby-version Простой путь; следите за гонками при общем томе
package + lock в Git vendor/cache хеш lock + опционально хеш списка vendor/cache/*.gem Сильнее при «плохом» egress; растёт размер репозитория
Глобальный кеш Bundler $BUNDLE_USER_HOME или дефолтный user cache lock + версия Bundler Нужна политика очистки на общих агентах

Чеклист приёмки Gemfile.lock перед merge:

1
ruby -v и образ CI совпадают по патчу.
2
В Gemfile.lock в PLATFORMS есть платформа агента (при необходимости bundle lock --add-platform).
3
Строка BUNDLED WITH согласована с bundle -v в CI или зафиксирована через образ.
4
bundle lock --check без изменений; bundle install --deployment (или --frozen) не переписывает lock.
5
При vendor/cache: bundle install --local --deployment проходит на чистом клоне.
bundle lock --check
bundle install --deployment --jobs 4 --retry 3

# Поток с vendor/cache
bundle package --all
# после коммита vendor/cache
bundle install --local --deployment --jobs 4 --retry 3

FAQ

Почему BUNDLE_JOBS=16 не даёт выигрыша? Скорее всего, узким местом является сеть или git, а не локальный CPU. Вернитесь к 4–6, измерьте p95 этапа «Fetching» и только потом повышайте параллелизм на выделенном агенте с мониторингом.

Сколько раз подряд повторять bundle install в workflow? Три попытки с паузами 2 / 4 / 8 с обычно достаточно для кратковременных сбоев; если исчерпаны — меняйте зеркало или снижайте параллелизм соседних job, а не увеличивайте счётчик до двузначных значений.

Совместимы ли bundle config deployment true и кеш CI? Да: deployment запрещает неявные изменения, кеш ускоряет повторную установку. Ключ должен меняться при любом изменении lock.

Нужен ли отдельный job только для bundle lock --check? Для крупных репозиториев это дешёвый gate: он ловит дрейф до дорогих шагов с Xcode или Docker. Свяжите с политикой веток.

Где взять ближайший к зеркалам egress для Ruby CI? Аренда удалённого Mac в регионе с одобренным исходящим трафиком сокращает джиттер и упрощает воспроизводимость по сравнению с постоянными трансграничными прогонами с ноутбуков разработчиков.

Структурированные данные HowTo и FAQPage

В блоке <head> размещены JSON-LD типов BlogPosting, Article, BreadcrumbList, HowTo и FAQPage. При правках текста FAQ синхронизируйте формулировки с разметкой; не вставляйте секреты и внутренние URL без публикации.

Итог

Разделите потоки rubygems и git, задайте зеркало и осмысленные BUNDLE_JOBS / BUNDLE_RETRY, включите строгий install с актуальным Gemfile.lock и соберите ключ кеша из lock и версии Ruby. На общем пуле Apple Silicon ограничивайте одновременные pipeline жёстче, чем кажется «по ядрам». Аренда удалённого Mac рядом с одобренным egress превращает эту матрицу в устойчивый ежедневный CI без лотереи по сети.

Дальше без входа: главная, цены и узлы, покупка и аренда, центр помощи, технический блог.

Зафиксируйте lock как контракт, относитесь к параллелизму как к бюджету соединений на весь хост, а зеркала держите с явным планом переключения — тогда Bundler на удалённом Mac перестаёт быть главным источником флапа в пайплайне.

Удалённый Mac для Ruby / Bundler CI

Узлы Apple Silicon, быстрый локальный SSD под кеш gem и SSH для отладки. Главная, тарифы, покупка, помощь и блог доступны без обязательного входа — оцените аренду для стабильного bundle install рядом с вашим зеркалом.