Трендовые github проекты в нашем телеграм канале. Подпишись → Собственный runner GitHub Actions без лишних привилегий
CI быстро становится заметной статьёй расходов, когда изменения появляются сериями и каждая итерация запускает проверки. Особенно это заметно в проектах с агентной разработкой: цикл изменение → commit → CI → исправление повторяется значительно чаще, чем при ручной работе. После исчерпания включённой квоты GitHub Actions дополнительные минуты GitHub-hosted Linux runner тарифицируются отдельно.
Для стабильной нагрузки часть задач можно перенести на собственный GitHub Actions runner. GitHub остаётся точкой управления workflow: принимает push, ставит jobs в очередь, показывает результаты и хранит историю запусков. Вычисления при этом выполняет выделенная VM. Такая схема подходит только после оценки эксплуатационных и security-рисков: self-hosted runner исполняет код CI на вашей инфраструктуре.
Когда собственная VM оправдана
Экономику удобно считать от нагрузки. При цене GitHub-hosted Linux runner $0,006 за минуту дополнительные 1000 минут дают около $6, 3000 — $18, а 5000 — $30. Небольшая облачная VM может иметь фиксированную месячную стоимость порядка 6–10 евро. Точка окупаемости зависит от региона, конфигурации и стоимости обслуживания, однако при нескольких тысячах платных минут в месяц её стоит посчитать.
Деньги остаются только одним из критериев. Собственный runner требует регулярных обновлений ОС, контроля диска, диагностики, мониторинга, работы с инцидентами и понятной стратегии восстановления. GitHub-hosted runner каждый job запускает в чистом управляемом окружении; persistent runner сохраняет своё состояние между задачами.
Схема полезна при трёх условиях:
- CI запускается часто и нагрузка достаточно предсказуема;
- одного или нескольких постоянно работающих runner достаточно по concurrency;
- команда готова обслуживать отдельный execution environment.
Редкие проверки, выполнение недоверенного кода и потребность в большой эластичной параллельности обычно оставляют GitHub-hosted среду более практичным выбором. Для публичных репозиториев правила запуска workflow из pull request требуют особенно внимательной настройки.
Изоляция начинается с отдельной машины
Runner стоит размещать на выделенной VM без пользовательских данных, production-сервисов и ценных credentials. Базовому хосту нужны Linux, Docker, Git, исходящий HTTPS, свободное место для checkout и временных файлов, а также запас RAM под jobs.
Контейнеризация помогает явно описать границу исполнения. У контейнера runner должны быть отдельный непривилегированный UID/GID, отключённый privileged mode, no-new-privileges и сброшенные Linux capabilities. Контейнеру не требуются host network, опубликованные порты, bind mount репозитория и Docker socket. Последний особенно опасен: доступ к /var/run/docker.sock фактически даёт job контроль над Docker host.
После запуска настройки следует проверять по фактической конфигурации контейнера, а не только по Compose-файлу:
docker inspect <runner-container> \
--format '{{json .HostConfig.Privileged}} {{json .HostConfig.Binds}} {{json .HostConfig.PortBindings}} {{json .HostConfig.CapDrop}} {{json .Config.User}}'
В выводе ожидаются false для privileged mode, отсутствие host binds и published ports, список dropped capabilities и отдельный пользователь. Это простая проверка того, что заявленная изоляция действительно действует во время работы.
Регистрация и маршрутизация jobs
В интерфейсе GitHub runner регистрируется на уровне репозитория через Settings → Actions → Runners. Registration token короткоживущий: его передают контейнеру только на время регистрации, например как Docker Compose secret. Постоянное хранение токена в репозитории или .env создаёт лишний риск.
Стандартные labels self-hosted, linux и x64 стоит дополнить label конкретного назначения, например playbook-ci. Тогда workflow явно выбирает подходящую машину:
runs-on: [self-hosted, linux, x64, playbook-ci]
Селектор runs-on: self-hosted со временем становится слишком широким. Когда runner несколько, job может попасть на машину с другим набором инструментов или уровнем доверия.
Persistent workspace требует уборки
GitHub-hosted job стартует на свежей VM. На постоянном runner файл, кэш или артефакт от предыдущей задачи способны остаться в рабочем каталоге. Следующая job может случайно использовать это состояние и пройти только из-за прошлого запуска. Результат формально зелёный, но уже не воспроизводимый.
Очистка должна стать частью контракта runner. Для checkout полезны git reset --hard и git clean -ffdx; временные артефакты валидации тоже нужно удалять. Заключительный шаг workflow выполняется при любом результате:
- name: Cleanup
if: always()
run: |
git reset --hard
git clean -ffdx
Конкретный набор путей зависит от проекта. Важно проверить очистку после успешного job, после ошибки и после отмены запуска.
Перед переносом production workflow
Статус Online подтверждает соединение с GitHub, но не доказывает готовность runner к CI. Безопаснее начать с отдельного synthetic smoke workflow и проверить полный жизненный цикл:
- Checkout конкретного commit SHA и запуск валидации.
- Повторный запуск на том же SHA.
- Очистку workspace.
- Перезапуск контейнера runner и успешное восстановление.
- Отмену или timeout job, затем повторный запуск.
Ключевая проверка миграции — эквивалентность окружений: один и тот же точный SHA должен успешно пройти на ubuntu-latest и на self-hosted runner. Тогда расхождение результатов указывает на execution environment, а не на изменения в коде между двумя commit.
Rollback следует подготовить и протестировать до cutover. В workflow должна оставаться понятная возможность вернуть job на ubuntu-latest. При сбое VM это позволяет восстановить CI сразу, а диагностику инфраструктуры проводить без давления сломанного pipeline.
Переносите только место исполнения
После smoke-проверок можно заменять runs-on: ubuntu-latest на набор labels self-hosted runner. В эту же поставку лучше не включать изменения триггеров, permissions, concurrency, timeout, команд валидации, required checks и branch protection. Узкий scope упрощает поиск причины, если job начинает вести себя иначе.
Self-hosted runner превращает CI из полностью управляемой услуги в часть вашей инфраструктуры. Выигрыш появляется при регулярной нагрузке и дисциплине эксплуатации. Выделенная VM, жёсткая контейнерная граница, очистка workspace, сравнение exact SHA и заранее проверенный rollback дают основу для такого переноса без потери доверия к результатам CI.