Обзор архитектуры проекта
Введение
Данный документ описывает архитектуру проекта автоматизированного конвейера сборки 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 в namespacealtflow. - После успешного запуска 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-образа
- Пользователь получает у API токен для выполнения запросов на сборку OCI-образов.
- Запрос на сборку поступает в API.
- API выполняет аутентификацию и авторизацию пользователя, формирует
taskIdи отправляет запрос в RabbitMQ. - Компонент Saver извлекает запрос из очереди брокера.
- Saver добавляет в TrackDB первую запись по заданию: стадия
BUILD, состояниеNEW. - Saver записывает credentials пользователя для доступа к git и registry в Secret Manager.
- При успешном выполнении шагов 5 и 6 Saver отправляет событие
BUILD/NEWв Distributor через RabbitMQ. - Первый доступный компонент Distributor извлекает запрос из очереди брокера.
- Distributor получает параметры Helm chart для стадии из SysDB, подставляет данные задания в
values.yamlи при необходимости читает credentials из Secret Manager. - Distributor запускает Kubernetes Job через
helm upgrade --installи записывает в TrackDB состояниеSUSPEND. Это состояние означает, что стадия поставлена в Kubernetes и ожидает выполнения. - В pod стадии запускается рабочий контейнер стадии. Для сбора логов используется Vector: он отправляет логи в Loki в namespace
altflow-observability. - Tracker отслеживает pod'ы стадий через Kubernetes LIST/WATCH. Когда рабочий контейнер стартует, Tracker записывает
RUNNINGв TrackDB. - После завершения рабочего контейнера Tracker записывает
DONEилиFAILв TrackDB, публикует финальное состояние стадии в RabbitMQ для Stager и удаляет Helm release стадии. - Stager получает финальное состояние стадии из RabbitMQ.
- Если стадия завершилась
DONE, Stager выбирает следующую стадию по конфигурации.TESTвыполняется только при наличии test entrypoints,SIGNвыполняется только для настроенных групп пользователей,PUSHпубликует готовый образ в пользовательский registry. - Если стадия завершилась
FAIL, Stager отправляет задачу наCLEANUP, если эта стадия включена в конфигурацию. - Для следующей стадии Stager записывает
NEWв TrackDB и отправляет событие в Distributor. - После завершения
PUSHStager запускает финальную стадиюCLEANUP, которая удаляет временные ресурсы и данные задания.
Действия по получению статуса сборки OCI-образа
- Запрос на получение статуса сборки поступает в API.
- API проверяет токен и выполняет авторизацию.
- API обращается к TrackDB, получает историю состояний задания и возвращает её пользователю.
Действия по получению логов сборки OCI-образа
- Запрос на получение логов сборки поступает в API.
- API проверяет токен и выполняет авторизацию.
- API обращается к Loki через observability endpoint и возвращает пользователю логи, отфильтрованные по
taskIdи, при необходимости, поeventStage.
Будущее развитие конвейера сборки OCI-образов
Будущая работа над конвейером сборки OCI-образов будет сосредоточена на повышении надёжности, безопасности и удобства эксплуатации системы.
-
Расширение API: добавление новых endpoint'ов для управления заданиями, административной диагностики и потокового чтения логов.
-
Документация и обучение: создание подробной документации и обучающих материалов для пользователей и администраторов.
-
Повышение безопасности сборки образов в Kubernetes: ужесточение securityContext, ограничение прав сервисных аккаунтов, контроль привилегированных операций и изоляция стадийных Job'ов.
-
Повышение доступности и отказоустойчивости: улучшение поведения компонентов при сбоях RabbitMQ, TrackDB, SysDB, Secret Manager и Kubernetes API.
-
Улучшение контроля заданий в кластере через Distributor:
- получение лимитов одновременно запущенных заданий из SysDB;
- проверка количества активных стадий в TrackDB и Kubernetes;
- отложенный запуск или отказ от запуска при превышении лимитов;
- запись причины отказа или ожидания в TrackDB.
-
Улучшение reconciler-механизмов: более точное восстановление после пропущенных Kubernetes events, защита от ложного завершения живых задач и расширенная диагностика orphan-сценариев.
-
Развитие 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_path | string | да | Путь в формате registry/image. Устаревший алиас: registry_name. Если в image_path указан тег через :, он добавляется в список тегов. |
image_arch | string или array[string] | да | Архитектуры. Допустимые значения: amd64, x86_64, arm64, aarch64, 386, i586, loongarch64, riscv64. Значения нормализуются к amd64, arm64, i586, loongarch64, riscv64. |
image_tag | string или array[string] | нет | До 10 тегов. Строка может содержать теги через запятую. По умолчанию latest. |
git_url | string | да | HTTPS URL Git-репозитория. |
git_creds | string | нет | Учётные данные Git в формате login:password. |
buildGitBranch | string | нет | Ветка Git. |
registry_creds | string | да | Учётные данные пользовательского registry в формате login:password. |
config_path | string | нет | Путь к пользовательскому конфигурационному файлу. |
buildDockerfilePath | string | да | Путь к Dockerfile внутри Git-репозитория. |
buildContextPath | string | нет | Контекст сборки. Если поле пустое, API берёт каталог, в котором находится Dockerfile, либо .. |
testEntrypoints | array[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"
}
Поля:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
taskId | string | да | ULID задачи. |
eventStage | string | нет | Один из BUILD, TEST, SIGN, PUSH, CLEANUP. Если не указан, API запрашивает логи всех стадий задачи. |
start | string | нет | Начало интервала для Loki. |
end | string | нет | Конец интервала для Loki. |
since | string | нет | Относительный интервал; используется если не указан start. |
limit | string | нет | Лимит записей. По умолчанию 100. |
direction | string | нет | backward или forward. По умолчанию backward. |
format | string | нет | 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_tag | array[string] | Теги образа. Если пользователь не указал тег, API добавляет latest. |
image_arch | array[string] | Нормализованные архитектуры: amd64, arm64, i586, loongarch64, riscv64. |
config_path | string | Опциональный путь к конфигурации сборки. |
registry_name | string | Registry из image_path. |
image_name | string | Имя образа из image_path. |
registry_creds | string | Учётные данные registry в формате login:password. |
git_url | string | HTTPS URL Git-репозитория. |
git_creds | string | Опциональные учётные данные Git в формате login:password. |
build_git_branch | string | Опциональная ветка Git. |
build_context_path | string | Контекст сборки. |
build_dockerfile_path | string | Путь к Dockerfile. |
test_entrypoints | array[array[string]] | Команды для стадии TEST; пустой массив означает пропуск стадии. |
user_gid | number | GID для TrackDB events: в full API используется 200, в lite API 100. |
user | string | Пользователь, определённый API. В lite режиме используется системное значение registry.altlinux.org. |
ulid | string | ULID задачи; в TrackDB пишется как taskId. |
Дальнейшие действия Saver
После получения сообщения Saver:
- Разбирает
git_credsиregistry_credsна парыusername/password. - Сохраняет credentials задачи в Secret Manager по task id.
- Пишет исходное событие
BUILD/NEWв TrackDB таблицуimageEvents. - Публикует stage-event для Distributor в очередь
distributor_queue.
Stage-event для Distributor описан в интерфейсе Stager/Distributor;
Saver использует тот же формат для первичного события BUILD/NEW.
Интерфейс событий TrackDB между Saver, Tracker и Stager
Этот интерфейс не является прямым RPC между Saver и Stager. Компоненты обмениваются состоянием задачи через TrackDB и RabbitMQ:
Saverпишет первичное событиеBUILD/NEWвimageEvents.- Materialized views TrackDB проецируют данные в
stateEventsиimagesByNames. TrackerпишетRUNNING,DONEилиFAILвimageEventsпо факту состояния Kubernetes Job.Trackerпубликует completion-событие в RabbitMQ queuestager_queue.Stagerчитаетstager_queue, проверяет данные в TrackDB, выбирает следующую стадию и пишетNEWдля неё вimageEvents.
TrackDB tables
Источник событий:
imageEvents
Рабочая проекция статусов:
stateEvents
Проекция расширенных данных образа:
imagesByNames
stateEvents содержит компактную историю состояний:
| Поле | Тип/значения | Описание |
|---|---|---|
insertTime | DateTime64(9) | Время вставки события. |
eventTime | DateTime | Логическое время события. |
eventStage | BUILD, TEST, SIGN, PUSH, CLEANUP | Стадия pipeline. |
eventStageState | SUSPEND, NEW, REJECT, RUNNING, DONE, FAIL | Состояние стадии. |
taskId | FixedString(26) | ULID задачи. |
user | string | Пользователь задачи. |
stageImageName | string | Имя образа на текущей стадии. |
stageImageTag | string | Тег образа на текущей стадии. |
errorTypeNum | uint | Код ошибки. |
errorMessage | string | Текст ошибки. |
registryName | string | Registry. |
imageId | string | ID образа, если известен. |
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:
- Проверяет, что для
taskId + eventStageещё нет последнегоNEW. - Пишет новое событие в
imageEvents. - Публикует 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"]
]
}
Поля:
| Поле | Тип | Описание |
|---|---|---|
taskid | string | ULID задачи. В TrackDB это поле называется taskId. |
eventStage | string | Стадия: BUILD, TEST, SIGN, PUSH, CLEANUP. |
eventStageState | string | Для запуска Distributor ожидает NEW. |
user | string | Пользователь задачи. |
stageImageName | string | Имя образа на текущей стадии. |
stageImageTag | string | Тег образа на текущей стадии, если известен. |
registryName | string | Registry. |
imageId | string | ID образа, если известен. |
errorTypeNum | number | Код ошибки, обычно 0 для NEW. |
errorMessage | string | Сообщение об ошибке, обычно пустое для NEW. |
buildGitURL | string | Git URL исходников. |
buildDockerfilePath | string | Путь к Dockerfile. |
buildContextPath | string | Контекст сборки. |
buildGitBranch | string | Ветка Git. |
repoTags | array[string] | Теги результирующего образа. |
architecture | array[string] | Архитектуры для job package. |
entrypoints | array[array[string]] | Команды для TEST; для остальных стадий обычно пустой массив. |
Действия Distributor
После получения сообщения Distributor:
- По
eventStageполучает из SysDB URL, имя и версию Helm chart. - Для
BUILDиPUSHчитает credentials задачи из Secret Manager. - Выполняет
helm registry loginдля registry с chart packages, если это включено в конфигурации. - Выполняет
helm pull oci://... --untar. - Переписывает
values.yamljob 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.
- RabbitMQ callback labels/values:
- Запускает Kubernetes Job через:
helm upgrade --install <taskid-lowercase>-<stage-lowercase> ./<chart_name>
- Пишет в
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 необходимо подготовить кластер следующим образом:
- В кластере должен быть установлен конроллер External Secret Operator (ESO).
- Cert-manager. Для выпуска сертификата должны быть загружены сертификат промежуточного CA и его ключ, например, так:
kubectl -n cert-manager create secret tls altlinux-intermediate-ca \
--cert=test.chain.pem \
--key=test.key.unencrypted.pem
- Должен быть установлен ClusterIssuer для altlinux-intermediate-ca.
- Должен быть загружен корневой сертификат выпускающего центра, выпустившего промежуточный 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
- В секреты k8s для namespaces
altflowиaltflow-observabilityдолжны быть загружены credentials для altflow-openbao, примерmanifests/openbao-secrets.yaml. - Установлен ceph-rbd.
- Ноды, на которых 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.
Поток данных

Все компоненты пишут события только во входную таблицу imageEvents. Она использует
ENGINE = Null: сама таблица не хранит строки, а служит точкой fan-out для materialized
views.
Поток выглядит так:
Saverвставляет первичное событиеBUILD/NEW.DistributorвставляетSUSPENDилиFAILпосле попытки создать Helm release.TrackerвставляетRUNNING,DONEилиFAILпо состоянию Kubernetes pod.StagerвставляетNEWдля следующей стадии илиCLEANUPпослеFAIL.- Materialized views раскладывают поток в таблицы чтения:
stateEvents,stateList_State,imagesByNames.
Таблицы и представления
| Объект | Тип | Назначение |
|---|---|---|
imageEvents | Null table | Входной append-only stream событий. Все INSERT идут сюда. |
stateEvents_mv | materialized view | Проецирует компактные поля состояния из imageEvents в stateEvents. |
stateEvents | MergeTree | История состояний задач без полного OCI metadata. Используется API, Tracker, Stager. |
stateList_State_mv | materialized view | Агрегирует последние состояния задачи по стадии. |
stateList_State | AggregatingMergeTree | Хранит промежуточные aggregate states для stateList. |
stateList | view | Удобное представление с states, eventTimes, imageIds. |
imagesByNames_mv | materialized view | Проецирует расширенные данные образа из imageEvents в imagesByNames. |
imagesByNames | MergeTree | Данные образов, build context, tags, architectures, entrypoints и userGID. |
В текущей схеме нет stateEvents_live: старое live view было удалено.
Стадии и состояния
eventStage:
| Значение | Описание |
|---|---|
BUILD | Сборка OCI-образа. |
TEST | Запуск пользовательских test entrypoints. |
SIGN | Подпись образа. |
PUSH | Публикация образа в пользовательский registry. |
CLEANUP | Удаление временных ресурсов и данных задачи. |
eventStageState:
| Значение | Описание |
|---|---|
NEW | Стадия поставлена в pipeline и ожидает Distributor. |
SUSPEND | Distributor создал Kubernetes Job; дальнейшее состояние пишет Tracker. |
RUNNING | Рабочий контейнер стадии начал выполняться. |
DONE | Стадия успешно завершилась. |
FAIL | Стадия завершилась ошибкой или не была создана. |
REJECT | Зарезервированное состояние отказа в запуске. |
imageEvents
imageEvents — единственная таблица для записи событий.
Ключевые поля:
| Поле | Тип | Описание |
|---|---|---|
insertTime | DateTime64(9) DEFAULT now64(9) | Время вставки события в ClickHouse. Не передаётся в INSERT. |
eventTime | DateTime | Время события со стороны компонента. |
eventStage | enum | Стадия pipeline. |
eventStageState | enum | Состояние стадии. |
taskId | FixedString(26) | ULID задачи. |
user | String | Пользователь задачи. |
stageImageName | String | Имя образа без registry. |
stageImageTag | String DEFAULT '' | Тег образа на стадии, если известен. |
errorTypeNum | UInt32 DEFAULT 0 | Код ошибки. |
errorMessage | String DEFAULT '' | Текст ошибки. |
registryName | String | Registry. |
buildGitURL | String | Git URL исходников. |
buildGitBranch | String | Git branch/tag. |
buildConfigPath | String | Путь к пользовательскому config file. |
buildDockerfilePath | String | Путь к Dockerfile. |
buildContextPath | String | Контекст сборки. |
imageId | String DEFAULT '' | ID образа, если известен. |
digest | String DEFAULT '' | Digest образа. |
repoTags | Array(String) | Теги результирующего образа. |
repoDigests | Array(String) | Digest-адреса образа. |
userUID | UInt32 DEFAULT 0 | UID пользователя внутри образа. |
userGID | UInt32 | GID пользователя Altflow; используется gate-логикой SIGN. |
env | Array(String) | Environment образа. |
entrypoints | Array(Array(String)) | Команды для стадии TEST. |
labels | Map(String, String) | Labels образа. |
architecture | Array(String) | Целевые архитектуры. |
os | String DEFAULT '' | OS образа. |
size | UInt32 DEFAULT 0 | Размер образа. |
layers | Array(String) | Layers образа. |
annotations | Map(String, String) | OCI annotations. |
manifestType | String DEFAULT '' | Тип manifest. |
history | Array(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)
Важные поля:
| Поле | Источник | Описание |
|---|---|---|
name | stageImageName | Имя образа. |
tag | stageImageTag | Тег образа на стадии. |
taskId | taskId | ULID задачи. |
stage | eventStage | Стадия. |
state | eventStageState | Состояние. |
buildGitURL | buildGitURL | Git URL. |
buildGitBranch | buildGitBranch | Git branch/tag. |
buildDockerfilePath | buildDockerfilePath | Dockerfile path. |
buildContextPath | buildContextPath | Build context. |
repoTags | repoTags | Теги результирующего образа. |
entrypoints | entrypoints | TEST commands. |
architecture | architecture | Target architectures. |
userGID | userGID | Используется 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 создаёт двух пользователей:
| Пользователь | Права |
|---|---|
altflow | SHOW TABLES, SELECT ON TrackDB.*, INSERT ON TrackDB.imageEvents |
altflow_stager | SHOW 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
Скрипт создаёт:
| Пользователь | Назначение |
|---|---|
altflow | API, Saver, Distributor, Tracker. |
altflow_stager | Stager. |
Права:
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:
| Параметр | Значение по умолчанию/пример |
|---|---|
| database | TrackDB |
| status table для API | stateEvents |
| write table для Saver/Stager/Distributor/Tracker | imageEvents |
| task id field | taskId |
| HTTP port без TLS | 8123 |
| HTTP port с TLS | 8443 |
Для 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-чарта.
| Поле | Тип | Описание |
|---|---|---|
stage | text | стадия pipeline |
helm_chart_name | text | имя чарта |
helm_chart_ver | text | версия |
helm_repo | text | репозиторий Helm |
Пример данных:
| stage | chart | version |
|---|---|---|
| SIGN | sign-image | 0.1.5 |
| BUILD | build-image | 1.1.13 |
| PUSH | push-image | 1.0.0 |
Важно! Версии чартов в таблице должны совпадать с теми, которые вы загрузили в oci_registry.
Актуальные версии чартов, как правило, можно посмотреть в репозитории по пути altflow/helm/job_packages.
Таблица helm_repo
Список доступных Helm-репозиториев.
| Поле | Тип |
|---|---|
| repo_name | text |
| repo_url | text |
Формат repo_url: https://<IP/DNS>/<PROJECT_NAME>
Таблица tasks
Описывает задачи pipeline.
| Поле | Тип |
|---|---|
| id | integer |
| stage | varchar(10) |
| helm | text |
Используется последовательность:
tasks_id_seq
База данных jwt
Используется для системы авторизации и ролей.
Таблица permissions
Список разрешений.
| Поле | Тип |
|---|---|
| id | integer |
| permission_name | varchar |
| description | varchar |
Пример:
building-oci
Таблица roles
Список ролей.
| Поле | Тип |
|---|---|
| id | integer |
| role_name | varchar |
| permissions | integer[] |
Пример:
builder -> {1}
Таблица users
Пользователи системы.
| Поле | Тип |
|---|---|
| user_id | varchar |
| username | varchar |
| roles | integer[] |
Данная архитектура разделяет ответственность между несколькими базами:
| База | Назначение |
|---|---|
| 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-secrets | amqp_password, amqp_username, metrics_token, trackdb_password, trackdb_username, loki_login, loki_passwd, auth_token | Упрощённый набор секретов API. |
api-secrets | amqp_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-secrets | HARBOR_NAME, HARBOR_TOKEN, OPENBAO_TOKEN, PASSWORD, POSTGRES_PASSWORD, POSTGRES_USER, RABBITMQ_PASSWORD, RABBITMQ_USER, USERNAME | Секреты Distributor для Harbor, OpenBao, TrackDB, PostgreSQL и RabbitMQ. |
internal-registry | dockerconfig | Docker config для доступа к внутреннему registry. |
oci-registry | dockerconfig | Docker config для доступа к внешнему OCI registry. |
rabbitmq-secrets | password, erlangCookie | Пароль RabbitMQ и Erlang cookie. |
saver-secrets | amqp_password, amqp_username, open_bao_token, trackdb_password, trackdb_username | Секреты Saver для RabbitMQ, OpenBao и TrackDB. |
sign-job-creds | VAULT_TOKEN | Токен OpenBao для задач подписи. |
stager-secrets | PASSWORD, RABBITMQ_PASSWORD, RABBITMQ_USER, USERNAME | Секреты Stager для TrackDB и RabbitMQ. |
tracker-secrets | TRACKDB_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
Создание и обновление секретов
При первоначальной загрузке администратор:
- Получает root-токен или другой административный токен OpenBao.
- Включает KV v1 на mount point
kv. - Загружает JSON-шаблоны в
kv/altflow-system-helm-secrets/. - Создаёт policies и настраивает AppRole или Kubernetes auth.
- Передаёт данные доступа 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 разделен на две основные части, каждая из которых выполняет свою уникальную функцию:
- Vector для сборочных компонентов:
Этот экземпляр Vector настроен на сбор логов из Kubernetes logs и развернут в виде DaemonSet на каждой ноде кластера. Это позволяет собирать логи с всех подов, работающих на данной ноде, обеспечивая централизованный сбор данных.
- 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 и предоставляет краткую информацию о работоспособности кластера. Он также обеспечивает удобную навигацию по основным дашбордам.
Дашборд статуса сборочных компонентов:
Данный дашборд выводит количество развернутых и желаемых реплик каждого компонента, а также отображает потребление ими системных ресурсов.
Дашборд логов сборочных компонентов:
Этот дашборд предоставляет интерфейс для выбора интересующего компонента и просмотра его логов в режиме реального времени.
Дашборд логов заданий сборки:
В этом дашборде можно выбрать идентификатор сборки и этап, чтобы просмотреть как ранее выполненные сборки, так и выполнение в реальном времени.
Заключение
Использование Grafana для визуализации данных позволяет эффективно отслеживать состояние системы и анализировать логи и метрики сборочных компонентов. Настраиваемые дашборды обеспечивают удобный доступ к необходимой информации, что способствует более быстрому реагированию на возникающие проблемы и улучшению общей производительности системы.