Git-сервис нужен всей команде постоянно: кто-то открывает merge request, кто-то клонирует репозиторий, кто-то смотрит историю файла. А сборка нового образа способна на несколько минут занять всю машину: процессор уходит в компиляцию, диск в слои и кэши, память в тесты. Пока это происходит на том же сервере, где живёт Forgejo, страница с кодом начинает отвечать так, будто ей тоже нужно собраться с мыслями.
Отсюда вопрос, ради которого написана эта статья: почему Forgejo и исполнитель Actions стоит рассматривать как разные нагрузки, и что из этого следует для устройства VPS, доступов и восстановления после сбоя. Установку самого Forgejo мы здесь не повторяем: она разобрана в статье Как установить и настроить Forgejo на VPS. Дальше предполагается, что сервис уже работает, а вы решаете, где будут выполняться сборки.
Читать репозиторий и выполнять его код — разные занятия
Чтобы увидеть разницу, сравните последствия двух действий для сервера.
Когда разработчик делает git fetch, сервер читает объекты репозитория, упаковывает нужное и отдаёт по сети. Нагрузка предсказуема: она растёт с размером репозитория и числом одновременных клиентов, но сам сервер выполняет только свой, известный администратору код. Содержимое репозитория для него — данные.
Когда срабатывает workflow, содержимое репозитория перестаёт быть только данными. В файле описаны шаги, а шаги — это команды, скрипты, загрузка зависимостей из внешних реестров, запуск тестов, которые сами по себе программы. Нагрузка непредсказуема по форме: один запуск почти ничего не потребляет, другой сутки подряд держит все ядра. Хуже того, исполняется чужой для сервера код: тот, что написали коллеги, подтянули зависимости или принесли в форке.
Документация Forgejo говорит об этом прямо: runner выполняет удалённый код, и это создаёт риск для хоста и сети (Forgejo: Actions). Поэтому разговор о месте для runner — не только про производительность. Это разговор о том, что именно вы готовы подпустить к машине, на которой лежат все репозитории, пользователи и настройки сервиса.
| Свойство | Git-сервис Forgejo | Исполнитель Actions |
|---|---|---|
| Что делает с содержимым репозитория | Хранит и отдаёт как данные | Выполняет описанные шаги как код |
| Профиль потребления | Ровный, зависит от числа обращений | Всплесками, зависит от задания |
| Цена остановки | Команда не может работать с кодом | Очередь заданий ждёт, репозитории доступны |
| Что на машине ценного | Репозитории, учётные записи, база сервиса | В идеале только временные рабочие каталоги |
| Отношение к замене | Заменять с осторожностью, с резервной копией | Должен заменяться без сожалений |
Последняя строка таблицы — главная. Если сервер хранит ценное, он должен быть спокойным и скучным. Если сервер исполняет чужие шаги, он должен быть расходным. Сочетать эти роли на одной машине можно для проверки идеи, но для постоянной работы разделение даёт сразу два выигрыша: сборка не отнимает ресурсы у сервиса, а возможная неприятность во время сборки не получает прямого соседства с данными.
Как runner связан с Forgejo
По документации, runner устанавливается и регистрируется отдельно от Forgejo, а задания, которые он исполняет, определяются его labels (Forgejo: Actions). Это важная архитектурная деталь: Forgejo не запускает сборки у себя, он ведёт очередь и распределяет задания, а исполнитель приходит за работой со стороны.
Из этого следуют два практических вывода.
Первый: runner можно поставить на другой VPS. Ему нужно уметь достучаться до адреса вашего Forgejo и пройти регистрацию, а самому Forgejo не нужно заходить на машину исполнителя. Направление связи удобное с точки зрения сети: сервис с репозиториями не обязан знать, где находятся исполнители и сколько их.
Второй: labels — это способ договориться между workflow и исполнителем. Workflow просит определённый вид рабочего места, а runner с подходящим label это задание принимает. Придумайте для labels осмысленные имена по назначению, а не по номеру машины. Например, условно: один label для обычных проверок кода, другой для сборки образов, третий для тяжёлых тестов. Тогда замена машины не требует правки десятков workflow: новый исполнитель регистрируется с теми же labels и подхватывает ту же работу.
Документация также указывает, что несколько runner могут обслуживать один Forgejo, а один runner может подключаться к нескольким контекстам. Здесь пригодится осторожность. Возможность не означает, что так нужно делать: один исполнитель для нескольких контекстов — это общий рабочий стол для разных команд или проектов, а значит общий круг риска. Разные уровни доверия лучше разводить по разным исполнителям, даже если технически это не обязательно.
Полномочия: что нужно runner, а что нет
Хорошее правило для исполнителя звучит так: он должен знать ровно столько, сколько нужно для получения заданий и выполнения конкретных шагов, и не больше. Подумайте, какими данными вы его снабжаете.
- Доступ к Forgejo. Только в той мере, которая нужна для регистрации и получения заданий. Административная учётная запись для этого не нужна, и использовать её в процессе работы runner не стоит.
- Секреты для workflow. Передавайте конкретному проекту только то, что нужно его сборке, а не всё подряд на уровне машины. Секрет, лежащий в переменных окружения или файле на хосте runner, доступен любому шагу любого задания, которое там исполняется.
- Доступ в сеть. Сборке обычно нужен интернет для зависимостей, но внутренние адреса, панели управления и базы данных ей не нужны. Если исполнитель в той же частной сети, что и другие ваши сервисы, подумайте, нужно ли ему видеть их все.
- Ключи для публикации. Токен на запись в реестр образов — разумная вещь для задания публикации. Но если он нужен только части workflow, пусть он будет только у неё, а не у всей очереди.
Здесь же полезна простая мысль: любая секретная вещь на хосте runner должна быть заменима. Если вы не можете спокойно стереть машину, потому что на ней лежит единственный экземпляр чего-то важного, исполнитель перестал быть расходным.
Приватный репозиторий — ещё не доверенный workflow
Здесь легко ошибиться на уровне интуиции. Раз репозиторий приватный, значит, туда пишут только свои, и всё, что в нём запускается, можно считать безопасным. Но приватность описывает, кто видит код, а не что этот код делает при исполнении.
Представьте обычные пути, по которым в workflow попадает постороннее. Зависимость обновилась и принесла новый скрипт установки. Коллега скопировал пример шага из интернета. Автор изменения оказался сторонним подрядчиком с ограниченным доверием. Учётную запись сотрудника кто-то перехватил. Ни один из этих случаев не нарушает «приватность»: репозиторий по-прежнему закрыт, а шаг сборки всё равно выполняет то, что в нём написано.
Поэтому режимы runner, которые определяют, как именно исполняются задания, — это вопрос политики доверия, а не удобства. Документация Forgejo прямо оговаривает, что исполнение удалённого кода несёт риск для хоста и сети и что режимы стоит выбирать осознанно (Forgejo: Actions). Читайте раздел про режимы запуска в документации той версии, которую вы поставили, и не переносите по памяти решения из других систем CI.
Контейнер вокруг шага снижает неприятные последствия, но не превращает исполнителя в безопасное место. Граница между заданием и хостом зависит от настроек, от того, что вы в контейнер пробросили, и от того, какие права ему выданы. Контейнер с доступом к сокету управления контейнерами хоста — почти ключ от хоста, а не стена. Поэтому честная формулировка такая: контейнер — один из слоёв, его надёжность вы проверяете настройками, а не названием технологии.
Ещё одно практическое следствие: не обещайте себе, что любой workflow из GitHub Actions заработает «как есть». Синтаксис похож, и многое переносится, но совместимость конкретных действий нужно проверять на своём runner, а не считать гарантией. Для каждого заимствованного шага стоит открыть его исходники и решить, готовы ли вы пускать его на свою машину.
Одно задание: от назначения исполнителю до артефакта
Чтобы разобраться, где что живёт, проведём через систему одно условное задание. Пусть команда пушит изменение в приватный репозиторий, и в нём есть workflow сборки образа.
- Событие. Forgejo получает push и видит, что для этого события описан workflow. Он создаёт запуск и ставит задания в очередь. На этом шаге ничего не собирается: сервис лишь записал, что работа нужна.
- Подбор исполнителя. Задание просит определённый label. Свободный runner с таким label получает его. Если подходящих исполнителей нет, задание ждёт в очереди; репозиторий и интерфейс при этом работают как обычно.
- Подготовка рабочего места. Runner получает код нужной ревизии и создаёт для шагов временную среду. Здесь начинается исполнение того, что написано в workflow, и здесь проходит граница доверия, о которой говорилось выше.
- Шаги. Установка зависимостей, тесты, сборка. На это уходят процессор, память и диск именно исполнителя. Если машина перегружена, страдает только скорость сборки.
- Артефакт. Результат — образ, архив, отчёт — нужно унести из временного рабочего каталога туда, где он переживёт сам runner. Конкретное место зависит от задачи: хранилище артефактов на стороне Forgejo, реестр образов или внешнее хранилище. Принцип один: на runner результат не остаётся.
- Завершение. Задание отмечено выполненным или упавшим, временная среда убирается, исполнитель возвращается к очереди.
Обратите внимание на пятый пункт. Именно из-за него потеря runner перестаёт быть драмой: если всё, что имеет ценность, ушло в долговечное место, на машине исполнителя остаётся лишь то, что можно пересоздать. Если же результат лежит только в локальном каталоге, вы в действительности держите на runner постоянные данные и должны либо менять схему, либо резервировать его как сервис.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Сборка образов: отдельный повод для осторожности
В документации отдельно отмечено, что сборка Docker-образов требует дополнительных настроек (Forgejo: Actions). Почему это важно именно здесь, понятно из общей логики: для сборки образа шагу нужен доступ к механизму контейнеров, а доступ к механизму контейнеров — это очень много полномочий.
Подход, который стоит принять как правило: сначала решите, какой режим сборки образов вы допускаете, и только потом включайте его, сверяясь с документацией своей версии. Если вы собираете образы только из собственных, проверенных репозиториев, у вас один уровень риска. Если у вас есть внешние участники, форки или сторонние шаги, уровень совсем другой, и сборки образов для них лучше вынести на отдельный исполнитель с отдельным label и без лишних секретов.
Практичная схема: два вида исполнителей. Один принимает обычные проверки кода и не имеет ни доступа к механизму контейнеров хоста, ни ключей публикации. Другой принимает сборки образов и публикацию, находится в отдельной машине и обслуживает только доверенные репозитории. Разделение по labels делает эту схему видимой прямо в workflow: открыв файл, вы сразу понимаете, на каком уровне доверия он исполняется.
Что дают ресурсы VPS при таком разделении
Для небольшой команды типичная раскладка выглядит так: Forgejo остаётся на одном VPS, а исполнитель поселяется на втором. Размер второго определяется тем, что вы собираете, а не прихотью. Приведём условное рассуждение, а не измерение.
Тяжесть сборки обычно складывается из трёх компонентов. Процессор определяет, насколько быстро идёт компиляция и параллельные тесты. Оперативная память определяет, не упрётся ли линковка или тесты в нехватку. Диск определяет, сколько слоёв образов, кэшей зависимостей и временных файлов поместится, пока старое не вычистится. У диска есть неприятное свойство: он заполняется постепенно, и ошибка «нет места на устройстве» приходит в самый неудобный момент.
Среди тарифов MAATRIX есть ступени от Ba с 1 vCPU, 2 ГБ памяти и 40 ГБ NVMe до Djed с 16 vCPU, 32 ГБ и 320 ГБ; между ними Uab, Heka и Maat. Эти цифры — описание ресурсов, а не обещание, что на конкретном тарифе сборка вашего проекта уложится в определённое время. Верный путь такой: начать с умеренного варианта, понаблюдать за нагрузкой на реальных сборках и при необходимости перейти на тариф выше. Поскольку исполнитель расходный, перенос на другой размер — обычная замена рабочего места, а не операция на сердце.
Root-доступ на VPS удобен для этого сценария: вы сами решаете, какие пакеты и какой механизм контейнеров ставить, как ограничивать сеть и как чистить диск. Расположение машины тоже можно выбрать из предлагаемых локаций: если исполнитель и Forgejo находятся близко друг к другу, обмен заданиями и артефактами проходит заметно приятнее. Это общее соображение, а не измерение.
Потеря исполнителя — это замена стула, а не пожар
Теперь главный сценарий. Машина с runner перестала отвечать: диск повреждён, провайдер что-то пересоздал, вы сами по ошибке стёрли каталог. Что вы потеряли?
Если архитектура выстроена верно, то немногое. Репозитории, история, merge request, пользователи и настройки остались на сервере Forgejo, потому что там они и жили. Workflow остались в репозиториях, потому что они — обычные файлы в коде. Артефакты, которые вы успели отправить в долговечное хранилище, тоже на месте. Пропали: сама рабочая машина, её локальные кэши, возможно задание, которое выполнялось в тот момент, и регистрация исполнителя.
С такими потерями можно жить. Незавершённое задание запускается заново; кэш наполнится снова, хотя первая сборка будет медленнее; регистрация создаётся новая. Это очень похоже на ситуацию, когда в офисе сломалось рабочее место: сотруднику выдают другой компьютер, а его документы лежат на общем сервере.
Опасность в одном: незаметно превратить исполнителя в хранилище. Типичные пути такого превращения — «положу готовый релиз пока в каталог на runner», «оставлю там ключ, чтобы не вводить заново», «скрипт настройки лежит только на этой машине». Каждое такое решение по отдельности безобидно, а вместе они делают замену болезненной. Поэтому любую вещь на хосте исполнителя полезно проверять вопросом: если сегодня машина исчезнет, где эта вещь лежит ещё?
Отдельно о сервисе Forgejo: он, напротив, заслуживает регулярной резервной копии, и её вы организуете сами и проверяете восстановлением. Наличие автоматических копий на тарифе VPS не следует предполагать без подтверждения в условиях заказа.
Новый исполнитель без чужого багажа
Восстановление runner полезно описать как последовательность решений, а не набор команд, потому что точные команды зависят от выбранной вами версии и способа установки; их берите из документации Forgejo для своей версии (Forgejo: Actions).
1. Новая чистая машина. Берите свежий VPS, а не пытайтесь вылечить старый. Если причина сбоя неясна, лечение занимает больше времени, чем замена, а заодно можно унести на новое место то, что лучше оставить. Проверьте, что ресурсы подходят заданиям: вспомните, какой label на что рассчитан.
2. Базовая настройка хоста. Обновления, ограничение доступа по SSH, отдельный непривилегированный пользователь для runner. Исполнитель не должен работать под root, если без этого можно обойтись. Сетевые ограничения задайте сразу: машине нужен выход к вашему Forgejo и к внешним реестрам зависимостей, а доступ к другим вашим внутренним сервисам открывайте только осознанно.
3. Установка и регистрация как нового исполнителя. По документации, runner регистрируется отдельно, и labels определяют, какие задания он возьмёт. Это ключевой момент: вы не переносите старую регистрацию, а создаёте новую. Старый регистрационный токен, файлы состояния и всё, что с ними связано, на новую машину не копируйте. Задайте те же labels, что были у утраченного исполнителя, тогда workflow не заметят подмены. Старую запись о недоступном исполнителе в Forgejo уберите, чтобы она не вводила в заблуждение.
4. Секреты по минимуму. Не копируйте домашний каталог старой машины, историю команд, чужие ключи и конфигурацию «на всякий случай». Секреты для workflow должны храниться там, где их задаёт Forgejo для проекта, и выдаваться при запуске; при восстановлении вам не нужно возвращать их вручную на хост. Если на утраченной машине лежали ключи или токены, считайте их скомпрометированными по умолчанию и выпустите новые, а старые отзовите. Это не паранойя, а простая бухгалтерия: вы не знаете, что стало с диском.
5. Настройка режима запуска и сборки образов. Здесь повторите выбор, который вы сделали раньше, и проверьте его заново, а не восстанавливайте по памяти. Если исполнитель собирает образы, перечитайте требования документации к дополнительным настройкам.
6. Контрольный запуск. Запустите безобидный workflow с нужным label: он должен получить задание, выполнить шаги и вернуть результат. Затем запустите реальную сборку на небольшом проекте. Только после этого возвращайте исполнителя к важным репозиториям.
7. Записать, что получилось. Если процедура заняла больше времени, чем хотелось, запишите причины и превратите их в шаблон установки: короткий скрипт или заметку в репозитории инфраструктуры. Тогда следующая замена будет скучной, а скучная замена — лучшее качество, которое можно получить от расходной машины.
Мусор, который стоит оставить на старой машине
Есть соблазн перенести на новый runner всё, что накопилось: кэши, образы, рабочие каталоги, настройки. Не переносите. Кэши наполнятся сами; старые образы и слои собираются заново; рабочие каталоги всегда временные. Самые вредные вещи для переноса — локальные секреты и «временные» файлы с правами, выданными для разовой задачи, о которых все давно забыли.
Если в процессе вы обнаружили, что без какого-то файла со старой машины не обойтись, значит, найден тот самый скрытый постоянный элемент, о котором шла речь выше. Лучшее, что можно сделать — вынести его в понятное место: репозиторий инфраструктуры для не секретной настройки или хранилище секретов проекта для секретной. И тогда следующая замена его уже не потребует.
Репозиторий на месте, очередь оживает
Вернёмся к началу, но теперь на другом конце сбоя. Старая машина с исполнителем исчезла, а Forgejo всё это время работал: коллеги открывали репозитории, смотрели историю и оставляли комментарии к изменениям. Задание, которое ждало исполнителя, не пропало, а стоит в очереди.
На новом VPS заканчивается регистрация, в списке Forgejo появляется исполнитель с нужными labels. Он берёт первое задание из очереди, готовит временное рабочее место, и на экране сборки начинают бежать строки шагов. Сервис с кодом при этом не заметил ничего, кроме того, что статус задания сменился с ожидания на выполнение.