Перейти к содержанию

Установка через Wizard

Wizard — официальный Go-инсталлер AppSec.GenAI v2, упакованный в Docker-образ. Управляет полным жизненным циклом: namespace, pull-secret, инфраструктура, продуктовые сервисы — в правильном порядке.

Docker Compose

Инструкция по развёртыванию на отдельном сервере через Docker Compose — в разделе Установка и запуск.


Перед началом

Подготовьте до запуска — эти пять пунктов закрывают почти все причины неудачной первой установки:

  1. Версия и доступ к реестру. Получите у SwordFish Security тег образа Wizard (например 2026.5.1) и учётные данные Harbor (логин + пароль) — их нужно передать команде установки (--harbor-user / HARBOR_PASS, см. Быстрый старт), иначе поды не скачают образы (ImagePullBackOff).
  2. kubeconfig со статическим Bearer-токеном. Wizard работает в контейнере и не умеет OIDC/exec-плагины (gcloud, aws-iam-authenticator, d8/kubelogin). Подготовьте kubeconfig с прямым user.token — см. Доступ к кластеру: kubeconfig ниже.
  3. Envoy Gateway — для режима exposure: gateway (по умолчанию). Он должен быть установлен в кластере заранее, иначе preflight:gateway завершится с FAIL:
    helm install envoy-gateway \
      oci://docker.io/envoyproxy/gateway-helm \
      --version v1.5.1 -n envoy-gateway-system --create-namespace
    
    Если ставите через классический Ingress — выберите exposure: ingress (Envoy не нужен, но OIDC недоступен).
  4. StorageClass — при persistence: true (production). Убедитесь, что в кластере есть default StorageClass (kubectl get storageclass), либо приготовьте имя нужного SC, чтобы указать его в мастере.
  5. 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 на хосты.
preflight:gateway   проверка Envoy Gateway / Gateway API CRD
Phase 1     — postgres, redis, nats, minio, keycloak (если auth)
Phase 1.1   — bootstrap внешних сервисов (схемы, 6 streams, 12 buckets, realm)
Phase 1.5   — genai-gateway (GatewayClass + Gateway + HTTPRoute ±OIDC)
Phase 2     — 12 продуктовых сервисов

Классический Ingress (ingress-nginx и т.п.). OIDC недоступен — сервисы открыты без логина.

Phase 1     — postgres, redis, nats, minio
Phase 1.1   — bootstrap внешних сервисов
Phase 2     — 12 продуктовых сервисов
Phase 2.5   — Ingress для UI

Точка входа не создаётся. После установки создаёте 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:

kubectl port-forward svc/ui 8080:8080 -n genai
→ http://localhost:8080  (DEV_MODE, без логина)
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 credentialsharbor-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.yaml
  • POSTGRES_PASSWORD — читается из Secret postgres-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. Для полного удаления:

kubectl delete pvc -n genai --all
kubectl delete namespace genai


Устранение неполадок

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

Phase 2 зависла

# Логи конкретного сервиса
kubectl logs -l app.kubernetes.io/name=<service> -n genai --tail=50

# Переустановить только Phase 2
docker run --rm ... install --only phase2:product -f /values.yaml