Обзор архитектуры проекта

Введение

Данный документ описывает архитектуру проекта автоматизированного конвейера сборки OCI-образов. Система принимает запросы на сборку, раскладывает их на последовательность стадий, запускает Kubernetes Job для каждой стадии и сохраняет состояние выполнения в TrackDB.

Архитектура построена вокруг асинхронного обмена событиями через RabbitMQ. Компоненты не вызывают друг друга напрямую для продвижения задания по конвейеру: они публикуют события стадий, а состояние задания фиксируется в TrackDB.

Схема архитектуры

архитектурная схема, загрузка

Основные компоненты

1. API

Описание: API служит внешним интерфейсом конвейера. Он принимает запросы на сборку, возвращает статус задания и логи, выполняет аутентификацию и авторизацию пользователей. Для каждого запроса на сборку OCI-образа формируется уникальный taskId.

Функции:

  • Выполняет keycloak-аутентификацию пользователей, создаёт и проверяет записи пользователей в таблицах SysDB.
  • Выпускает токен для авторизации пользователей.
  • Выполняет авторизацию пользователей, проверяя токен и сверяясь с таблицами SysDB.
  • Обрабатывает запросы на сборку, присваивая ULID в виде taskId.
  • Отправляет запросы на сборку в RabbitMQ для последующей обработки компонентом Saver.
  • Получает статусы сборки из TrackDB.
  • Получает логи из Loki через observability-контур.

2. API-LITE

Описание: упрощённая версия API без подключения к Keycloak. Она предназначена для сценариев, где используется один заранее настроенный пользователь и статический токен авторизации.

3. Компонент Saver

Описание: Saver принимает запросы от API из RabbitMQ, валидирует данные запроса, записывает стартовое состояние задания в TrackDB, сохраняет пользовательские credentials в Secret Manager и отправляет первую стадию BUILD в Distributor.

Функции:

  • Извлекает запросы на сборку из очереди RabbitMQ.
  • Создаёт первую запись о задании в TrackDB со стадией BUILD и состоянием NEW.
  • Записывает credentials пользователя для доступа к git и registry в Secret Manager.
  • Отправляет событие BUILD/NEW через RabbitMQ в компонент Distributor.

4. Компонент Stager

Описание: Stager получает от Tracker события завершения стадий и определяет следующую стадию конвейера. Список стадий задаётся конфигурацией и обычно включает BUILD, опциональные TEST и SIGN, затем PUSH и CLEANUP.

Функции:

  • Подписан на события RabbitMQ от Tracker.
  • Обрабатывает состояния DONE и FAIL.
  • При DONE выбирает следующую стадию с учётом настроек задания: TEST выполняется только при наличии test entrypoints, SIGN выполняется только для настроенных групп пользователей.
  • При FAIL отправляет задание на CLEANUP, если эта стадия включена в конфигурацию.
  • Записывает NEW для следующей стадии в TrackDB и отправляет её в Distributor.

5. Компонент Distributor

Описание: Distributor получает события стадий из RabbitMQ и разворачивает соответствующий Helm chart в Kubernetes. Для каждой стадии создаётся отдельный Helm release с именем, основанным на taskId и названии стадии.

Функции:

  • Извлекает события стадий из очереди RabbitMQ.
  • Получает параметры Helm chart для стадии из SysDB.
  • Подставляет параметры задания в values chart'а.
  • Для стадий, которым нужны credentials, получает их из Secret Manager.
  • Выполняет helm upgrade --install для запуска Kubernetes Job в namespace altflow.
  • После успешного запуска Job записывает состояние SUSPEND в TrackDB. Это состояние трактуется как "задание поставлено в Kubernetes и ожидает выполнения".

6. Компонент Tracker

Описание: Tracker отслеживает Kubernetes pod'ы, созданные стадийными Job'ами, и переводит состояние стадии в TrackDB. Для распределения ответственности между несколькими экземплярами Tracker используется hash ring по taskId.

Функции:

  • Отслеживает pod'ы стадий через Kubernetes LIST/WATCH.
  • Пишет RUNNING, когда рабочий контейнер стадии начал выполняться.
  • Пишет DONE или FAIL после завершения рабочего контейнера.
  • Отправляет финальные состояния стадий через RabbitMQ в Stager.
  • Выполняет периодическую сверку с TrackDB и помечает orphan-задачи как FAIL, если в TrackDB стадия числится RUNNING, но соответствующего pod'а больше нет.
  • Удаляет Helm release стадии после обработки финального состояния.

7. Компонент Builder

Описание: Builder обеспечивает выполнение стадий сборки и тестирования OCI-образов в Kubernetes.

Функции:

  • Подготавливает ноды Kubernetes для мультиархитектурной сборки через binfmt/qemu.
  • Предоставляет runtime-образ для Job'ов стадий BUILD и TEST.
  • Используется Helm job packages для запуска стадий BUILD, TEST, SIGN, PUSH и CLEANUP.

8. Observability

Описание: Observability-контур собирает метрики и логи компонентов системы и стадийных Job'ов.

Функции:

  • Vector собирает логи pod'ов и отправляет их в Loki.
  • Loki хранит логи стадий и отдаёт их API для пользовательских запросов.
  • Prometheus собирает метрики компонентов и Kubernetes-объектов.
  • kube-state-metrics отдаёт Prometheus состояние Kubernetes-ресурсов.
  • HAProxy публикует защищённые endpoint'ы для доступа к Loki и Prometheus.
  • Grafana может быть развёрнута опционально как пользовательский интерфейс для метрик и логов.

9. SysDB, Secret Manager и TrackDB

Описание:

  • SysDB: Хранит конфигурационные данные и специальные параметры для запросов на сборку, установленные администратором. Хранит информацию о пользователях для их авторизации.
  • Secret Manager: Хранит пользовательские credentials по каждому taskId, а также чувствительную информацию, необходимую для развёртывания и работы компонентов altflow.
  • TrackDB: ClickHouse-хранилище событий стадий. Содержит историю состояний заданий и обеспечивает доступ к этой информации для API, Stager, Tracker и Distributor.

Эти компоненты работают совместно, обеспечивая эффективный и автоматизированный процесс сборки OCI-образов в рамках конвейера.

Развертывание

Архитектура проекта предназначена для развёртывания в Kubernetes. Кластерные компоненты поставляются как Helm charts и Flux HelmRelease-манифесты. Стадийные Job'ы также запускаются через Helm charts, которые Distributor устанавливает для конкретного taskId и стадии.

Преимущества и недостатки

Преимущества

  • Масштабируемость: Компоненты обмениваются событиями через RabbitMQ, а независимые стадии выполняются отдельными Kubernetes Job'ами.
  • Микросервисная архитектура: Компоненты реализуют простые операции, что упрощает разработку и позволяет использовать различные языки программирования при соблюдении единого интерфейса взаимодействия.
  • Наблюдаемость: Логи и метрики собираются отдельным observability-контуром.

Недостатки

  • Сложность архитектуры: Для работы системы нужны Kubernetes, RabbitMQ, SysDB, TrackDB, Secret Manager и observability-контур.
  • Высокий порог входа для администрирования и отладки распределённых сценариев.

Заключение

Архитектура проекта автоматизированного конвейера сборки OCI-образов разработана с акцентом на масштабируемость и разделение ответственности. API принимает пользовательские запросы, Saver фиксирует старт задания, Distributor запускает стадии в Kubernetes, Tracker завершает стадии по фактическому состоянию pod'ов, а Stager продвигает задание по конвейеру.

Последовательность шагов сборки

Введение

Данная документация описывает последовательность шагов, необходимых для функционирования конвейера сборки OCI-образов. Конвейер автоматизирует сборку, тестирование, подпись, публикацию и очистку временных ресурсов.

Схема конвейера, загрузка

Описание шагов сборки

Действия по сборке OCI-образа

  1. Пользователь получает у API токен для выполнения запросов на сборку OCI-образов.
  2. Запрос на сборку поступает в API.
  3. API выполняет аутентификацию и авторизацию пользователя, формирует taskId и отправляет запрос в RabbitMQ.
  4. Компонент Saver извлекает запрос из очереди брокера.
  5. Saver добавляет в TrackDB первую запись по заданию: стадия BUILD, состояние NEW.
  6. Saver записывает credentials пользователя для доступа к git и registry в Secret Manager.
  7. При успешном выполнении шагов 5 и 6 Saver отправляет событие BUILD/NEW в Distributor через RabbitMQ.
  8. Первый доступный компонент Distributor извлекает запрос из очереди брокера.
  9. Distributor получает параметры Helm chart для стадии из SysDB, подставляет данные задания в values.yaml и при необходимости читает credentials из Secret Manager.
  10. Distributor запускает Kubernetes Job через helm upgrade --install и записывает в TrackDB состояние SUSPEND. Это состояние означает, что стадия поставлена в Kubernetes и ожидает выполнения.
  11. В pod стадии запускается рабочий контейнер стадии. Для сбора логов используется Vector: он отправляет логи в Loki в namespace altflow-observability.
  12. Tracker отслеживает pod'ы стадий через Kubernetes LIST/WATCH. Когда рабочий контейнер стартует, Tracker записывает RUNNING в TrackDB.
  13. После завершения рабочего контейнера Tracker записывает DONE или FAIL в TrackDB, публикует финальное состояние стадии в RabbitMQ для Stager и удаляет Helm release стадии.
  14. Stager получает финальное состояние стадии из RabbitMQ.
  15. Если стадия завершилась DONE, Stager выбирает следующую стадию по конфигурации. TEST выполняется только при наличии test entrypoints, SIGN выполняется только для настроенных групп пользователей, PUSH публикует готовый образ в пользовательский registry.
  16. Если стадия завершилась FAIL, Stager отправляет задачу на CLEANUP, если эта стадия включена в конфигурацию.
  17. Для следующей стадии Stager записывает NEW в TrackDB и отправляет событие в Distributor.
  18. После завершения PUSH Stager запускает финальную стадию CLEANUP, которая удаляет временные ресурсы и данные задания.

Действия по получению статуса сборки OCI-образа

  1. Запрос на получение статуса сборки поступает в API.
  2. API проверяет токен и выполняет авторизацию.
  3. API обращается к TrackDB, получает историю состояний задания и возвращает её пользователю.

Действия по получению логов сборки OCI-образа

  1. Запрос на получение логов сборки поступает в API.
  2. API проверяет токен и выполняет авторизацию.
  3. API обращается к Loki через observability endpoint и возвращает пользователю логи, отфильтрованные по taskId и, при необходимости, по eventStage.

Будущее развитие конвейера сборки OCI-образов

Будущая работа над конвейером сборки OCI-образов будет сосредоточена на повышении надёжности, безопасности и удобства эксплуатации системы.

  1. Расширение API: добавление новых endpoint'ов для управления заданиями, административной диагностики и потокового чтения логов.

  2. Документация и обучение: создание подробной документации и обучающих материалов для пользователей и администраторов.

  3. Повышение безопасности сборки образов в Kubernetes: ужесточение securityContext, ограничение прав сервисных аккаунтов, контроль привилегированных операций и изоляция стадийных Job'ов.

  4. Повышение доступности и отказоустойчивости: улучшение поведения компонентов при сбоях RabbitMQ, TrackDB, SysDB, Secret Manager и Kubernetes API.

  5. Улучшение контроля заданий в кластере через Distributor:

  • получение лимитов одновременно запущенных заданий из SysDB;
  • проверка количества активных стадий в TrackDB и Kubernetes;
  • отложенный запуск или отказ от запуска при превышении лимитов;
  • запись причины отказа или ожидания в TrackDB.
  1. Улучшение reconciler-механизмов: более точное восстановление после пропущенных Kubernetes events, защита от ложного завершения живых задач и расширенная диагностика orphan-сценариев.

  2. Развитие observability: расширение набора метрик, dashboards, alerts и проверок здоровья конвейера.

Эти направления должны развиваться постепенно, без нарушения основной функции конвейера: принять запрос, выполнить стадии сборки, опубликовать результат и сохранить полную историю состояния задания.

Интерфейс к API

Документ описывает внешний HTTP-интерфейс компонентов altflow-api и altflow-api-lite. Все прикладные запросы отправляются в JSON. Основной префикс маршрутов: /api-v1.

altflow-api использует авторизацию через внешний identity provider и выдаёт внутренний токен Altflow. altflow-api-lite не имеет маршрутов /api-v1/auth/* и проверяет заранее настроенный статический bearer-токен.

Авторизация

Получение токена Altflow

Доступно только в altflow-api.

Метод: POST URL: /api-v1/auth/token

Заголовок:

Authorization: Bearer <identity-provider-token>

Успешный ответ:

{
  "access_token": "<altflow-token>",
  "refresh_token": null
}

Swagger UI использует отдельный callback route:

GET /api-v1/auth/docs

Запуск сборки

Метод: POST URL: /api-v1/build

Заголовок:

Authorization: Bearer <altflow-token>

Минимальный запрос:

{
  "image_arch": "amd64",
  "git_url": "https://github.com/user/repo.git",
  "image_path": "registry.example.org/project/image",
  "registry_creds": "login:password",
  "buildDockerfilePath": "./Dockerfile",
  "buildContextPath": "."
}

Полный запрос с опциональными полями:

{
  "image_arch": ["amd64", "arm64"],
  "image_tag": ["latest", "1.0.0"],
  "git_url": "https://github.com/user/repo.git",
  "git_creds": "git-login:git-password",
  "buildGitBranch": "master",
  "image_path": "registry.example.org/project/image",
  "registry_creds": "registry-login:registry-password",
  "config_path": "/path/to/config.yaml",
  "buildDockerfilePath": "./Dockerfile",
  "buildContextPath": ".",
  "testEntrypoints": [
    ["/app/altflow-tests/run"],
    ["nginx", "-v"]
  ]
}

Поля:

ПолеТипОбязательноеОписание
image_pathstringдаПуть в формате registry/image. Устаревший алиас: registry_name. Если в image_path указан тег через :, он добавляется в список тегов.
image_archstring или array[string]даАрхитектуры. Допустимые значения: amd64, x86_64, arm64, aarch64, 386, i586, loongarch64, riscv64. Значения нормализуются к amd64, arm64, i586, loongarch64, riscv64.
image_tagstring или array[string]нетДо 10 тегов. Строка может содержать теги через запятую. По умолчанию latest.
git_urlstringдаHTTPS URL Git-репозитория.
git_credsstringнетУчётные данные Git в формате login:password.
buildGitBranchstringнетВетка Git.
registry_credsstringдаУчётные данные пользовательского registry в формате login:password.
config_pathstringнетПуть к пользовательскому конфигурационному файлу.
buildDockerfilePathstringдаПуть к Dockerfile внутри Git-репозитория.
buildContextPathstringнетКонтекст сборки. Если поле пустое, API берёт каталог, в котором находится Dockerfile, либо ..
testEntrypointsarray[array[string]]нетКоманды стадии TEST. Если поле пустое или отсутствует, TEST пропускается.

Успешный ответ:

{
  "task_id": "01JBV2S4PGNV87EGC5WKYA0RND",
  "status": "queued"
}

Типовые ошибки:

{"detail": "Invalid request data"}
{"detail": "Unauthorized: the token is not valid"}
{"detail": "Unauthorized: the token has expired"}

Получение статуса сборки

Метод: GET URL: /api-v1/builds/{task_id}

Заголовок:

Authorization: Bearer <altflow-token>

API читает stateEvents в TrackDB и возвращает историю состояний по taskId, отсортированную по insertTime DESC.

Успешный ответ:

[
  {
    "eventTime": 1735678830,
    "eventStage": "BUILD",
    "eventStageState": "DONE",
    "user": "alexander"
  },
  {
    "eventTime": 1735678820,
    "eventStage": "BUILD",
    "eventStageState": "RUNNING",
    "user": "alexander"
  }
]

Если API уже выдал task_id, но TrackDB ещё не получил событие:

{
  "status": "processing",
  "warn": "TrackDB has not received the task yet. Please, try later or contact administrator"
}

Получение логов сборки

Метод: POST URL: /api-v1/logs

Заголовок:

Authorization: Bearer <altflow-token>

Запрос:

{
  "taskId": "01K5BYCRDA52QBGNTWX429JPDS",
  "eventStage": "BUILD",
  "start": "1758188000",
  "end": "1758188779",
  "since": "1h",
  "limit": "10",
  "direction": "backward",
  "format": "json"
}

Поля:

ПолеТипОбязательноеОписание
taskIdstringдаULID задачи.
eventStagestringнетОдин из BUILD, TEST, SIGN, PUSH, CLEANUP. Если не указан, API запрашивает логи всех стадий задачи.
startstringнетНачало интервала для Loki.
endstringнетКонец интервала для Loki.
sincestringнетОтносительный интервал; используется если не указан start.
limitstringнетЛимит записей. По умолчанию 100.
directionstringнетbackward или forward. По умолчанию backward.
formatstringнетjson или text. По умолчанию json.

API формирует Loki query по labels taskid и опционально eventStage, затем возвращает либо исходный JSON result, либо текстовые строки логов.

Служебные маршруты

GET /alive
GET /ready
GET /metrics
GET /docs/

/metrics возвращает Prometheus metrics. Доступ к нему защищён отдельной проверкой заголовка AUTH_METRICS, если она включена в middleware/deployment.

Интерфейс между API и Saver

API передаёт принятый build request компоненту Saver через RabbitMQ. Формат сообщения: JSON, content-type application/json, delivery mode persistent.

RabbitMQ

API объявляет durable queue из конфигурации amqp_queue_name, привязывает её к amqp_exchange с пустым routing key и публикует сообщение в этот же exchange с пустым routing key.

Если включён DLX, очередь создаётся с аргументами:

x-dead-letter-exchange = <amqp_dlx>
x-overflow = reject-publish-dlx

Saver потребляет эту очередь с manual ack/nack. При успешной записи данных в TrackDB, Secret Manager и публикации события для Distributor сообщение ack'ается. При ошибке сообщение nack'ается без requeue и уходит в DLX.

Payload API -> Saver

API принимает внешний BuildRequest, валидирует его и преобразует в SaverRequest. image_path разбивается на registry_name и image_name; user, user_gid и ulid добавляются API автоматически.

{
  "image_tag": ["latest", "1.0.0"],
  "image_arch": ["amd64", "arm64"],
  "config_path": "/path/to/config.yaml",
  "registry_name": "registry.example.org",
  "image_name": "project/image",
  "registry_creds": "registry-login:registry-password",
  "git_url": "https://github.com/user/repo.git",
  "git_creds": "git-login:git-password",
  "build_git_branch": "master",
  "build_context_path": ".",
  "build_dockerfile_path": "./Dockerfile",
  "test_entrypoints": [
    ["/app/altflow-tests/run"],
    ["nginx", "-v"]
  ],
  "user_gid": 200,
  "user": "alexander",
  "ulid": "01JBV2S4PGNV87EGC5WKYA0RND"
}

Поля:

ПолеТипОписание
image_tagarray[string]Теги образа. Если пользователь не указал тег, API добавляет latest.
image_archarray[string]Нормализованные архитектуры: amd64, arm64, i586, loongarch64, riscv64.
config_pathstringОпциональный путь к конфигурации сборки.
registry_namestringRegistry из image_path.
image_namestringИмя образа из image_path.
registry_credsstringУчётные данные registry в формате login:password.
git_urlstringHTTPS URL Git-репозитория.
git_credsstringОпциональные учётные данные Git в формате login:password.
build_git_branchstringОпциональная ветка Git.
build_context_pathstringКонтекст сборки.
build_dockerfile_pathstringПуть к Dockerfile.
test_entrypointsarray[array[string]]Команды для стадии TEST; пустой массив означает пропуск стадии.
user_gidnumberGID для TrackDB events: в full API используется 200, в lite API 100.
userstringПользователь, определённый API. В lite режиме используется системное значение registry.altlinux.org.
ulidstringULID задачи; в TrackDB пишется как taskId.

Дальнейшие действия Saver

После получения сообщения Saver:

  1. Разбирает git_creds и registry_creds на пары username/password.
  2. Сохраняет credentials задачи в Secret Manager по task id.
  3. Пишет исходное событие BUILD/NEW в TrackDB таблицу imageEvents.
  4. Публикует stage-event для Distributor в очередь distributor_queue.

Stage-event для Distributor описан в интерфейсе Stager/Distributor; Saver использует тот же формат для первичного события BUILD/NEW.

Интерфейс событий TrackDB между Saver, Tracker и Stager

Этот интерфейс не является прямым RPC между Saver и Stager. Компоненты обмениваются состоянием задачи через TrackDB и RabbitMQ:

  1. Saver пишет первичное событие BUILD/NEW в imageEvents.
  2. Materialized views TrackDB проецируют данные в stateEvents и imagesByNames.
  3. Tracker пишет RUNNING, DONE или FAIL в imageEvents по факту состояния Kubernetes Job.
  4. Tracker публикует completion-событие в RabbitMQ queue stager_queue.
  5. Stager читает stager_queue, проверяет данные в TrackDB, выбирает следующую стадию и пишет NEW для неё в imageEvents.

TrackDB tables

Источник событий:

imageEvents

Рабочая проекция статусов:

stateEvents

Проекция расширенных данных образа:

imagesByNames

stateEvents содержит компактную историю состояний:

ПолеТип/значенияОписание
insertTimeDateTime64(9)Время вставки события.
eventTimeDateTimeЛогическое время события.
eventStageBUILD, TEST, SIGN, PUSH, CLEANUPСтадия pipeline.
eventStageStateSUSPEND, NEW, REJECT, RUNNING, DONE, FAILСостояние стадии.
taskIdFixedString(26)ULID задачи.
userstringПользователь задачи.
stageImageNamestringИмя образа на текущей стадии.
stageImageTagstringТег образа на текущей стадии.
errorTypeNumuintКод ошибки.
errorMessagestringТекст ошибки.
registryNamestringRegistry.
imageIdstringID образа, если известен.

imagesByNames дополнительно хранит build context, Dockerfile path, branch, tags, architectures, entrypoints и userGID. Stager использует эти данные для принятия решения по TEST и SIGN.

Событие Saver -> TrackDB

Saver вставляет в imageEvents событие BUILD/NEW. Значимые поля:

{
  "eventStage": "BUILD",
  "eventStageState": "NEW",
  "taskId": "01JBV2S4PGNV87EGC5WKYA0RND",
  "user": "alexander",
  "stageImageName": "project/image",
  "stageImageTag": "",
  "registryName": "registry.example.org",
  "buildGitURL": "https://github.com/user/repo.git",
  "buildGitBranch": "master",
  "buildConfigPath": "/path/to/config.yaml",
  "buildDockerfilePath": "./Dockerfile",
  "buildContextPath": ".",
  "repoTags": ["latest", "1.0.0"],
  "architecture": ["amd64", "arm64"],
  "entrypoints": [["/app/altflow-tests/run"]],
  "userGID": 200
}

Событие Tracker -> Stager

Tracker дополняет контекст задачи из TrackDB и публикует в RabbitMQ queue stager_queue событие завершения или запуска стадии.

Минимально значимые поля:

{
  "taskid": "01JBV2S4PGNV87EGC5WKYA0RND",
  "eventStage": "BUILD",
  "eventStageState": "DONE",
  "user": "alexander",
  "stageImageName": "project/image",
  "stageImageTag": "",
  "registryName": "registry.example.org",
  "imageId": "",
  "errorTypeNum": 0,
  "errorMessage": ""
}

Stager обрабатывает только DONE и FAIL как управляющие состояния. NEW, RUNNING и SUSPEND подтверждаются, но не запускают следующую стадию.

Логика Stager

Список стадий задаётся конфигурацией STAGES; допустимые значения:

BUILD, TEST, SIGN, PUSH, CLEANUP

Ограничения:

ПравилоОписание
Первая стадияДолжна быть BUILD.
Последняя стадияДолжна быть CLEANUP.
Обязательная стадияPUSH должна присутствовать.
ПорядокBUILD < PUSH < CLEANUP.
TEST gateСтадия запускается только если в задаче есть непустые entrypoints.
SIGN gateСтадия запускается только если userGID совпадает с SIGN_USER_GID.
FAILЛюбой FAIL до CLEANUP маршрутизируется в CLEANUP, если она есть в STAGES.

При выборе следующей стадии Stager:

  1. Проверяет, что для taskId + eventStage ещё нет последнего NEW.
  2. Пишет новое событие в imageEvents.
  3. Публикует stage-event в очередь Distributor.

Формат stage-event описан в интерфейсе Stager/Distributor.

Интерфейс между Stager и Distributor

Stager передаёт следующую стадию pipeline компоненту Distributor через RabbitMQ. Тот же формат использует Saver, когда отправляет первичное событие BUILD/NEW.

RabbitMQ

По умолчанию Stager публикует в:

queue: distributor_queue
exchange: distributor
routing_key: ""

Имена задаются переменными:

RABBITMQ_QUEUE_NAME
RABBITMQ_EXCHANGE
RABBITMQ_ROUTING_KEY

Сообщение отправляется как JSON. Distributor потребляет distributor_queue с manual ack/nack. При transient error сообщение requeue'ится до max_retries; после исчерпания попыток Distributor пишет FAIL в TrackDB и ack'ает сообщение.

Payload

Сообщение содержит не только taskid, eventStage и eventStageState, но и контекст, который нужен Distributor для рендера Helm chart values.

{
  "taskid": "01JBV2S4PGNV87EGC5WKYA0RND",
  "eventStage": "TEST",
  "eventStageState": "NEW",
  "user": "alexander",
  "stageImageName": "project/image",
  "stageImageTag": "",
  "registryName": "registry.example.org",
  "imageId": "",
  "errorTypeNum": 0,
  "errorMessage": "",
  "buildGitURL": "https://github.com/user/repo.git",
  "buildDockerfilePath": "./Dockerfile",
  "buildContextPath": ".",
  "buildGitBranch": "master",
  "repoTags": ["latest", "1.0.0"],
  "architecture": ["amd64", "arm64"],
  "entrypoints": [
    ["/app/altflow-tests/run"],
    ["nginx", "-v"]
  ]
}

Поля:

ПолеТипОписание
taskidstringULID задачи. В TrackDB это поле называется taskId.
eventStagestringСтадия: BUILD, TEST, SIGN, PUSH, CLEANUP.
eventStageStatestringДля запуска Distributor ожидает NEW.
userstringПользователь задачи.
stageImageNamestringИмя образа на текущей стадии.
stageImageTagstringТег образа на текущей стадии, если известен.
registryNamestringRegistry.
imageIdstringID образа, если известен.
errorTypeNumnumberКод ошибки, обычно 0 для NEW.
errorMessagestringСообщение об ошибке, обычно пустое для NEW.
buildGitURLstringGit URL исходников.
buildDockerfilePathstringПуть к Dockerfile.
buildContextPathstringКонтекст сборки.
buildGitBranchstringВетка Git.
repoTagsarray[string]Теги результирующего образа.
architecturearray[string]Архитектуры для job package.
entrypointsarray[array[string]]Команды для TEST; для остальных стадий обычно пустой массив.

Действия Distributor

После получения сообщения Distributor:

  1. По eventStage получает из SysDB URL, имя и версию Helm chart.
  2. Для BUILD и PUSH читает credentials задачи из Secret Manager.
  3. Выполняет helm registry login для registry с chart packages, если это включено в конфигурации.
  4. Выполняет helm pull oci://... --untar.
  5. Переписывает values.yaml job chart:
    • RabbitMQ callback labels/values: taskid, eventStage, eventStageState=RUNNING;
    • Secret Manager credentials для BUILD или PUSH;
    • TrackDB/build context: Git URL, Dockerfile path, context path, branch, registry, image name, tags, architectures;
    • entrypoints для стадии TEST.
  6. Запускает Kubernetes Job через:
helm upgrade --install <taskid-lowercase>-<stage-lowercase> ./<chart_name>
  1. Пишет в imageEvents состояние:
    • SUSPEND, если Helm release успешно создан;
    • FAIL, если chart не найден, pull/install завершился ошибкой или TEST вызван без entrypoints.

Фактическое завершение Kubernetes Job (RUNNING, DONE, FAIL) фиксирует Tracker.

Запуск AltFLOW

Необходимые компоненты

Для работоспособности AltFLOW необходимы компоненты, находящиеся вне кластера, такие как:

  • TrackDB
  • SysDB
  • Secret Manager (OpenBao)
  • Internal Registry (используется Harbor — развёртывается по официальной документации в upsteam-репозитории)

Для каждого из перечисленных компонентов необходимо предварительно выпустить tls-сертификат от имени сертифицирующего центра с сертификатом root-ca.cert.pem (используется ниже), а затем включить маршрутизацию через tls-соединения.

Подготовка кластера Kubernetes

Для развёртывания Altflow необходимо подготовить кластер следующим образом:

  1. В кластере должен быть установлен конроллер External Secret Operator (ESO).
  2. Cert-manager. Для выпуска сертификата должны быть загружены сертификат промежуточного CA и его ключ, например, так:
kubectl -n cert-manager create secret tls altlinux-intermediate-ca \
  --cert=test.chain.pem \
  --key=test.key.unencrypted.pem
  1. Должен быть установлен ClusterIssuer для altlinux-intermediate-ca.
  2. Должен быть загружен корневой сертификат выпускающего центра, выпустившего промежуточный CA, например (см. каталог certs):
kubectl -n altflow create secret generic altlinux-root-ca --from-file=ca.crt=root-ca.cert.pem
kubectl -n altflow-observability create secret generic altlinux-root-ca --from-file=ca.crt=root-ca.cert.pem
  1. В секреты k8s для namespaces altflow и altflow-observability должны быть загружены credentials для altflow-openbao, пример manifests/openbao-secrets.yaml.
  2. Установлен ceph-rbd.
  3. Ноды, на которых Altflow будет собирает образы, должны быть промаркированы:
kubectl label node <node-name> altflow.io/builder=true

При необходимости изолируйте рабочие ноды taint'ом altflow.io/builder=true:NoSchedule, чтобы обычные Pod’ы не занимали те ноды, которые вы специально выделите под сборку образов:

kubectl taint node <node-name> altflow.io/builder=true:NoSchedule

Развертывание AltFLOW в кластере

Чтобы запустить AltFLOW необходимо установить helm-чарты в кластере в следующем порядке:

  • altflow-bootstrap
  • altflow-rabbitmq
  • altflow-api
  • altflow-api-lite
  • altflow-saver
  • altflow-tracker
  • altflow-stager
  • altflow-distributor
  • altflow-builder
  • observability

Для установки каждого helm-чарта нужно заполнить необходимые параметры в values и CM файлах, а затем выполнить make install . в каталогах /altflow/helm/cluster_packages/altflow-*

Более подробно рассмотреть все необходимые параметры можно тут

TrackDB

TrackDB — ClickHouse-база событий Altflow. Она хранит историю стадий сборки OCI-образа и даёт компонентам быстрый способ читать текущее состояние задачи, историю состояний и расширенный контекст образа.

Актуальная схема описана в файле основного репозитория: scripts/trackdb/createEvents.sql.

Поток данных

Поток данных TrackDB

Все компоненты пишут события только во входную таблицу imageEvents. Она использует ENGINE = Null: сама таблица не хранит строки, а служит точкой fan-out для materialized views.

Поток выглядит так:

  1. Saver вставляет первичное событие BUILD/NEW.
  2. Distributor вставляет SUSPEND или FAIL после попытки создать Helm release.
  3. Tracker вставляет RUNNING, DONE или FAIL по состоянию Kubernetes pod.
  4. Stager вставляет NEW для следующей стадии или CLEANUP после FAIL.
  5. Materialized views раскладывают поток в таблицы чтения: stateEvents, stateList_State, imagesByNames.

Таблицы и представления

ОбъектТипНазначение
imageEventsNull tableВходной append-only stream событий. Все INSERT идут сюда.
stateEvents_mvmaterialized viewПроецирует компактные поля состояния из imageEvents в stateEvents.
stateEventsMergeTreeИстория состояний задач без полного OCI metadata. Используется API, Tracker, Stager.
stateList_State_mvmaterialized viewАгрегирует последние состояния задачи по стадии.
stateList_StateAggregatingMergeTreeХранит промежуточные aggregate states для stateList.
stateListviewУдобное представление с states, eventTimes, imageIds.
imagesByNames_mvmaterialized viewПроецирует расширенные данные образа из imageEvents в imagesByNames.
imagesByNamesMergeTreeДанные образов, build context, tags, architectures, entrypoints и userGID.

В текущей схеме нет stateEvents_live: старое live view было удалено.

Стадии и состояния

eventStage:

ЗначениеОписание
BUILDСборка OCI-образа.
TESTЗапуск пользовательских test entrypoints.
SIGNПодпись образа.
PUSHПубликация образа в пользовательский registry.
CLEANUPУдаление временных ресурсов и данных задачи.

eventStageState:

ЗначениеОписание
NEWСтадия поставлена в pipeline и ожидает Distributor.
SUSPENDDistributor создал Kubernetes Job; дальнейшее состояние пишет Tracker.
RUNNINGРабочий контейнер стадии начал выполняться.
DONEСтадия успешно завершилась.
FAILСтадия завершилась ошибкой или не была создана.
REJECTЗарезервированное состояние отказа в запуске.

imageEvents

imageEvents — единственная таблица для записи событий.

Ключевые поля:

ПолеТипОписание
insertTimeDateTime64(9) DEFAULT now64(9)Время вставки события в ClickHouse. Не передаётся в INSERT.
eventTimeDateTimeВремя события со стороны компонента.
eventStageenumСтадия pipeline.
eventStageStateenumСостояние стадии.
taskIdFixedString(26)ULID задачи.
userStringПользователь задачи.
stageImageNameStringИмя образа без registry.
stageImageTagString DEFAULT ''Тег образа на стадии, если известен.
errorTypeNumUInt32 DEFAULT 0Код ошибки.
errorMessageString DEFAULT ''Текст ошибки.
registryNameStringRegistry.
buildGitURLStringGit URL исходников.
buildGitBranchStringGit branch/tag.
buildConfigPathStringПуть к пользовательскому config file.
buildDockerfilePathStringПуть к Dockerfile.
buildContextPathStringКонтекст сборки.
imageIdString DEFAULT ''ID образа, если известен.
digestString DEFAULT ''Digest образа.
repoTagsArray(String)Теги результирующего образа.
repoDigestsArray(String)Digest-адреса образа.
userUIDUInt32 DEFAULT 0UID пользователя внутри образа.
userGIDUInt32GID пользователя Altflow; используется gate-логикой SIGN.
envArray(String)Environment образа.
entrypointsArray(Array(String))Команды для стадии TEST.
labelsMap(String, String)Labels образа.
architectureArray(String)Целевые архитектуры.
osString DEFAULT ''OS образа.
sizeUInt32 DEFAULT 0Размер образа.
layersArray(String)Layers образа.
annotationsMap(String, String)OCI annotations.
manifestTypeString DEFAULT ''Тип manifest.
historyArray(Map(String, String))OCI image history.

Пример начального события от Saver:

INSERT INTO imageEvents (
  eventTime, eventStage, eventStageState, taskId, user,
  stageImageName, registryName, buildGitURL, buildGitBranch,
  buildConfigPath, buildDockerfilePath, buildContextPath,
  repoTags, userGID, entrypoints, architecture
) VALUES (
  now(), 'BUILD', 'NEW', '01JBV2S4PGNV87EGC5WKYA0RND', 'alexander',
  'project/image', 'registry.example.org', 'https://github.com/user/repo.git', 'master',
  '', './Dockerfile', '.', ['latest'], 200, [['/app/altflow-tests/run']], ['amd64']
);

stateEvents

stateEvents хранит компактную историю состояний. Таблица создаётся как:

ENGINE = MergeTree()
ORDER BY (eventTime, taskId)
PRIMARY KEY (eventTime, taskId)

Поля:

insertTime, eventTime, eventStage, eventStageState, taskId, user,
stageImageName, stageImageTag, errorTypeNum, errorMessage, registryName, imageId

API читает статусы именно из stateEvents:

SELECT eventTime, eventStage, eventStageState, user
FROM stateEvents
WHERE taskId = '01JBV2S4PGNV87EGC5WKYA0RND'
ORDER BY insertTime DESC;

Stager может читать инкрементальный поток по insertTime:

SELECT *
FROM stateEvents
WHERE insertTime > '2026-05-02 11:22:13.339000000'
ORDER BY insertTime DESC
LIMIT 50;

Tracker использует stateEvents для проверки уже записанных terminal states и подавления дублей.

stateList_State и stateList

stateList_State хранит aggregate states:

taskId
eventStage
states_state
eventTimes_state
imageIds_state

stateList раскрывает их в обычные массивы:

SELECT
  taskId,
  eventStage,
  states,
  eventTimes,
  imageIds
FROM stateList
WHERE eventStage = 'BUILD'
HAVING states[-1] = 'RUNNING';

Это представление удобно для аналитических запросов по последнему известному состоянию стадии. Оно не заменяет stateEvents для точной истории событий.

imagesByNames

imagesByNames хранит расширенные данные образа. Таблица создаётся как MergeTree:

PRIMARY KEY (name, tag, taskId)
ORDER BY (name, tag, taskId, stage)

Важные поля:

ПолеИсточникОписание
namestageImageNameИмя образа.
tagstageImageTagТег образа на стадии.
taskIdtaskIdULID задачи.
stageeventStageСтадия.
stateeventStageStateСостояние.
buildGitURLbuildGitURLGit URL.
buildGitBranchbuildGitBranchGit branch/tag.
buildDockerfilePathbuildDockerfilePathDockerfile path.
buildContextPathbuildContextPathBuild context.
repoTagsrepoTagsТеги результирующего образа.
entrypointsentrypointsTEST commands.
architecturearchitectureTarget architectures.
userGIDuserGIDИспользуется Stager для SIGN gate.

Stager читает build details из imagesByNames для события BUILD/NEW:

SELECT buildGitURL, buildDockerfilePath, buildContextPath, entrypoints,
       buildGitBranch, repoTags, architecture, userGID
FROM imagesByNames
WHERE taskId = '01JBV2S4PGNV87EGC5WKYA0RND'
  AND stage = 'BUILD'
  AND state = 'NEW'
LIMIT 1;

Пользователи

Актуальный createUsers.sql создаёт двух пользователей:

ПользовательПрава
altflowSHOW TABLES, SELECT ON TrackDB.*, INSERT ON TrackDB.imageEvents
altflow_stagerSHOW TABLES, SELECT ON TrackDB.*, INSERT ON TrackDB.imageEvents

Оба пользователя пишут только в imageEvents; прямые вставки в stateEvents, stateList_State и imagesByNames не нужны.

Установка и инициализация TrackDB

TrackDB работает поверх ClickHouse. Актуальные SQL-файлы находятся в основном репозитории Altflow:

scripts/trackdb/createEvents.sql
scripts/trackdb/createUsers.sql

Установка ClickHouse

Пример для ALT-based окружения:

apt-get install clickhouse-client clickhouse-server
systemctl enable --now clickhouse-server

Проверка подключения:

clickhouse-client --query "SELECT version()"

Создание базы

clickhouse-client --query "CREATE DATABASE IF NOT EXISTS TrackDB"

Создание таблиц

Применить схему:

clickhouse-client --database=TrackDB --multiquery < scripts/trackdb/createEvents.sql

Скрипт пересоздаёт:

imageEvents
stateEvents
stateEvents_mv
stateList_State
stateList_State_mv
stateList
imagesByNames
imagesByNames_mv

imageEvents использует ENGINE = Null; это ожидаемое поведение. Данные хранятся в таблицах, которые наполняются materialized views.

Создание пользователей

Перед применением createUsers.sql замените placeholder-пароли:

BY 'your_password_here'
BY 'another_password_here'

Затем выполните:

clickhouse-client --multiquery < scripts/trackdb/createUsers.sql

Скрипт создаёт:

ПользовательНазначение
altflowAPI, Saver, Distributor, Tracker.
altflow_stagerStager.

Права:

GRANT SHOW TABLES, SELECT ON TrackDB.* TO altflow;
GRANT SHOW TABLES, SELECT ON TrackDB.* TO altflow_stager;
GRANT INSERT ON TrackDB.imageEvents TO altflow;
GRANT INSERT ON TrackDB.imageEvents TO altflow_stager;

Проверка схемы

clickhouse-client --database=TrackDB --query "SHOW TABLES"

Ожидаемые объекты:

imageEvents
imagesByNames
imagesByNames_mv
stateEvents
stateEvents_mv
stateList
stateList_State
stateList_State_mv

Проверка вставки события:

clickhouse-client --database=TrackDB --query "
INSERT INTO imageEvents (
  eventTime, eventStage, eventStageState, taskId, user,
  stageImageName, registryName, buildGitURL, buildGitBranch,
  buildConfigPath, buildDockerfilePath, buildContextPath,
  repoTags, userGID, entrypoints, architecture
) VALUES (
  now(), 'BUILD', 'NEW', '01JBV2S4PGNV87EGC5WKYA0RND', 'test-user',
  'project/image', 'registry.example.org', 'https://github.com/user/repo.git', 'master',
  '', './Dockerfile', '.', ['latest'], 200, [], ['amd64']
)"

Проверка materialized view:

clickhouse-client --database=TrackDB --query "
SELECT eventStage, eventStageState, taskId, user
FROM stateEvents
WHERE taskId = '01JBV2S4PGNV87EGC5WKYA0RND'
"

Подключение компонентов

Компоненты Altflow ожидают следующие параметры TrackDB:

ПараметрЗначение по умолчанию/пример
databaseTrackDB
status table для APIstateEvents
write table для Saver/Stager/Distributor/TrackerimageEvents
task id fieldtaskId
HTTP port без TLS8123
HTTP port с TLS8443

Для production-развёртывания компоненты обычно используют HTTPS endpoint ClickHouse и CA certificate, заданный в Helm values соответствующего компонента.

Тестовые данные

В документационном репозитории altflow-concept есть генератор нагрузочных данных:

scripts/TrackDB/SYNTH

Это отдельное дерево скриптов altflow-concept, а не scripts/trackdb из основного репозитория altflow. SQL-схема при этом берётся из основного репозитория:

/home/artyom/git/altflow/altflow/scripts/trackdb/createEvents.sql

Путь к SQL-схеме можно переопределить переменной:

TRACKDB_SCHEMA_SQL=/path/to/createEvents.sql ./insertN.sh BUILD 1000 3

Базовый порядок генерации:

cd /home/artyom/git/altflow/altflow-concept/scripts/TrackDB/SYNTH/TrackDB/schemas
./generateNEW.sh

cd ../results
./NewToRunning.sh BUILD
./RunningToDone.sh BUILD
./insertN.sh BUILD 1000 3

watchLive.sh больше не использует удалённое stateEvents_live; он периодически делает обычный SELECT из stateEvents по insertTime.

Архитектура базы данных системы AltFlow

Общее описание

Cистемой AltFlow для sysBD используется PostgreSQL. В кластере создаются несколько логически разделённых баз данных, каждая из которых отвечает за отдельный компонент системы.

Структура:

PostgreSQL Cluster
│
├── distributor    (конфигурация Helm-чартов и стадий)
├── jwt            (авторизация и роли)
└── postgres       (служебная база PostgreSQL)

Также в кластере создаётся несколько ролей пользователей для доступа сервисов к базам данных.


Роли PostgreSQL

Система использует следующие роли:

РольНазначение
distributor_userсервис Distributor
api_userсервис авторизации JWT
postgresадминистратор БД

Пароли должны задаваться вручную.


База данных distributor

Используется сервисом Distributor, который управляет запуском Helm-чартов для разных стадий pipeline.

Таблица helm_charts

Содержит соответствие стадии и Helm-чарта.

ПолеТипОписание
stagetextстадия pipeline
helm_chart_nametextимя чарта
helm_chart_vertextверсия
helm_repotextрепозиторий Helm

Пример данных:

stagechartversion
SIGNsign-image0.1.5
BUILDbuild-image1.1.13
PUSHpush-image1.0.0

Важно! Версии чартов в таблице должны совпадать с теми, которые вы загрузили в oci_registry.

Актуальные версии чартов, как правило, можно посмотреть в репозитории по пути altflow/helm/job_packages.


Таблица helm_repo

Список доступных Helm-репозиториев.

ПолеТип
repo_nametext
repo_urltext

Формат repo_url: https://<IP/DNS>/<PROJECT_NAME>

Таблица tasks

Описывает задачи pipeline.

ПолеТип
idinteger
stagevarchar(10)
helmtext

Используется последовательность:

tasks_id_seq

База данных jwt

Используется для системы авторизации и ролей.

Таблица permissions

Список разрешений.

ПолеТип
idinteger
permission_namevarchar
descriptionvarchar

Пример:

building-oci

Таблица roles

Список ролей.

ПолеТип
idinteger
role_namevarchar
permissionsinteger[]

Пример:

builder -> {1}

Таблица users

Пользователи системы.

ПолеТип
user_idvarchar
usernamevarchar
rolesinteger[]

Данная архитектура разделяет ответственность между несколькими базами:

БазаНазначение
distributorуправление Helm-развёртываниями
jwtроли и авторизация

Такой подход обеспечивает:

  • изоляцию сервисов
  • безопасность доступа
  • масштабируемость компонентов системы.

Установка и инициализация

Установите podman:

apt-get update
apt-get install -y podman
podman --version

Создайте каталоги, например так:

mkdir -p /srv/altflow-postgres/data
mkdir -p /srv/altflow-postgres/conf
mkdir -p /srv/altflow-postgres/tls
mkdir -p /srv/altflow-postgres/init

Скопируйте файлы сертификата и ключа в TLS-каталог:

cp /path/to/your/server.crt /srv/altflow-postgres/tls/server.crt
cp /path/to/your/server.key /srv/altflow-postgres/tls/server.key
chmod 600 /srv/altflow-postgres/tls/server.key
chmod 644 /srv/altflow-postgres/tls/server.crt

Подготовьте pg_hba.conf так, чтобы пускать только TLS:

touch /srv/altflow-postgres/conf/pg_hba.conf

Содержимое файла:

# локальный сокет внутри контейнера
local   all             all                                     trust

# запретить обычный TCP без TLS
hostnossl all           all             0.0.0.0/0               reject
hostnossl all           all             ::0/0                   reject

# разрешить только TLS по паролю
hostssl  all            all             0.0.0.0/0               scram-sha-256
hostssl  all            all             ::0/0                   scram-sha-256

Подготовьте SQL для инициализации AltFlow SysDB:

touch /srv/altflow-postgres/init/01-sysdb.sql

Содержимое /01-sysdb.sql (SQL для развёртывания базы с нуля):

-- ========================================
-- Roles
-- ========================================

CREATE ROLE distributor_user LOGIN PASSWORD '<DISTRIBUTOR_PASSWORD>';
CREATE ROLE api_user LOGIN PASSWORD '<JWT_PASSWORD>';

-- ========================================
-- Database: distributor
-- ========================================

CREATE DATABASE distributor;
\connect distributor

CREATE TABLE helm_charts (
    stage TEXT PRIMARY KEY,
    helm_chart_name TEXT,
    helm_chart_ver TEXT,
    helm_repo TEXT
);

CREATE TABLE helm_repo (
    repo_name TEXT PRIMARY KEY,
    repo_url TEXT
);

CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    stage VARCHAR(10) NOT NULL,
    helm TEXT NOT NULL
);

INSERT INTO helm_charts VALUES
('SIGN','sign-image','0.1.5','altflow-main'),
('BUILD','build-image','1.1.13','altflow-main'),
('PUSH','push-image','1.0.0','altflow-main');

INSERT INTO helm_repo VALUES
('altflow-main','<HELM_REPOSITORY_URL>');

GRANT CONNECT ON DATABASE distributor TO distributor_user;
GRANT USAGE ON SCHEMA public TO distributor_user;
GRANT SELECT ON helm_charts TO distributor_user;
GRANT SELECT ON helm_repo TO distributor_user;
GRANT SELECT ON tasks TO distributor_user;

-- ========================================
-- Database: jwt
-- ========================================

CREATE DATABASE jwt;
\connect jwt

CREATE TABLE permissions (
    id SERIAL PRIMARY KEY,
    permission_name VARCHAR,
    description VARCHAR
);

CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    role_name VARCHAR,
    permissions INTEGER[]
);

CREATE TABLE users (
    user_id VARCHAR PRIMARY KEY,
    username VARCHAR,
    roles INTEGER[]
);

INSERT INTO permissions VALUES
(1,'building-oci','Building oci-images');

INSERT INTO roles VALUES
(1,'builder','{1}');

GRANT CONNECT ON DATABASE jwt TO api_user;
GRANT USAGE ON SCHEMA public TO api_user;
GRANT ALL PRIVILEGES ON TABLE permissions TO api_user;
GRANT ALL PRIVILEGES ON TABLE roles TO api_user;
GRANT ALL PRIVILEGES ON TABLE users TO api_user;
GRANT USAGE, SELECT ON SEQUENCE permissions_id_seq TO api_user;
GRANT USAGE, SELECT ON SEQUENCE roles_id_seq TO api_user;

Загрузите образ контейнера:

podman pull registry.altlinux.org/p11/postgresql

Инициализируйте данные postgres:

chown 46:46 /srv/altflow-postgres/data
chmod 700 /srv/altflow-postgres/data

podman run --rm -it \
  --user 46:46 \
  -v /srv/altflow-postgres/data:/var/lib/postgresql/data \
  registry.altlinux.org/p11/postgresql \
  initdb -D /var/lib/postgresql/data --auth-host=scram-sha-256 --auth-local=trust

Запустите контейнер:

chown 46:46 /srv/altflow-postgres/tls/server.key /srv/altflow-postgres/tls/server.crt
chmod 600 /srv/altflow-postgres/tls/server.key
chmod 644 /srv/altflow-postgres/tls/server.crt

podman run -d \
  --name altflow-postgres \
  --restart=always \
  --user 46:46 \
  -p 5432:5432 \
  -v /srv/altflow-postgres/data:/var/lib/postgresql/data:Z \
  -v /srv/altflow-postgres/tls:/etc/postgresql/tls:ro,Z \
  -v /srv/altflow-postgres/conf/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro,Z \
  registry.altlinux.org/p11/postgresql \
  postgres \
  -D /var/lib/postgresql/data \
  -c listen_addresses='*' \
  -c ssl=on \
  -c ssl_cert_file='/etc/postgresql/tls/server.crt' \
  -c ssl_key_file='/etc/postgresql/tls/server.key' \
  -c password_encryption='scram-sha-256' \
  -c hba_file='/etc/postgresql/pg_hba.conf'

Проверьте запущенный контейнер:

podman logs -f altflow-postgres

Зайдите в контейнер, чтобы задать пароль:

podman exec -it altflow-postgres psql -U postgres

Задайте пароль администратора:

ALTER USER postgres WITH PASSWORD '<STRONG_PASSWORD>';

Инициализируйте базы данных для altflow:

podman exec -i altflow-postgres psql -U postgres -d postgres < /srv/altflow-postgres/init/01-sysdb.sql

Описание Secret Manager

Введение

Secret Manager в Altflow отвечает за безопасное хранение и выдачу секретов: паролей, токенов, ключей доступа к реестрам, ключей подписи и других чувствительных данных. В качестве Secret Manager используется OpenBao (форк HashiCorp Vault).

OpenBao используется в двух сценариях:

  • хранение системных секретов Altflow, которые нужны helm-чартам и компонентам в Kubernetes;
  • хранение пользовательских секретов сборок: данных доступа к git-репозиторию и OCI registry для конкретного TaskID.

В проекте используется KV-хранилище версии 1 без версионирования. При обновлении секрета старое значение перезаписывается, поэтому перед изменениями нужно сохранять резервную копию.

Цели

  • Обеспечить безопасное хранение секретов приложений.
  • Обеспечить безопасное хранение данных аутентификации в git-репозиториях и OCI-реестрах пользователей системы сборки.
  • Упростить доступ к секретам для компонентов системы.
  • Поддержать аудит и контроль доступа к секретам.

Архитектура

Система состоит из следующих частей:

  • OpenBao: основное хранилище секретов.
  • External Secrets Operator: синхронизирует системные секреты из OpenBao в Kubernetes Secrets.
  • Kubernetes Secrets: источник секретов для компонентов Altflow внутри кластера.
  • Клиенты OpenBao: компоненты, которые напрямую читают или записывают секреты сборок.

Компоненты Altflow в Kubernetes обычно не обращаются напрямую к OpenBao за системными секретами. Эти секреты синхронизируются External Secrets Operator и затем монтируются в pod как переменные окружения или файлы.

Общие принципы работы с OpenBao

  • Mount point: kv.
  • Тип хранилища: KV v1.
  • Путь системных секретов: kv/altflow-system-helm-secrets/.
  • Путь секретов сборок: kv/altflow-builds/.
  • Секреты хранятся как JSON-объекты.
  • Доступ ограничивается policies OpenBao.
  • Для доступа из Kubernetes используется AppRole или Kubernetes auth.
  • Для подключений к OpenBao должен использоваться TLS.

Структура секретов в OpenBao

Системные helm-секреты

Системные секреты лежат по пути:

kv/altflow-system-helm-secrets/<имя_ключа>

Актуальные шаблоны находятся по ссылке.

КлючПоляНазначение
api-lite-secretsamqp_password, amqp_username, metrics_token, trackdb_password, trackdb_username, loki_login, loki_passwd, auth_tokenУпрощённый набор секретов API.
api-secretsamqp_password, amqp_username, trackdb_password, trackdb_username, metrics_token, sysdb_password, sysdb_user, loki_passwd, loki_login, keycloak_client_id, keycloak_client_secret, token_secretОсновной набор секретов API.
distributor-secretsHARBOR_NAME, HARBOR_TOKEN, OPENBAO_TOKEN, PASSWORD, POSTGRES_PASSWORD, POSTGRES_USER, RABBITMQ_PASSWORD, RABBITMQ_USER, USERNAMEСекреты Distributor для Harbor, OpenBao, TrackDB, PostgreSQL и RabbitMQ.
internal-registrydockerconfigDocker config для доступа к внутреннему registry.
oci-registrydockerconfigDocker config для доступа к внешнему OCI registry.
rabbitmq-secretspassword, erlangCookieПароль RabbitMQ и Erlang cookie.
saver-secretsamqp_password, amqp_username, open_bao_token, trackdb_password, trackdb_usernameСекреты Saver для RabbitMQ, OpenBao и TrackDB.
sign-job-credsVAULT_TOKENТокен OpenBao для задач подписи.
stager-secretsPASSWORD, RABBITMQ_PASSWORD, RABBITMQ_USER, USERNAMEСекреты Stager для TrackDB и RabbitMQ.
tracker-secretsTRACKDB_PASSWORD, TRACKDB_USERСекреты Tracker для TrackDB.

Пример загрузки системного секрета:

bao kv put kv/altflow-system-helm-secrets/api-secrets @api-secrets.json

Секреты сборок

Пользователи системы сборки образов при обращении к API передают информацию о расположении git-репозитория и данные для доступа к нему и к OCI registry назначения. Эта информация хранится по схеме:

kv/altflow-builds/<buildID>/git
kv/altflow-builds/<buildID>/registry

Пример структуры данных:

altflow-builds/
└── <buildID>/
    ├── git/
    │   ├── username: gituser
    │   └── password: gitpassword
    └── registry/
        ├── username: registryuser
        └── password: registrypassword

Получение секретов git:

curl -s \
  --header "X-Vault-Token: <token>" \
  --header "Content-Type: application/json" \
  https://<vault_address>/v1/kv/altflow-builds/<buildID>/git | jq -r '.data'

Запись секретов git:

curl -s \
  --request POST \
  --header "X-Vault-Token: <token>" \
  --header "Content-Type: application/json" \
  --data '{"username": "<gituser>", "password": "<gitpassword>"}' \
  https://<vault_address>/v1/kv/altflow-builds/<buildID>/git

Для registry используется тот же формат:

curl -s \
  --request POST \
  --header "X-Vault-Token: <token>" \
  --header "Content-Type: application/json" \
  --data '{"username": "<registryuser>", "password": "<registrypassword>"}' \
  https://<vault_address>/v1/kv/altflow-builds/<buildID>/registry

Аутентификация

Для прямого доступа к OpenBao приложения используют токен из переменной среды SM_BAO_TOKEN. Запрос должен передавать токен в заголовке X-Vault-Token.

curl -s \
  --header "X-Vault-Token: ${SM_BAO_TOKEN}" \
  --header "Content-Type: application/json" \
  https://<vault_address>/v1/kv/<path>

Для доступа из Kubernetes используется AppRole или Kubernetes auth. Роль должна выдавать токен с минимально необходимыми правами на чтение системных секретов и создание/обновление секретов сборок.

Пример policy:

path "kv/altflow-system-helm-secrets/*" {
  capabilities = ["read", "list"]
}

path "kv/altflow-builds" {
  capabilities = ["create", "update", "read", "list"]
}

path "kv/altflow-builds/*" {
  capabilities = ["create", "update", "read", "list"]
}

Интеграция с Kubernetes

В Kubernetes системные секреты синхронизируются через External Secrets Operator. Для каждого компонента создаётся ExternalSecret, который читает поля из OpenBao и создаёт или обновляет обычный Kubernetes Secret.

Пример:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: altflow-api-secrets
spec:
  refreshInterval: 1m
  secretStoreRef:
    name: openbao-store
    kind: SecretStore
  target:
    name: altflow-api-secrets
  data:
    - secretKey: amqp_password
      remoteRef:
        key: kv/altflow-system-helm-secrets/api-secrets
        property: amqp_password

После синхронизации компонент использует Kubernetes Secret:

env:
  - name: amqp_password
    valueFrom:
      secretKeyRef:
        name: altflow-api-secrets
        key: amqp_password

Этапы создания SecretStore и ExternalSecret выполняются helm-чартами. Вручную нужно подготовить значения секретов в OpenBao и учётные данные для доступа Kubernetes к OpenBao.

Ключ подписи OCI-образов

Для создания ключа подписи используется transit engine:

bao secrets enable -description='key for sign oci-images altflow test env' transit
bao write transit/keys/altflow-test-key \
  derived=false \
  exportable=true \
  allow_plaintext_backup=true \
  type=ecdsa-p256 \
  auto_rotate_period=0s

Создание и обновление секретов

При первоначальной загрузке администратор:

  1. Получает root-токен или другой административный токен OpenBao.
  2. Включает KV v1 на mount point kv.
  3. Загружает JSON-шаблоны в kv/altflow-system-helm-secrets/.
  4. Создаёт policies и настраивает AppRole или Kubernetes auth.
  5. Передаёт данные доступа helm-чартам, которые создают SecretStore и ExternalSecret.

При регулярном обновлении нужно заменить соответствующий JSON целиком. Так как используется KV v1, частичное обновление без сохранения остальных полей может привести к потере данных. Перед изменением секрета нужно сохранить текущую версию.

Требования к секретам

  • Пароли и токены должны генерироваться CSPRNG, например openssl rand -base64 18.
  • JWT/HMAC-секреты должны быть не короче 32 байт, рекомендуется 64 байта.
  • Docker config в полях dockerconfig должен быть валидным JSON, сохранённым как строка.
  • Секреты не должны попадать в Git в заполненном виде; в репозитории допустимы только пустые шаблоны.
  • Ротация паролей, токенов и JWT-секретов должна выполняться регулярно, рекомендуемый интервал — не реже одного раза в 90 дней.

Мониторинг и аудит

  • В OpenBao должен быть включён audit log.
  • Нужно отслеживать неуспешные попытки аутентификации и подозрительные обращения к секретам.
  • В Kubernetes нужно контролировать состояние ExternalSecret и ошибки синхронизации.

Развёртывание Secret Manager

Подготовьте сервер со статическим IP или DNS-адресом. Выпишите для openbao пару сертификата и ключа в формате X.509 v3 с обязательным указанием в SAN этого IP и/или DNS-адреса.

Для развёртывания OpenBao на отдельном внешнем сервере можно воспользоваться инструкцией. Здесь вам также потребуются выпущенные сертификаты вместе с сертификатом сертифицирующего центра CA, подписавшего ваши сертификаты. Их необходимо поместить в папку ssl репозитория с предопределёнными именами.

Важный момент: если ваш сертификат выпущен промежуточным CA, а в папку ssl вы кладёте сертификат его родителя ROOT CA, то, возможно, вам потребуется создать chain. Например, сертификат для openbao у вас будет называться my-openbao.crt, промежуточного CA - middleCA.crt, а root CA - ca.crt. Тогда под видом требуемого в инструкции openbao.crt вы должны положить не выпущенный для openbao сертификат, а chain, созданный таким образом:

cat my-openbao.crt  middleCA.crt > openbao.crt

Настройка

Основная часть

Установите пакет openbao на компьютер, с которого планируете управлять сервером:

apt-get install openbao

Установите переменные среды с информацией для удалённого подключения через клиент openbao:

  • VAULT_ADDR — URL вашего сервера,
  • VAULT_CACERT — путь к PEM-файлу корневого CA,
  • VAULT_TLS_SERVER_NAME — имя/IP, которое должно совпадать с сертификатом сервера.

Например:

export VAULT_ADDR='https://10.10.7.200:8200'
export VAULT_CACERT="$HOME/ca.crt" # Путь к копии того самого ROOT CA, который вы использовался при установке OpenBao на сервере
export VAULT_TLS_SERVER_NAME='10.10.7.200'

Запустите команду, чтобы получить токен управления и ключи для операции unseal:

bao operator init # Сохраните полученные ключи и токен в безопасном месте

Непосредственно перед логином вам, возможно, нужно провести операцию unseal с помощью введения полученных ранее ключей через команду:

bao operator unseal # вводится несколько раз для каждого ключа

Входим в режим управления хранилищем:

bao login <INITIAL_ROOT_TOKEN>

Для altflow нам понадобится не kv-v2, а обычный non-versioned KV (KV v1), включить который нужно так:

bao secrets enable -path=kv kv

Завершающим шагом будет внесение в базу данных секретов, необходимых для функциорования компонентов alflow. Для этого можно воспользоваться скриптом.

Примечание! Данная инструкция может помощь в упрощённом развёртывании altflow. В реальности же правильно рассмотреть развёртывание openbao внутри кластера kubernetes.

Создание AppRole

Для доступа из kubernetes, вероятнее всего, потребуются RoleID и SecretID.

Для того чтобы создать их, включите AppRole auth method:

bao auth enable approle

Создайте policy только на чтение нужных секретов в отдельном файле altflow-read.hcl:

cat > altflow-read.hcl <<'EOF'
path "kv/altflow-system-helm-secrets/*" {
  capabilities = ["read", "list"]
}

path "kv/altflow-builds" {
  capabilities = ["create", "update", "read", "list"]
}

path "kv/altflow-builds/*" {
  capabilities = ["create", "update", "read", "list"]
}
EOF

Загрузите policy в OpenBao:

bao policy write altflow-read altflow-read.hcl

Создайте AppRole (в документации AppRole описано, что роль создаётся на пути auth/approle/role/<name>):

bao write auth/approle/role/altflow-secrets \
  token_policies="altflow-read" \
  token_ttl="1h" \
  token_max_ttl="24h"

Получите RoleID:

bao read auth/approle/role/altflow-secrets/role-id

Сгенерируйте SecretID:

bao write -f auth/approle/role/altflow-secrets/secret-id

Стек логирования для сборочных компонентов и заданий сборки

В данной документации кратко описывается стек логирования, используемый для сбора и анализа логов сборочных компонентов и заданий сборки. В качестве основных компонентов стека выбраны Vector, Loki и Grafana. Инструменты Vector и Loki объединены в Helm-чарте.

Архитектура развертывания Vector

Vector разделен на две основные части, каждая из которых выполняет свою уникальную функцию:

  1. Vector для сборочных компонентов:

Этот экземпляр Vector настроен на сбор логов из Kubernetes logs и развернут в виде DaemonSet на каждой ноде кластера. Это позволяет собирать логи с всех подов, работающих на данной ноде, обеспечивая централизованный сбор данных.

  1. Vector для сборочных заданий:

Данный экземпляр Vector развёрнут по паттерну Sidecar рядом с каждым сборочным заданием, когда оно инициируется. В качестве источника логов используется File Source. Это решение было выбрано из-за специфики наших сборочных заданий и используемых инструментов. Рассматривались и другие варианты, такие как: Kubernetes Logs (не подошел из-за необходимости предоставления прав для запросов к Kubernetes API и высоких накладных расходов на фильтрацию всех подов), stdin (использование этого метода было невозможно, так как Kaniko не может перенаправить поток логов в файл и передать их в Vector). Для File Source в Vector прокинут каталог /var/log/pods, из которого автоматизация выбирает нужную директорию пода и читает необходимые логи.

Архитектура развертывания Loki

Loki развернут в монолитном режиме. Для упрощения поддержки Loki был переведен из SSD в монолитный режим с развертыванием нескольких реплик. Также добавлена поддержка хранения индексов и чанков в S3.

Заключение

Стек Vector + Loki + Grafana обеспечивает эффективный сбор, хранение и визуализацию логов сборочных компонентов и заданий. Это решение отличается высокой гибкостью и масштабируемостью. Благодаря индексации только меток и сжатию логов, хранение данных становится максимально экономичным.

Стек мониторинга сборочных компонентов

В данной документации представлен стек мониторинга, используемый для отслеживания состояния сборочных компонентов в Kubernetes. В качестве основных компонентов стека выбраны KubeStateMetrics, Prometheus и Grafana.

Архитектура стека

KubeStateMetrics:

Этот инструмент отвечает за извлечение метрик из кластера Kubernetes. Он собирает данные о состоянии выбранных подов, в нашем случае - сборочных компонентов, предоставляя информацию о таких параметрах, как статус, количество реплик и другие ключевые характеристики.

Prometheus:

Prometheus выполняет функции хранения и сбора метрик. Он периодически запрашивает KubeStateMetrics для получения актуальных данных и сохраняет их в TSDB. Prometheus также предлагает возможности для выполнения запросов и анализа собранных метрик.

Grafana:

Grafana используется для визуализации метрик, собранных Prometheus. С помощью Grafana можно создавать настраиваемые дашборды, которые позволяют отслеживать состояние сборочных компонентов в реальном времени и анализировать их производительность.

Заключение

Стек KubeStateMetrics + Prometheus + Grafana обеспечивает надежный мониторинг сборочных компонентов в Kubernetes. Это решение позволяет эффективно собирать и визуализировать метрики, что способствует более глубокому пониманию работы системы и упрощает процесс диагностики проблем и оптимизации.

Визуализация данных в Grafana

Для отображения логов и метрик используется Grafana. Этот инструмент предоставляет мощные возможности для визуализации данных и создания настраиваемых дашбордов. В настоящее время доступно несколько дашбордов, каждый из которых выполняет свою уникальную функцию.

Доступные дашборды

Основной дашборд:

Этот дашборд отображается при входе в Grafana и предоставляет краткую информацию о работоспособности кластера. Он также обеспечивает удобную навигацию по основным дашбордам.

main json модель дашборда

Дашборд статуса сборочных компонентов:

Данный дашборд выводит количество развернутых и желаемых реплик каждого компонента, а также отображает потребление ими системных ресурсов.

main json модель дашборда

Дашборд логов сборочных компонентов:

Этот дашборд предоставляет интерфейс для выбора интересующего компонента и просмотра его логов в режиме реального времени.

main json модель дашборда

Дашборд логов заданий сборки:

В этом дашборде можно выбрать идентификатор сборки и этап, чтобы просмотреть как ранее выполненные сборки, так и выполнение в реальном времени.

main json модель дашборда

Заключение

Использование Grafana для визуализации данных позволяет эффективно отслеживать состояние системы и анализировать логи и метрики сборочных компонентов. Настраиваемые дашборды обеспечивают удобный доступ к необходимой информации, что способствует более быстрому реагированию на возникающие проблемы и улучшению общей производительности системы.