Установка через Wizard¶
Wizard — официальный Go-инсталлер AppSec.GenAI v2, упакованный в Docker-образ. Управляет полным жизненным циклом: namespace, pull-secret, инфраструктура, продуктовые сервисы — в правильном порядке.
Docker Compose
Инструкция по развёртыванию на отдельном сервере через Docker Compose — в разделе Установка и запуск.
Перед началом¶
Подготовьте до запуска — эти пять пунктов закрывают почти все причины неудачной первой установки:
- Версия и доступ к реестру. Получите у SwordFish Security тег образа Wizard (например
2026.5.1) и учётные данные Harbor (логин + пароль) — их нужно передать команде установки (--harbor-user/HARBOR_PASS, см. Быстрый старт), иначе поды не скачают образы (ImagePullBackOff). - kubeconfig со статическим Bearer-токеном. Wizard работает в контейнере и не умеет OIDC/exec-плагины (
gcloud,aws-iam-authenticator,d8/kubelogin). Подготовьте kubeconfig с прямымuser.token— см. Доступ к кластеру: kubeconfig ниже. - Envoy Gateway — для режима
exposure: gateway(по умолчанию). Он должен быть установлен в кластере заранее, иначеpreflight:gatewayзавершится сFAIL:Если ставите через классический Ingress — выберитеhelm install envoy-gateway \ oci://docker.io/envoyproxy/gateway-helm \ --version v1.5.1 -n envoy-gateway-system --create-namespaceexposure: ingress(Envoy не нужен, но OIDC недоступен). - StorageClass — при
persistence: true(production). Убедитесь, что в кластере есть default StorageClass (kubectl get storageclass), либо приготовьте имя нужного SC, чтобы указать его в мастере. - TLS-секрет и DNS — при
auth: true. Для production с OIDC заранее создайте TLS-секрет и записи DNS на публичные хосты (UI, Keycloak, MinIO).
Проверка без изменений
Перед реальной установкой всегда прогоняйте check (см. Pre-flight проверки) — он валидирует доступ к кластеру, наличие Envoy Gateway, Secret'ы и конфигурацию, ничего не устанавливая.
Способ доступа извне (exposure)¶
Wizard разделяет два независимых решения:
exposure— как внешний трафик попадает в сервисы:gateway|ingress|none(единый источник правды).auth.enabled— включён ли OIDC (Keycloak + Envoy SecurityPolicy). Ортогонален, учитывается только приexposure: gateway.
Трафик через Envoy Gateway (Gateway API). Только этот режим поддерживает OIDC.
auth: false— Gateway/HTTPRoute без аутентификации (dev/smoke).auth: true— Keycloak + OIDC SecurityPolicy, JWT во всех сервисах (production). Требует TLS-секрет и DNS на хосты.
Классический Ingress (ingress-nginx и т.п.). OIDC недоступен — сервисы открыты без логина.
Точка входа не создаётся. После установки создаёте Gateway/Ingress/HTTPRoute сами. Доступ для проверки — kubectl port-forward.
Быстрый старт: интерактивный Wizard¶
Одна команда: задаёт вопросы, пишет values.yaml и — если ответить y на «Run install now?» — сразу разворачивает платформу (создаёт namespace, pull-secret harbor-cr, инфраструктуру и продуктовые сервисы). Harbor-креды передаются той же командой:
docker run --rm -it \
-v ~/.kube/config:/kubeconfig:ro \
-v $(pwd):/workspace \
-e HARBOR_PASS="<пароль>" \
registry.appsec.global/appsecgenai-release/wizard:<VERSION> \
wizard --harbor-user "<логин>"
Кластер на OIDC/exec-аутентификации (EKS, GKE, Deckhouse…)
Если ~/.kube/config использует OIDC или exec-плагин, wizard не подключится к кластеру. Смонтируйте вместо него kubeconfig со статическим токеном: -v ~/kube-wizard.yaml:/kubeconfig:ro. Как его собрать — Доступ к кластеру: kubeconfig ниже.
Только сгенерировать values.yaml
Если хотите сначала отредактировать конфигурацию — ответьте n на «Run install now?». Тогда Harbor-креды не нужны на этом шаге; разверните позже командой install с теми же --harbor-user/HARBOR_PASS.
Доступ к кластеру: kubeconfig со статическим токеном¶
OIDC / exec-based auth
Если ~/.kube/config использует OIDC (auth-provider: name: oidc) или exec-плагин (exec: command: d8/kubelogin/...) — wizard не сможет подключиться: внешние бинари недоступны в контейнере. Нужен kubeconfig со статическим Bearer-токеном.
Получите токен из вашего OIDC-провайдера (пример для Deckhouse/dex):
TOKEN=$(d8 login get-token ... | python3 -c "import sys,json; print(json.load(sys.stdin)['status']['token'])")
Создайте ~/kube-wizard.yaml и подставьте токен в поле user.token:
apiVersion: v1
kind: Config
current-context: my-cluster
clusters:
- cluster:
server: https://api.<cluster-domain>
name: my-cluster
contexts:
- context:
cluster: my-cluster
namespace: genai
user: wizard
name: my-cluster
users:
- name: wizard
user:
token: <TOKEN>
Монтировать: -v ~/kube-wizard.yaml:/kubeconfig:ro
Токен живёт ~1h — при истечении пересоздать.
Wizard задаёт минимум вопросов и записывает values.yaml в текущую директорию:
AppSec GenAI — Setup Wizard
────────────────────────────
Domain: app.example.com
Namespace [genai]:
How should services be exposed? (gateway/ingress/none) [gateway]: gateway
Enable auth (Keycloak + Envoy OIDC)? [y/N]: n
Enable persistence (required for production)? [y/N]: y
StorageClass (leave blank for cluster default):
✓ values.yaml written
Run install now? [Y/n]: y
Дополнительные вопросы при auth
При exposure: gateway + Enable auth? y wizard также спросит имя TLS-секрета (default genai-tls), Gateway className (default genai-class) и внешний IP (пусто — авто-назначение в облаке). При exposure: ingress — только Ingress className (с предупреждением, что OIDC недоступен).
Куда записывается values.yaml
Инсталлер внутри контейнера обнаруживает /workspace и пишет файл туда — он появляется в текущей директории хоста благодаря монтированию -v $(pwd):/workspace.
По окончании wizard выводит endpoints:
UI: https://app.example.com
Keycloak: https://keycloak.example.com
MinIO: https://minio.example.com
DNS records (все → gateway.externalIPs[0]):
app.example.com
keycloak.example.com
minio.example.com
Пароль bootstrap-пользователя genai-admin:
kubectl -n genai get secret keycloak-auth \
-o jsonpath='{.data.KEYCLOAK_BOOTSTRAP_USER_PASSWORD}' | base64 -d
Установка из готового values.yaml¶
docker run --rm \
-v ~/.kube/config:/kubeconfig:ro \
-v ./values.yaml:/values.yaml \
-e HARBOR_PASS="<пароль>" \
registry.appsec.global/appsecgenai-release/wizard:<VERSION> \
install --harbor-user "<логин>" -f /values.yaml
Запуск как in-cluster Job¶
Альтернатива docker run с рабочей машины — запуск инсталлера внутри кластера через Kubernetes Job. Предпочтительно, когда нет прямого доступа к kube-API с локальной машины, нужен wait-for зависимостей по внутреннему DNS кластера, или установка идёт из CI/CD.
Образ full-таргета несёт вшитые чарты и ENTRYPOINT ["/app/installer", "--local-charts", "/charts"]; команда (check/install/delete) добавляется через args.
# 1) ConfigMap c values: kubectl -n genai create configmap wizard-values \
# --from-file=values.yaml=values.yaml --dry-run=client -o yaml | kubectl apply -f -
# 2) SA + ClusterRoleBinding(cluster-admin) на wizard-installer (создаёт ns, ставит релизы, детектит CRD)
# 3) Job:
apiVersion: batch/v1
kind: Job
metadata: { name: wizard-check, namespace: genai }
spec:
backoffLimit: 0
template:
spec:
serviceAccountName: wizard-installer
restartPolicy: Never
imagePullSecrets: [{ name: harbor-cr }]
containers:
- name: wizard
image: registry.appsec.global/appsecgenai-release/wizard:<VERSION>
args: ["check", "-f", "/cfg/values.yaml"] # затем install / delete
env:
- { name: KUBECONFIG, value: "" } # fallback на in-cluster SA
volumeMounts: [{ name: cfg, mountPath: /cfg }]
volumes:
- name: cfg
configMap: { name: wizard-values }
После зелёного check — пересоздать Job с args: ["install", ...].
Грабли in-cluster Job
ImagePullBackOff: no basic auth credentials—harbor-crавторизует хостregistry.appsec.global; вimage:использовать именно его.loading kubeconfig— образ ставитENV KUBECONFIG=/kubeconfig, а в Job такого файла нет → выставитьenv KUBECONFIG=""(fallback на in-cluster SA).
Команды¶
| Команда | Описание |
|---|---|
wizard |
Интерактивный ввод → values.yaml → опциональный install |
install |
Полная установка всех компонентов |
update |
Умный апгрейд — пропускает релизы без изменений |
update --force |
Принудительный апгрейд всех релизов |
delete |
Удалить все Helm-релизы (PVC сохраняются) |
check |
Pre-flight проверки без изменений |
Флаги¶
| Флаг | По умолчанию | Описание |
|---|---|---|
-f <path> |
values.yaml |
Путь к конфигурации |
--dry-run |
false |
Отрендерить без применения |
--only <a,b> |
— | Только указанные фазы |
--skip <a,b> |
— | Пропустить фазы |
--set key=value |
— | Переопределить значение |
--harbor-user |
— | Логин Harbor для pull-secret |
--harbor-pass |
$HARBOR_PASS |
Пароль Harbor |
--harbor-server |
registry.appsec.global |
Хост Harbor |
--parallel N |
4 |
Параллельность Phase 2 |
--no-gpu |
false |
Пропустить GPU-сервисы (gpu: true в профиле) |
--skip-services <a,b> |
— | Пропустить конкретные phase-2 сервисы |
--release-version |
вшита в образ | Переопределить версию чартов |
Пароль Harbor через env
Передавайте пароль через -e HARBOR_PASS="...", не через --harbor-pass (чтобы не светить в ps aux).
values.yaml — справочник¶
Обязательные параметры¶
| Параметр | Валидация | Описание |
|---|---|---|
domain |
required, не app.customer.com |
Базовый домен инсталляции |
namespace |
required | Kubernetes namespace (default: genai) |
Полная схема¶
# ─── Базовые ────────────────────────────────────────────────────────────────
domain: "app.example.com" # ОБЯЗАТЕЛЕН
namespace: "genai"
instance: "" # суффикс для мультиинсталляций
# ─── Доступ извне ─────────────────────────────────────────────────────────────
exposure: "gateway" # gateway | ingress | none
auth:
enabled: false # OIDC; учитывается только при exposure=gateway
gateway: # заполнять при exposure=gateway
publicScheme: "https" # http | https
externalIPs: [] # bare-metal: ["10.0.0.1"]; cloud: []
className: "genai-class" # уникален в кластере
ingress:
className: "" # при exposure=ingress
ingressTlsSecretName: "" # при auth=true + https
imagePullSecretName: "harbor-cr"
# ─── Секреты ─────────────────────────────────────────────────────────────────
secrets:
create: true # false = создать Secret'ы вручную до запуска
# ─── Инфра-компоненты ────────────────────────────────────────────────────────
postgres:
enabled: true
auth:
username: genai_admin
database: genai_db
persistence: { enabled: false, size: "50Gi", storageClass: "" }
redis:
enabled: true
persistence: { enabled: false, size: "2Gi", storageClass: "" }
nats:
enabled: true
persistence: { enabled: false }
minio:
enabled: true
persistence: { enabled: false, size: "200Gi", storageClass: "" }
keycloak:
enabled: true # только при auth.enabled: true
persistence: { enabled: false, size: "1Gi", storageClass: "" }
# ─── Внешняя инфраструктура ──────────────────────────────────────────────────
global:
image:
registry: "registry.appsec.global"
repositoryPrefix: "appsecgenai-release"
deps:
postgres:
host: postgres
port: 5432
existingSecret: postgres-auth
username: genai_admin
database: genai_db
# bootstrap: true # создать схемы в существующей БД
redis:
host: redis
port: 6379
existingSecret: redis-auth
nats:
host: nats
port: 4222
# scheme: tls # tls:// если внешний NATS за TLS (default: nats://)
# bootstrap: true # создать 6 JetStream streams
minio:
host: minio
port: 9000
existingSecret: minio-auth
# scheme: https # https если внешний MinIO за TLS (default: http)
# bootstrap: true # создать 12 S3 buckets
keycloak:
host: keycloak
port: 8080
# scheme: https # https если внешний Keycloak за TLS (default: http)
# bootstrap: true # создать realm appsec-genai
# ─── Опционально ─────────────────────────────────────────────────────────────
license:
centerPublicKey: "" # RSA-2048 pubkey от SwordFish Security
tracing:
enabled: false # true требует otel-collector в кластере
bootstrap: идемпотентность
Операции bootstrap безопасны для повторного запуска — бакеты, потоки и realm создаются с IF NOT EXISTS / --ignore-existing (409 Conflict = пропуск). Оставить bootstrap: true на каждый update — нормально.
Когда нужен bootstrap:
| Ситуация | Нужен? |
|---|---|
Встроенный сервис (enabled: true) |
Нет — Helm hook jobs инициализируют автоматически |
Внешний сервис (enabled: false), первый install |
Да |
Внешний сервис (enabled: false), повторный update |
Оставить true (безвредно) или убрать (тоже нормально) |
Секреты инфра-компонентов¶
Что делает secrets.create¶
secrets.create |
Компонент enabled |
Кто создаёт Secret | Что делает Wizard |
|---|---|---|---|
true (default) |
true |
Infra-чарт автоматически при helm install |
Ничего дополнительно |
false |
true |
Вы — до запуска install | Preflight проверяет наличие |
| любое | false (внешний) |
Вы — всегда вручную | Preflight НЕ проверяет |
Внешний сервис — всегда ручной Secret
При postgres.enabled: false / redis.enabled: false / minio.enabled: false Wizard не создаёт никаких Secret'ов вне зависимости от secrets.create. Secret нужно создать самостоятельно до запуска.
Для чего existingSecret в global.deps.*¶
existingSecret — это имя K8s Secret'а, из которого продуктовые сервисы (jailbreak, results, orchestrator и т.д.) будут читать credentials при старте. Это не путь, не URL — просто имя объекта в namespace.
- При
secrets.create: trueинфра-чарт сам создаёт Secret с этим именем. - При
secrets.create: falseили внешнем сервисе — вы создаёте Secret с этим именем сами.
Менять existingSecret нужно только если вы хотите использовать другое имя Secret'а (например, интеграция с ESO / Vault, где секрет уже существует под другим именем).
Какие ключи должны быть в каждом Secret'е¶
| Secret | Ключ | Значение | Кто читает |
|---|---|---|---|
postgres-auth |
POSTGRES_PASSWORD |
пароль пользователя БД | все сервисы с БД |
redis-auth |
REDIS_PASSWORD |
пароль Redis | сервисы с Redis |
minio-auth |
MINIO_ROOT_USER |
access key (3–20 символов) | сервисы с S3 |
minio-auth |
MINIO_ROOT_PASSWORD |
secret key (8+ символов) | сервисы с S3 |
Postgres: пароль, не URL
В postgres-auth хранится только пароль (POSTGRES_PASSWORD). Полный DATABASE_URL продуктовые сервисы собирают сами из нескольких источников:
host,port,username,database— берутся изglobal.deps.postgres.*в values.yamlPOSTGRES_PASSWORD— читается из Secretpostgres-authчерезsecretKeyRef- Итог:
postgresql+asyncpg://{username}:{POSTGRES_PASSWORD}@{host}:{port}/{database}
Класть полный URL в секрет не нужно и не поддерживается в wizard-режиме.
Pre-create команды (при secrets.create: false или внешнем сервисе)¶
# PostgreSQL
kubectl create secret generic postgres-auth -n genai \
--from-literal=POSTGRES_PASSWORD='<strong-password>'
# Redis
kubectl create secret generic redis-auth -n genai \
--from-literal=REDIS_PASSWORD='<strong-password>'
# MinIO
kubectl create secret generic minio-auth -n genai \
--from-literal=MINIO_ROOT_USER='<access-key>' \
--from-literal=MINIO_ROOT_PASSWORD='<secret-key>'
Если используете нестандартное имя Secret'а — укажите его в global.deps.*.existingSecret.
Фазы установки¶
| Фаза | Имя | Что делает | Пропускается |
|---|---|---|---|
| Pre-flight | preflight:secrets |
Проверяет Secret'ы | при secrets.create: true |
| Pre-flight | preflight:gateway |
Envoy Gateway / Gateway API CRD | при exposure≠gateway → ok/warn |
| Pre-flight | preflight:gpu |
Нода с nvidia.com/gpu |
для GenAI всегда (нет GPU-сервисов) |
| Pre-flight | preflight:deps |
Непустота global.deps.*.existingSecret |
— |
| Pre-flight | preflight:appsecrets |
App-секреты продукта | для GenAI всегда (нет required) |
| Phase 1 | phase1:infra |
postgres, redis, nats, minio, keycloak | — |
| Phase 1.1 | phase1.1 |
Bootstrap внешних сервисов | если *.bootstrap не задан |
| Phase 1:secrets | phase1:secrets |
Создаёт Secret'ы | при secrets.create: false |
| Phase 1.5 | phase1.5:genai-gateway |
Envoy Gateway + OIDC | при exposure≠gateway |
| Phase 2 | phase2:product |
12 продуктовых сервисов | — |
| Phase 2.5 | phase2.5 |
Ingress для UI | при exposure≠ingress |
Частичный запуск¶
# Только продуктовые сервисы (после hotfix образа)
docker run --rm ... install --only phase2:product
# Пропустить инфраструктуру
docker run --rm ... install --skip phase1:infra,phase1.1
# Переопределить значение на лету
docker run --rm ... install --set tracing.enabled=true
Pre-flight проверки¶
docker run --rm \
-v ~/.kube/config:/kubeconfig:ro \
-v ./values.yaml:/values.yaml \
registry.appsec.global/appsecgenai-release/wizard:<VERSION> \
check -f /values.yaml
CHECK STATUS MESSAGE
----- ------ -------
gateway:envoy OK Envoy Gateway controller running
postgres WARN storageClass is empty — will use cluster default
phase1:infra OK infra config looks good
✓ all checks passed
| Статус | Значение |
|---|---|
OK |
Проверка пройдена |
WARN |
Установка продолжится, стоит обратить внимание |
FAIL |
Критическая ошибка — установка прервётся |
Обновление¶
# Умный апгрейд (пропускает релизы без изменений)
docker run --rm \
-v ~/.kube/config:/kubeconfig:ro \
-v ./values.yaml:/values.yaml \
registry.appsec.global/appsecgenai-release/wizard:<NEW_VERSION> \
update -f /values.yaml
# Принудительный апгрейд (hotfix под тот же semver)
docker run --rm ... update --force -f /values.yaml
Версия инсталлера = версия продукта
Тег образа Wizard определяет версию всех Helm-чартов. Для апгрейда достаточно поменять тег — версия в values.yaml не указывается.
Проверка установки¶
# Статус подов
kubectl get pods -n genai
# Healthcheck через port-forward (auth=false)
kubectl port-forward svc/ui 8080:8080 -n genai &
curl http://localhost:8080/
# Пароль bootstrap-пользователя (auth=true)
kubectl -n genai get secret keycloak-auth \
-o jsonpath='{.data.KEYCLOAK_BOOTSTRAP_USER_PASSWORD}' | base64 -d
Удаление¶
docker run --rm \
-v ~/.kube/config:/kubeconfig:ro \
-v ./values.yaml:/values.yaml \
registry.appsec.global/appsecgenai-release/wizard:<VERSION> \
delete -f /values.yaml
PVC сохраняются
Команда delete удаляет Helm-релизы, но не PVC. Для полного удаления:
Устранение неполадок¶
ImagePullBackOff¶
kubectl delete secret harbor-cr -n genai 2>/dev/null || true
kubectl create secret docker-registry harbor-cr -n genai \
--docker-server=registry.appsec.global \
--docker-username=<логин> --docker-password=<пароль>
FAIL: gateway:envoy-required¶
При exposure: gateway требуется Envoy Gateway:
helm install envoy-gateway \
oci://docker.io/envoyproxy/gateway-helm \
--version v1.5.1 -n envoy-gateway-system --create-namespace