Коротко: мы выпустили новое приложение для macOS под названием Cilicon, которое предоставляет и запускает эфемерные виртуальные машины для CI (Cilicon Installer — автономное приложение). Используя его, мы смогли переключиться на собственные Actions Runners и ускорить нашу CI в 3 раза, а также дать вторую жизнь некоторым из наших поврежденных устройств M1 MacBook Pro.
Наши проблемы с CI на macOS
Когда мы запускали Trade Republic, наше мобильное CI состояло из одного Mac Pro 2013 года выпуска, на котором работал агент Buildkite Agent. Его производительность была удовлетворительной, но быстро стало ясно, что ручное обслуживание более чем одной машины в долгосрочной перспективе нецелесообразно. В 2020 году, когда команда выросла и начались карантины из‑за COVID-19, мы начали искать альтернативы.
Хотя это означало снижение производительности, мы решили переключиться на раннеры (runner — это специальное приложение, предназначенное для запуска тестов, проверки выходных данных и предоставления инструментов для отладки и диагностики тестов и приложений), размещенные на GitHub. Это позволило нам выполнять больше заданий параллельно и вообще не беспокоиться об обслуживании.
В то время наш рабочий процесс iOS выполнялся всего около 10 минут (при использовании кэшированных зависимостей), но это быстро увеличилось до 30 минут по мере роста команды и кодовой базы.
Учитывая 10-кратный множитель цен на минуты запуска macOS, а также принятие GitHub Actions в других репозиториях, мы быстро начали превышать 50 000 бесплатных минут в месяц, включенных в наш корпоративный план. Кроме того, наши команды все больше разочаровывались в производительности раннеров, размещенных на GitHub, поэтому какое‑то время искали альтернативы.
Учитывая наш первоначальный опыт ручного обслуживания Buildkite Agent и прилагаемые усилия, самостоятельный хостинг не казался жизнеспособным вариантом.
Однако однажды я случайно наткнулся на Apple Virtualization Framework. Из любопытства я скачал пример кода и запустил его. К моему большому удивлению, код оказался очень простым и понятным. Вскоре стал очевиден потенциал создания и запуска виртуальных машин с помощью кода. Примерно в то же время мне сообщили, что у нас на складе есть несколько MacBook Pro M1, которые либо сломаны, либо находятся в слишком плохом состоянии, чтобы раздавать их сотрудникам. Самостоятельный хостинг нашего CI внезапно стал более реалистичным, и родилась идея Cilicon (CI + Silicon).
Представляем Cilicon
Концепция Cilicon сводится к простому циклу:
Duplicate Image (дублировать изображение)
Cilicon создает клон пакета (папки) вашей виртуальной машины для каждого запуска. Благодаря отличной функции клонирования в APFS это происходит очень быстро, даже с большими пакетами.
Provision Shared Folder (предоставить общую папку)
В зависимости от выбранного вами поставщика, Cilicon помещает файлы, необходимые вашей гостевой ОС, в папку Resources вашего пакета.
GitHub Actions Provisioner подготавливает образ с URL‑адресом загрузки исполнителя, токеном регистрации, именем и метками.
Process Provisioner запускает исполняемый файл по вашему выбору при подготовке и удалении пакета.
Вы также можете отказаться от использования поставщика, установив тип поставщика на none. Это может нормально работать с такими сервисами, как Buildkite, которые используют токены регистрации с неограниченным сроком действия.
Start Virtual Machine (запустить виртуальную машину)
Cilicon запускает виртуальную машину и автоматически монтирует папку пакета Resources в гостевой ОС.
Listen for Shutdown (слушать выключение)
Cilicon прослушивает выключение гостевой ОС и удаляет использованный образ перед запуском заново.
Для создания пакетов виртуальных машин Cilicon поставляется с собственным автономным приложением под названием “Cilicon Installer”. После создания всё, что осталось сделать, это запустить виртуальную машину в режиме редактора, установить все необходимые зависимости и добавить start.command из общей папки ресурсов в качестве Login Item (элемента входа).
Наш предыдущий опыт
После двухнедельного пробного периода в ноябре, в течение которого мы одновременно работали на собственном хостинге и на GitHub‑хостинге, мы почувствовали уверенность в том, что сможем переключиться. С тех пор, как мы используем наш парк исключительно из восьми M1 MacBook Pro и без единого сбоя, мы наслаждаемся работой в 3 раза быстрее. Большинство MacBook, которые мы использовали, считались непригодными для использования сотрудниками, поскольку у них были дефекты дисплея, клавиатуры или косметические дефекты. Хотя изначально мы планировали перейти на M1 Mac Mini после тестового периода, компьютеры MacBook проделали такую большую работу, что мы пока продолжим их использовать.
Возвращение к размещённым на Github раннерам
Самостоятельный хостинг всегда сопряжён с дополнительными рисками. Несмотря на то, что в наших офисных серверных комнатах есть резервное питание и сетевое резервирование, мы хотели добавить возможность легкого возврата к размещённым на GitHub раннерам в случае, если мы не сможем подключиться к своим.
Для этого мы добавили в наш рабочий процесс новое задание, которое проверяет, содержит ли PR метку с именем «Run on GitHub Hosted Runner», и соответственно выбирает runs‑on label (метку запуска) для последующего задания. Нежелание добавлять labeled trigger (помеченный триггер) в наш рабочий процесс означало, что нам необходимо было получать метки через GitHub API, а не извлекать их из предоставленных контекстных переменных, поскольку повторные запуски обеспечивают точный снимок данных из начального запуска. Поскольку раннеры, размещённые на Github, работают на x86, вы также можете включить arch раннера в свои ключи кэша, используя ${{ runner.arch }}.
jobs:
runner_type_job:
name: Runner Type Selection
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
outputs:
runner_type: ${{ steps.set_runner_type.outputs.runner_type }}
steps:
- id: set_runner_type
run: |
# Fetching fresh PR Labels using GH CLI, needed on Re-run Workflows.
GH_PR_URL="https://github.com/${{ github.repository }}/pull/${{ github.event.number }}"
GH_PR_LABELS=$((gh pr view $GH_PR_URL --json=labels --jq='.labels | map(.name) | @sh') | tr -d \')
echo "Labels applied: $GH_PR_LABELS"
RUN_ON_GH_HOSTED_RUNNER="Run on Github Hosted Runner"
if [[ ! " ${GH_PR_LABELS[*]} " =~ " ${RUN_ON_GH_HOSTED_RUNNER} " ]]; then
echo 'runner_type=["self-hosted", "macos-13", "ARM64", "xcode-14.1"]' >> $GITHUB_OUTPUT
else
echo 'runner_type="macos-12"' >> $GITHUB_OUTPUT
fi
test:
name: Unit Tests
needs: runner_type_job
runs-on: ${{ fromJSON(needs.runner_type_job.outputs.runner_type) }}
Обслуживание
Чтобы свести усилия по обслуживанию к минимуму, Cilicon предлагает несколько хитростей в рукаве.
Поскольку Cilicon не поддерживает подготовку образов по сети, в настоящее время рекомендуется передавать образ через SSD. Таким образом, чтобы исключить любое взаимодействие с ОС (тем более, что некоторые из используемых нами устройств имеют повреждённые дисплеи), Cilicon можно настроить на сканирование подключенного тома с определённым именем и автоматическое копирование VM.bundle. Начало и конец процесса копирования сопровождаются системными звуками, что избавляет от необходимости открывать крышку или взаимодействовать с клавиатурой.
Также не помешает время от времени перезапускать устройства, поэтому Cilicon можно настроить на перезапуск хост‑машины после заданного количества запусков.
Заключение
Мы надеемся, что больше компаний и частных лиц будут использовать Cilicon для снижения затрат и ускорения своего CI. В то время как оборудование для самостоятельного хостинга может не подойти для многих, Hetzner предлагает очень доступный хостинг M1 Mac Mini примерно за 70 евро в месяц, и подтверждено, что он работает с Cilicon.
Вклад в проект также приветствуется!
Комментарии (2)
tokarev
00.00.0000 00:00во-первых, спасибо за то что выложили в Open Source ????
только узнал про Cilicon на прошлой неделе, а сегодня уже статья на Хабре, да ещё от авторов - это прекрасно! Но интересует знали ли вы про Tart от Cirrus CI, когда начинали свою разработку? или может сравнивали функционал, но решили что он вам не подойдет?
MeGaPk
Я правильно понял что в данный момент Cilicon запускает только 1 раннер на одном компьютере? Есть ли в планах сделать так, что бы запускалось хотя бы 2 раннера на одном компьютере?