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, необязательный channel обновления и необязательная checksum бинарного файла) |
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, которую он взял.
На канале обновлений stable сообщение update отправляется, когда версия Runner отличается от версии сервера.
На канале обновлений canary поле version равно canary,
а сообщение update отправляется, когда сообщённая самим Runner контрольная сумма бинарного файла
отличается от опубликованной скользящей canary-сборки.
Поток соединения
После подключения 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.