Как устроена iOS-инфраструктура hh.ru: от коммита до релиза

Путь изменения в iOS-приложениях hh.ru начинается задолго до публикации в App Store. Команда автоматизировала большую часть процесса: от подготовки окружения разработчика и генерации проекта до проверок в pull request, загрузки сборок в TestFlight и поэтапного релиза для пользователей.
Инфраструктура обслуживает два приложения — для соискателей и работодателей, пять региональных версий и около 160 фича-модулей. Над iOS-продуктами работают 30 разработчиков, релизы выходят раз в неделю, а за сутки CI/CD обрабатывает до 55 прогонов. При такой нагрузке важны не только скорость отдельных проверок, но и стабильность всей цепочки.
Подготовка проекта и отправка изменений
Локальное окружение настраивается с помощью двух скриптов. bootstrap.sh готовит систему с нуля, создаёт необходимые кэши и генерирует проект, а up.sh используется для повторной генерации в повседневной работе. Проект и модули описываются через манифесты, а для их генерации применяется Tuist.
В процессе также создаются локализации и код для аналитики. События не приходится описывать вручную. После выполнения задачи разработчик отправляет коммит в Forgejo — самостоятельный Git-хостинг с поддержкой pull request, хуков и разграничения прав. Система развёрнута на собственном оборудовании, поэтому работа репозитория не зависит от внешнего сервиса.
CI/CD запускается вручную для отдельной ветки или автоматически после создания pull request. Ранее команда использовала Bamboo, но в 2024 году отказалась от него из-за отсутствия поддержки и критических обновлений используемой версии. В качестве замены рассматривались Jenkins, GitLab CI и Forgejo Actions. Выбор остановился на Jenkins: он хорошо работает с macOS, интегрируется с внутренней инфраструктурой, расширяется плагинами и поддерживает управление очередью по принципу «первым пришёл — первым вышел».
Какие задачи выполняет CI/CD
Планы CI/CD разделены на несколько категорий:
- Служебные: генерация словарей и предзашитых API, сбор аналитических событий, синхронизация локализаций и обслуживание машин.
- Регрессионные: ночные и утренние проверки, а также повторные запуски после автоматического вливания свежего develop в открытые pull request.
- Прогоны без статистики: черновые проверки для веток, падения которых не должны влиять на общие метрики.
- Проверки pull request: облегчённые для рабочих веток и полные для изменений, направленных в develop.
Проверка pull request в develop
Полный план начинается с остановки предыдущих сборок. Если разработчик уже отправил новый коммит, проверка старой версии больше не нужна и только занимает ресурсы CI.
Затем детектор анализирует diff и определяет, какие приложения затронуты. Изменения только в приложении для соискателей не запускают проверки версии для работодателей, и наоборот. Если затронуты общие модули или core-компоненты, проверяются оба приложения. Документационные изменения могут не запускать сборку вовсе.
На следующем этапе проект собирается один раз, после чего запускаются Unit-тесты и статический анализ. В проверку входят SwiftLint, поиск неиспользуемого кода с помощью Periphery и контроль предупреждений компилятора. Варнинги блокируют вливание в develop, поэтому в ветку должен попасть код без таких замечаний.
Jenkins определяет, когда и где выполнять шаги, а Fastlane описывает сами действия в виде Ruby-сценариев. Благодаря этому команды, используемые на CI, в большинстве случаев можно повторить локально. Собранный проект готовится в режиме build_for_testing, а Unit- и UI-тесты запускаются с параметром test_without_building. Сборка вместе с файлом .xctestrun упаковывается и передаётся на агентов UI-тестов, что избавляет систему от повторной компиляции.
UI-тесты и отчёт в pull request
Параллельно с проверками создаётся предрелизная AdHoc-сборка и загружается в TestFlight. Её использует QA для ручной проверки задачи. Самая продолжительная часть CI — UI-тесты. Они делятся на батчи, каждый из которых выполняется на отдельном Mac Mini, подключённом к CI/CD.
Для приложения соискателей в pull request в develop используется четыре батча, для приложения работодателей — два, а региональные версии проверяются одним батчем. Внутри каждого батча тесты дополнительно распределяются между копиями симуляторов. На машинах с 16 ГБ памяти запускаются три симулятора, с 24 ГБ — четыре, с 36 ГБ — пять.
После тестов запускается Danger. Поскольку штатной интеграции с Forgejo нет, команда использует собственный форк инструмента. Он публикует в pull request единый отчёт: результаты статического анализа и тестов, предупреждения о неиспользуемом коде и изменение размера сборки.
Для падших проверок предусмотрен отдельный плагин Retry. Он повторяет только неуспешные шаги, а на стадии UI-тестов — только упавшие тесты, а не весь батч. После успешного прохождения проверок pull request передаётся QA. Право влить изменения в develop ограничено настройками репозитория: QA проверяет зелёные чеки, отчёты и сборку в TestFlight, после чего выполняет merge.
Еженедельный релиз
Релизный срез формируется раз в неделю и включает все изменения, накопившиеся в develop. Процесс ведёт дежурный QA, который запускает отдельный автоматизированный план. Дальнейшие действия определяются статусом релизной задачи и данными в базе.
- Повышается номер версии, создаются релизная ветка и задача в Jira со списком вошедших изменений.
- Запускаются smoke-тесты, а результат записывается в релизную задачу.
- Собираются версии для всех регионов и загружаются в TestFlight для ручной проверки.
- QA отправляет сборку на ревью Apple, после одобрения начинается поэтапная раскатка.
- После выпуска создаётся pull request из релизной ветки обратно в develop, а в рабочий чат отправляется отчёт об изменении размера приложения.
Стабильность и метрики
Оптимизация CI/CD направлена на сокращение времени поставки без постоянного расширения парка оборудования. Команда увеличивала мощности тестовой инфраструктуры, улучшала тестовые стенды и снижала количество ретраев. В общей сложности работа над стабильностью заняла около трёх месяцев, а число запусков CI и доля успешных прогонов продолжили расти.
UI-тесты зависят не только от агентов, но и от тестовых стендов. Перед прогоном стенд проверяется, а при проблемах выбирается другой из пула. Результаты проверок поступают в мониторинг, при сбоях срабатывает алерт. По будням Mac Mini обслуживаются автоматически: удаляются симуляторы, старые версии Xcode и неиспользуемые окружения, очищаются кэши, после чего машины перезагружаются и настраиваются заново.
Один из главных выводов команды связан с балансом между временем выполнения и ожиданием в очереди. В эксперименте с UI-тестами приложения для соискателей число батчей увеличили с четырёх до шести. Каждый батч стал выполняться примерно на три минуты быстрее, но p80 времени ожидания вырос с 25 до 63 минут, а доля ретраев — с 8% до 12,5%. Через три недели конфигурацию вернули к четырём батчам, после чего обе метрики сразу улучшились.
Таким образом, ускорение отдельного шага не всегда означает ускорение поставки целиком. Для эффективной iOS-инфраструктуры приходится одновременно учитывать длительность тестов, загрузку агентов, очереди, стабильность стендов и количество повторных запусков. Именно совокупность этих показателей позволяет hh.ru поддерживать еженедельный релизный цикл при растущем потоке изменений.


