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:
ruby -v и образ CI совпадают по патчу.Gemfile.lock в PLATFORMS есть платформа агента (при необходимости bundle lock --add-platform).BUNDLED WITH согласована с bundle -v в CI или зафиксирована через образ.bundle lock --check без изменений; bundle install --deployment (или --frozen) не переписывает lock.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 рядом с вашим зеркалом.