Розгортаємо власний CI/CD сервер на M1* з GitHub Actions
Усім привіт, я Артем Дорош, Android-розробник, який протягом останніх трьох років займає неіснуючу позицію — Mobile DevOps. За моїм субʼєктивним визначенням Mobile DevOps — це людина, що любить писати bash скрипти, чекати по пів години на «зелений ліхтарик» після правки в YAML-файлі, і її завжди пінгують, коли хтось з її команди бачить в логах щось неочікуване.
Практично ніхто не любить мати справу з CI: він повільний, любить відвалюватись, чи, наприклад, затримувати ваш мердж реквест на пів години після зміни однієї стрічки, що привело до перезапуску всіх тестів, його важко дебажити (коли потрібно).
У даній статті я поділюсь власним досвідом в створенні власної CI/CD інфраструктури на M1*, проблемами що ми намагились вирішити таким чином, порадами і результатами наших тестів.
Щоб шо?
Нещодавно в нашій мобільній команді зʼявилась проблема: ми витрачаємо занадто багато коштів за наш мобільний CI. Так історично склалось, що інші команди майже перейшли одразу на використання GitHub Actions(скорочено GHA) після їх запуску в 2018. Інші команди були задоволені цим рішенням, окрім мобільних, тому вони пішли своїм шляхом.
В 2019 році ми переїхали зі старого CI/CD сервісу на інший, що на той час пропонував краще залізо і мав більше функціоналу. На противагу цьому були ціна за час виконання, неоптимальність кешування, досить довге початкове налаштування для кожного запуску. Не допомогало ситуації і те що, що кодбаза стабільно росла, а потужності їх інфраструктури не встигали за технічним прогресом.
Так ми почали шукати альтернативи. Першою думкою було здійснити переїзд на GitHub Actions через нативність рішення. Ми вже використовували їх для невеликих задач — міряння розмірів реквестів, підрахунок та логування кількості legacy коду тощо. Цього часу, як і до цього, ми досить швидко уперлися в недостатню потужність «з коробки». Але, дякуючи архітектурі GHA, ми не обмежені лише потужнсотями GitHub.
План дій
Далі ми спробували вирішити проблему системним підходом, а саме — створили план дій. В команді ми визначили наступні кроки:
- Визначити наші потреби. Ми маємо створити список вимог до нашого рішення: версію системи, обов’язкове програмне забезпечення, необхідні потужності та їх об’єм.
- Вивчити архітектуру GitHub Actions. Як воно працює, які є обмеження платформи та яким чином ми можемо додати власні потужності для наших потреб?
- Вибрати «залізо» для ваших потужностей. Для тестів ми можемо використовувати MacBook Air/Pro, на якому ви цілком можливо і читаєте даний допис. Якщо ж ми намагаємось будувати саме потужності для команди, то треба вивчити пропозиції на ринку та які саме конфігурації ми маємо розглядати для купівлі або оренди.
- Конфігурація та підтримка потужностей. Вивчити, як можна швидко розгорнути та оновити конфігурацію вашого сервера без потреби внесення змін в основний сервер, та як забезпечити ізоляцію. Для цього нам в нагоді будуть віртуальні машини.
- Тестування та аналіз. Відповівши на попередні пункти, ми маємо отримати PoC (proof of concept), який ми можемо використати для тестування. Саме на цьому етапі ми можемо допрацьовувати конфігурацію та фіналізувати наші потреби. Отримавши необхідні дані, ми можемо оцінити доцільність та ефективність такого рішення.
Визначаємо потреби
Як і завжди, ми почали з діалогу і зустрічі з нашою iOS-командою. Разом ми вивчили їх працюючий сетап і налаштування пайплайнів, та спитали що саме вони б хотіли би бачити в новому рішені. Ми зупинились на тому що нам потрібні сервери на macOS Ventura, декілька додаткових homebrew пакунків, XCode 14.0+ і 12 GB оперативної пам’яті на пайплайн. Ми не були впевнені в кількості ядер, тож ми визначали необхідну кількість пізніше шляхом спроб і помилок.
Важливим пунктом був і об’єм наших потужностей. У нас хоч і невелика команда (4+), нам потрібно щоб у кожного розробника була можливість в будь-який момент запустити перевірку на CI в межах мердж реквесту. Так ми прийшли до простої формули, що визначає необхідну кількість одночасних задач: (1.5~2) x розмір команди
.
Ваші потреби можуть варіюватись, тож обов’язково проконсультуйтесь з вашою командою. Спробуйте дізнатись якнайбільше інформації та формалізувати її десь «на папері».
Про GitHub Actions Runner
GHA використовують ранери(runners), щоб виконувати ваші команди (steps) в межах задачі (job). Runner — це програма-агент, що банально очікує задачу, виконує її і відправляє логи на сервер. Цю програму можна запустити будь-де, хоч на мікрохвильовці. Один ранер може виконувати одну задачу за один раз. Є три типи ранерів, що ви можете використовувати в вашому проєкті:
- GitHub-hosted — безкоштовні для open source, платні для закритих проєктів. Вони недостатньо потужні: так, macOS ранер має лише 3 віртуальні процесори та 7 GB оперативної памʼяті;
- Self-hosted — ви можете запустити ваш ранер на вашому залізі. Власне платите ви за інтернет, електрику та залізо/ оренду заліза;
- App-hosted — деякі сервіси пропнують вам кращі ранери за свою ціну. Зазвичай для цього ви додаєте їх додаток до проєкту і оформлюєте підписку.
У кожного з цих варіантів є свої переваги і недоліки. Ми досить швидко відмовились від ідеї використання GitHub-hosted ранерів, так як вони не відповідають нашим вимогам потужності та ціни. На час написання статті, 1 хвилина виконання на macOS коштує $0.08. Для порівняння, 1 хвилина на Linux ранері коштує $0.008.
Це вже не кажучи про те, що їхні ранери поки що не підтримують Apple Silicon, а відповідний тікет ще не отримав пріоритету чи дедлайну в роадмапі. Через це ми майже одразу вирішили експерементувати з self-hosted опцією.
Вибираємо залізо
Тут у нас не так багато опцій. На момент написання, Apple може нам запропонувати 3 десктопи на Apple Silicon:
- Mac Mini — M1 (8 cores, 4 performance cores, up to 16 GB RAM);
- iMac — те саме що Mac Mini, але навіщо?!
- Mac Studio — M1 Max (10 cores, 8 performance cores, up to 64 GB RAM), M1 Ultra (20 cores, 16 performance cores, up to 128 GB RAM).
Один Mac Mini буде хостити не більше ніж один ранер, якщо ви хочете оптимальний та не перевантажений сервер. Не варто економити на памʼяті, тож беріть одразу 16 GB RAM. 256 GB SSD також буде цілком достатньо.
Mac Studio своєю чергою має вдвічі або ж в чотири рази більше потужних ядер. Тут все просто: діліть кількість performance cores на 4 і ви отримаєте оптимальну кількість ранерів. Так, M1 Max з 32 GB RAM буде спокійно хостити 2 ранери, а M1 Ultra 64 GB RAM — 4. Можна і більше, але тоді ви будете втрачати в потужності. Також, резервуйте як мінімум 100 GB для кожного ранеру. Більше SSD — краще, хоча і 1 TB буде за очі.
Також рекомендую вам орендувати ці машини десь в хмарі недалеко від вашого регіону. Так вам не доведеться піклуватись про окремий старлінк та генератор для вашого CI на випадок постійних аварійних відключень електрохарчування. Невеличкий перелік місць, де ви можете їх орендувати:
Авжеж це не повний список. Провайдерів багато, тому знайти щось підходяще для вас не стане великою проблемою.
Підготовка заліза
І от ви отримали доступ до вашого заліза. Далі нам треба підготувати його як хост для нашого майбутнього CI серверу. Зверніть увагу, що деякі пункти будуть опціональними в залежності від початкового стану ОС.
Для початку, встановлємо розширення Xcode та Homebrew:
xcode-select --install /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Далі потрібно увімкнути автологін в систему після включення. Для цього нам знадобиться утиліта kcpassword:
git clone https://github.com/xfreebird/kcpassword.git && cd kcpassword ./enable_autologin <user> <password>
Після цього нам треба вимкнути режим сну та автоблокування. На цьому етапі, якщо на девайсі увімкнутий MDM (Mobile Device Management), вам потрібно буде звернутись до адміністратора вашого
# Вимикаємо screensaver під час логіну sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0 # Вимикаємо режим сну sudo systemsetup -setdisplaysleep Off sudo systemsetup -setsleep Off sudo systemsetup -setcomputersleep Off
Ще в декстопних маках є можливість увімкнути перезапуск системи на випадок відключення електроенергії чи помилки системи.
sudo systemsetup -setrestartpowerfailure On sudo systemsetup -setrestartfreeze On
В останню чергу, вимикаємо Spotlight в симтемі. Це дозволить уникнути пошукової індексації, що буде навантажувати диск та процесор.
sudo mdutil -a -i off
Цього має буде достатньо для базового хосту. Також, увімкніть VNC та SSH в системних налаштуваннях. Це дозволить вам зекономити час.
Створюємо віртуальний ранер
Як раніше згадувалось, GHA можна запустити на будь-чому. Нічого не заважає вам просто встановити GHA ранер на вже підготовлену систему, але це може спричинити проблеми. Ваш CI може бути завʼязаний на якусь версію системи або Xcode. Банальне оновлення системи може викликати головний біль як у вас, так і у вашої команди. Не думаю що вам сподобається фіксити homebrew чи Xcode після купи приватних повідомлень від вашої команди.
Саме через це ми будемо використовувати віртуалізацію. Це допоможе нам з декількома проблемами:
- Ізоляція файлової системи — все потрібне для виконання буде встановлене у віртуальній машині.
- Ізоляція ресурсів — ми можемо виділити визначені ресурси на один ранер. Це дозволить запустити декілька ранерів на одному хості з окремо виділеними ядрами та памʼяттю.
- Простота міграції — для оновлення ранерів не знадобиться оновлювати хост. Потрібно лише запустити нову віртуальну машину і видалити стару.
З macOS 11 Apple додали Virtualization.Framework для роботи з віртуальними машинами на Apple Silicon. За допомогою нього можна створювати додатки, що можуть створювати та запускати віртуальні машини.
Його використовує Tart та Anka 3. Anka має свій реєстр віртуальних машин та контролер для управління нодами, але вона платна і потребує ліцензії. Зате Tart повністю безкоштовний, open source і підтримує всі OCІ. Єдиниий великий мінус — він не має утиліт для кластеризації, але вони вже думають над створенням схожого рішення.
Tart можна встановити або оновити однією командою:
brew install cirruslabs/cli/tart # Оновити вже встановлений tart можна через upgrade brew upgrade cirruslabs/cli/tart
Тепер нам потрібно визначитись з образом для нашого ранера. Для цього нам потрібно визначитись з версією Xcode та системи. Це може бути або macOS Monterey чи Ventura та Xcode 13 і новіше. Візьміть до уваги, що якісь пакунки та симулятори будуть відсутні «з коробки». Якщо вам потрібно створити власний образ, це можна досить просто зробити за допомогою Packer та спеціального плагіну для tart. CirrusCi також тримає офіційні шаблони для образів в Open Source, і ви можете використати їх як відправну точку для ваших образів. Далі його можна опублікувати в будь-який OCІ реєстр, як то GitHub Packages.
Обравши образ, час зробити віртуальну машину на його основі. Для цього достатньо однієї команди. Tart завантажить образ та створить віртуальну машину з відповідним імʼям.
tart clone ghcr.io/cirruslabs/macos-ventura-xcode:14.1 ventura-runner-1
Це займе трохи часу, так як образи важать приблизно 40 GB. Після цього ви можете корегувати параметри віртуальної машини. Для прикладу, ми виділимо нашому ventura-runner-1
4 vCPU та 12 GB RAM:
tart set ventura-runner-1 --cpu 4 --memory 12228 # Перевірити зміни можна за допомогою tart get tart get ventura-runner-1
Після цього можна запустити нашу віртуальну машину:
tart run ventura-runner-1 # Альтернативно, можна запустити машину без UI tart run --no-graphics ventura-runner-1
Якщо ви запустите віртуальну машину з графічним вікном, то вона дисить швидко запуститься і ви зможете інтерактивно з нею взаємодіяти:
Незалежно від того? в якому режимі було запущено віртуальну машину, ви зможете приєднатись до неї по ssh. За замовчуванням, віртуальна машина буде мати користувача admin з паролем admin. Прямо як ваш роутер (серйозно, змініть пароль роутера).
ssh admin@$(tart ip ventura-runner-1)
Отримавши прямий доступ до віртуальної машини, маємо все необхідне для запуску вашого CI/CD агенту. Єдине про що варто пам’ятати — ваші віртуальні машини поки що не стартують автоматично разом із запуском фізичної машини. Це нескладно зробити через launchctl
за доволі простою інструкцією.
Конфігурація GHA ранеру
Віртуальні машини на базі образів від Cirrus вже мають встановлений GHA runner agent, тому завантаження та встановлення можна пропустити. Але нам все ще потрібно авторизувати та налаштувати агент.
Для цього зайдіть в налаштування вашого GitHub репозиторію, знайдіть пункт «Runners» в налаштуваннях та натисніть «New self-hosted runner»:
На наступній сторінці нам знадобляться токен. Токен можна знайти в блоці «Configure». Його можна просто скопіювати, або ж можете скопіювати разом з командою конфігурації.
Далі, приступимо до конфігурації. Для цього заходимо в вікно термінала, в якому ви підʼєднані до віртуальної машини, переходимо до теки з агентом, і авторизуємо його. Після цього варто налаштувати агент як сервіс, що буде запускатись автоматично при старті віртуальної машини.
cd ~/actions-runner # Довідка по параметрам конфігурації ./config.sh --help # Авторизація ./config.sh --unattended \ --url <repo_url> \ --token <runner_token> \ --name "ventura-runner-1" \ --labels "xcode-14.1" # Реєструємо агент як сервіс та активуємо його ./svc.sh install && ./svc.sh start
Якщо попередні дії були виконані без помилок, то в секції «Runners» в налаштуваннях ви побачите ваш ранер в статусі онлайн.
Тестуємо наш CI
Створимо базовий workflow, що буде запускатись на нашому сервері. Для цього нам треба вказати GitHub, на чому запускати наші задачі через теги та runs-on
. Достатньо використати тег «self-hosted», що додається автоматично до кожного self-hosted ранера. Втім, якщо ваші ранери можуть мати різну конфігурацію, варто використати більш унікальний тег. Наприклад, наступний worflow клонує ваш репозиторій та перевіряє версію XCode, що встановлена у вашому ранері:
# .github/workflows/test.yaml name: 'test' on: pull_request: push: jobs: test: runs-on: [self-hosted, xcode-14.1] steps: - uses: actions/checkout@v3 - run: xcodebuild -version
Додайте та запуште цей файл у ваш проєкт. GitHub на основі тригерів, що ви вказали у workflow, запустить необхідні задачі та буде звітувати про них вживу. Ви зможете побачити прогрес виконання цього пайплайну у вкладці «Actions» вашого репозиторію:
Дізнатись більше про синтаксис та можливості GitHub Actions ви можете з офіційної документації.
Про недоліки та результати
Головним недоліком даного рішення є відсутність підтримки вкладеної віртуалізації. На практиці це значить, що Android-емулятор чи часто потрібний Docker не будуть працювати всередині вашої віртуальної машини. Docker також використовується в деяких GitHub Action-ами, створеними спільнотою, що робить їх недоступними на ваших Apple Silicon ранерах. Це обмеження не лише Virtualization.Framework
, але і самого чипу M1. M2 вже має підтримувати вкладену віртуалізацію, хоча сам фреймворк ще не було оновлено для цього.
Другим недоліком буде саме керування цими віртуальними машинами. Відсутність кластеризації переносить відповідальність за підтримку та масштабування на вас. На словах це просто, але якщо ранерів стає багато, то це може займати суттєво більше часу, ніж здається. Спробуйте створити скрипти автоматизації для таких дій як перестворення і конфігурація віртуальних машин.
Авжеж, є і переваги в даному рішенні. Так, в наших тестах подібна конфігурація показала суттєве зменшення затраченого часу на виконання workflow — з 20 хвилин до 8 хвилин на скриншот та Unit-тести нашого iOS-проєкту. Інші задачі показували приріст в швидкості в межах
Кінцеві результати будуть залежати від конфігурації віртуальних машин та саме від ваших пайплайнів. Не забувайте проводити власні тести та валідацію, перед тим як пропонувати робити щось cхоже у ваших проєктах. Може скластися так, що ціна підтримки стане вищою за зекономлені гроші чи хвилини. Якщо у вас в компанії є DevOps, то обовʼязково проконсультуйтеся з ними.
Висновки
Створити власний CI на Apple Silicon може бути непоганою ідеєю, якщо ви хочете отримати найефективніше рішення за власну ціну. В цьому дописі ви дізнались про вибір заліза, створення віртуальних машин за допомогою утиліти tart, та про налаштування агента для запуску ваших задач з GitHub Actions.
Дякую за перегляд! Сподіваюсь, ви дізналися щось нове з цього допису, а не просто витратили час. Буду радий відповісти на ваші питання в коментарях.
26 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів