Bencher Runner Protocol
Бинарный файл runner и API-сервер обмениваются данными через одно
WebSocket-соединение. В этом справочнике описан этот протокол: передаваемые сообщения, жизненный цикл Job,
который они приводят в движение, а также то, как тайм-ауты и переподключение не дают Job застрять. Это подробное
дополнение к руководству по Self-Hosted Runner.
Вам не нужно знать ничего из этого, чтобы управлять Runner с помощью runner up;
это приведено для прозрачности и для всех, кто создаёт инструментарий вокруг API.
Соединение
Runner поддерживает одно WebSocket-соединение с API-сервером на протяжении всего своего жизненного цикла. Одно и то же соединение обрабатывает как назначение Job, так и выполнение Job, и остаётся открытым между множеством Job, избегая переподключения и рукопожатия для каждой из них.
- Эндпоинт:
/v0/runners/{runner}/channel - Аутентификация: ключ Runner отправляется как заголовок
Authorization: Bearer bencher_runner_<key>при установлении соединения. - Размер сообщения: каждое сообщение ограничено лимитом сервера
request_body_max_bytes(применяется как к максимальному размеру сообщения, так и к размеру кадра). Сообщение, превышающее этот лимит, например полезная нагрузкаcompleted, несущая большиеstdout,stderrили выходные файлы, отклоняется на уровне протокола WebSocket.
Каждое сообщение — это JSON-объект с полем event, которое определяет его тип.
Сообщения Runner
Сообщения, отправляемые от Runner к серверу.
| Event | Описание | Полезная нагрузка |
|---|---|---|
ready | Runner простаивает и запрашивает Job | Необязательные poll_timeout (1-900 с) и метаданные runner (os, arch, version) |
running | Настройка Job завершена, бенчмарк запускается | Нет |
heartbeat | Периодический сигнал жизнеспособности (примерно раз в секунду) | Нет |
completed | Бенчмарк успешно завершён | job (UUID Job) и results (вывод по каждой итерации) |
failed | Бенчмарк завершился с ошибкой | job (UUID Job), results и error |
canceled | Подтверждает отмену от сервера | job (UUID Job) |
Сообщения сервера
Сообщения, отправляемые от сервера к Runner.
| Event | Описание | Полезная нагрузка |
|---|---|---|
ack | Подтверждает полученное сообщение | Необязательный job (UUID Job) |
job | Назначает взятую Job для Runner | Взятая Job: её Spec, конфигурация Job и краткоживущий OCI pull-токен |
no_job | Тайм-аут опроса истёк, доступной Job нет | Нет |
cancel | Job отменена или истёк её тайм-аут; остановить выполнение | Нет |
update | Runner должен самообновиться до новой версии | version, url (URL загрузки) и checksum (SHA-256) |
OCI pull-токен в сообщении job генерируется при взятии Job и никогда не хранится.
Он ограничен единственным проектом, которому принадлежит Job, доступен только для загрузки и краткоживущий,
поэтому скомпрометированный Runner может загружать Image только для проекта той Job, которую он взял.
Поток соединения
После подключения Runner входит в цикл опроса в режиме простоя,
отправляя ready, пока сервер не назначит job
(или не вернёт no_job, когда истечёт тайм-аут опроса, или update, когда доступна новая версия).
Получив Job, Runner отправляет running,
передаёт сообщения heartbeat, пока выполняется бенчмарк,
и завершает терминальным сообщением completed или failed.
Сервер подтверждает каждое сообщение через ack,
а соединение остаётся открытым, поэтому Runner возвращается в цикл простоя для следующей Job.
Если Job отменена, сервер отвечает на heartbeat сообщением cancel.
Runner останавливает бенчмарк и отвечает canceled, что сервер подтверждает.
Жизненный цикл Job
Каждая Job проходит через фиксированный набор состояний по мере того, как её берут, выполняют и обрабатывают.
| Из | В | Триггер |
|---|---|---|
| pending | claimed | Runner берёт Job |
| pending | canceled | Пользователь отменяет Job |
| claimed | running | Runner отправляет running |
| claimed | failed | Runner отправляет failed, или истекает тайм-аут heartbeat |
| claimed | canceled | Пользователь отменяет Job |
| running | completed | Runner отправляет completed |
| running | failed | Runner отправляет failed, или истекает тайм-аут heartbeat |
| running | canceled | Пользователь отменяет Job, или превышен жёсткий тайм-аут Job |
| completed | processed | Сервер успешно обрабатывает результаты |
| failed | completed | Runner повторно отправляет completed, переопределяя сбой по тайм-ауту heartbeat |
processed и canceled являются терминальными.
completed и failed являются квази-терминальными:
completed переходит в processed, как только результаты разобраны,
а failed переходит в completed, если Runner повторно отправляет completed.
Каждый переход использует фильтр по статусу в своём обновлении базы данных,
поэтому Job, изменённая конкурентно, перечитывается, а не перезаписывается.
Тайм-ауты и восстановление
Три взаимодополняющих механизма гарантируют, что Job никогда не застрянет, даже если Runner упадёт или потеряет соединение.
Тайм-аут heartbeat
Пока соединение открыто, тайм-аут чтения обнаруживает Runner, который подключён, но молчит.
Только корректные сообщения протокола сбрасывают таймер;
некорректный JSON, кадры ping/pong и бинарные сообщения этого не делают.
По тайм-ауту Job, которая выполнялась дольше своего тайм-аута плюс льготный период, помечается как canceled,
а в остальных случаях помечается как failed (контакт с Runner был потерян).
Жёсткий тайм-аут Job
Сервер обеспечивает жёсткую максимальную продолжительность выполнения независимо от поведения Runner,
поэтому ошибочный или скомпрометированный Runner не может работать бесконечно, отправляя heartbeat.
Когда лимит (тайм-аут Job плюс льготный период) превышен,
Job помечается как canceled, а Runner получает сообщение cancel.
Восстановление после разрыва соединения
Если соединение обрывается, пока Job ещё в процессе,
сервер планирует проверку по истечении тайм-аута heartbeat.
Если Runner переподключился и возобновил heartbeat, Job продолжается;
в противном случае Job помечается как failed или canceled, если она превысила жёсткий тайм-аут.
При запуске сервер также восстанавливает осиротевшие Job в состоянии claimed,
переназначает тайм-ауты для Job в процессе
и повторно обрабатывает Job в состоянии completed, чьи результаты были сохранены, но ещё не разобраны.
Переподключение и доставка результатов
Переподключение поддерживается и идемпотентно.
Повторная отправка running для уже выполняющейся Job лишь обновляет её жизнеспособность,
а повторная отправка терминального сообщения completed, failed или canceled всегда безопасна.
Терминальные сообщения несут UUID Job и получают ack;
если соединение обрывается до прихода ack,
Runner сохраняет результат и повторно отправляет его при следующем соединении перед переходом в простой.
Фактический результат completed от Runner может даже переопределить статус failed, полученный по тайм-ауту heartbeat.
Без автоматического повтора
Job в состоянии failed не повторяется автоматически.
Неудачный бенчмарк — это сигнал, а не ошибка, которую нужно скрыть,
поэтому повторный запуск остаётся за вами.
Вывод Job
Когда Runner отправляет completed или failed,
полный вывод сохраняется в том же OCI-хранилище, что используется для контейнерных Image,
по пути {project}/output/v0/jobs/{job}.
Сохранённый вывод содержит массив results по каждой итерации и, в случае сбоя, строку error.
Каждая итерация записывает свой exit_code, stdout, stderr
и карту любых собранных выходных файлов с их содержимым.
После сохранения вывода сервер запускает адаптер harness бенчмарка по результатам,
чтобы разобрать Metric и Alert в Report, переводя Job в состояние processed.
Вывод возвращается при запросе Job через API GET /v0/projects/{project}/jobs/{job}.
Тот же лимит request_body_max_bytes, что ограничивает сообщения WebSocket,
ограничивает размер вывода, который может доставить Runner.