Skip to content

Установка в Kubernetes: Helm ​

Helm-чарт ставит то же, что install.sh на один сервер: образ Kvalli, PostgreSQL и том для вложений. Он для случаев, когда платформенная команда запускает приложения в кластере (OpenShift, Deckhouse, управляемый Kubernetes) и не возьмёт ВМ с Docker Compose.

Один под — так задумано ​

Kvalli работает в одном поде. Задания агентов и потоки их прогресса, счётчики ограничения запросов хранятся в процессе, а запускающийся экземпляр помечает как прерванные все задания, которые в базе числятся выполняющимися. Поэтому в чарте:

  • нет числа реплик и автоскейлера;
  • стратегия Recreate: при обновлении старый под останавливается до старта нового. Это несколько секунд простоя, а выполнявшиеся задания агентов завершатся ошибкой «Server restarted while job was running»;
  • том ReadWriteOnce — одному поду больше не нужно.

Если не хватает ресурсов, увеличьте resources, а не число реплик. Несколько реплик и хранилище S3 сознательно не поддерживаются.

Что нужно кластеру ​

  • Kubernetes 1.25+, Helm 3.
  • StorageClass по умолчанию — или persistence.storageClass и postgresql.persistence.storageClass.
  • Ingress-контроллер для доступа снаружи и TLS-сертификат (cert-manager или ваш Secret). Без Ingress (ingress.enabled: false, по умолчанию) можно поставить для первого знакомства и заходить через kubectl port-forward — см. Без Ingress.
  • Доступ к образу. Полная редакция ghcr.io/kvalli-ai/kvalli закрыта: создайте pull secret с токеном на чтение, выданным с ключом, или перенесите образ в свой реестр (ниже). Бесплатная редакция ghcr.io/kvalli-ai/kvalli-starter публичная — укажите её в image.repository.
  • Запросы 0,75 CPU и 1,5 ГиБ памяти на приложение и встроенный PostgreSQL вместе; лимиты до 3 ГиБ.

Установка ​

Чарт публикуется с каждым выпуском как OCI-артефакт рядом с образом. Текущий выпуск — 2.3.0.

bash
kubectl create namespace kvalli
kubectl -n kvalli create secret docker-registry kvalli-registry \
  --docker-server=ghcr.io --docker-username=<github-user> --docker-password=<токен на чтение>

values.yaml установки (храните его вместе с секретами, не в git):

yaml
imagePullSecrets:
  - name: kvalli-registry
secrets:
  encryptionKey: <openssl rand -hex 32>   # сохраните: им зашифрованы учётные данные
  jwtSecret: <openssl rand -hex 32>
postgresql:
  password: <openssl rand -hex 24>
admin:
  email: qa-lead@acme.com
  password: <временный, сменить после первого входа>
ingress:
  enabled: true
  className: nginx
  host: kvalli.acme.com
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
  tls:
    secretName: kvalli-tls
bash
helm registry login ghcr.io -u <github-user>      # тот же токен на чтение
helm install kvalli oci://ghcr.io/kvalli-ai/charts/kvalli --version 2.3.0 \
  -n kvalli -f values.yaml --wait --timeout 10m

Первый запуск применяет миграции и создаёт администратора, --wait возвращается, когда под готов. Войдите, смените пароль и удалите admin.password из values.yaml — последующие запуски его всё равно игнорируют.

Чарт откажется рендериться без secrets.encryptionKey, secrets.jwtSecret и пароля базы, чтобы не запускать то, что не сможет работать. ENCRYPTION_KEY он никогда не генерирует сам: ключ, сменившийся при обновлении, сделал бы нечитаемыми все сохранённые учётные данные.

Секреты из внешнего хранилища ​

Если секреты приходят из Vault, External Secrets или sealed-secrets, создайте Secret сами и укажите его в secrets.existingSecret. Каждый ключ в нём станет переменной окружения приложения. Чарт читает ENCRYPTION_KEY, JWT_SECRET, POSTGRES_PASSWORD (встроенный PostgreSQL) или DATABASE_URL (внешний) и, если есть, KVALLI_ADMIN_PASSWORD. Туда же кладите другие чувствительные настройки из переменных окружения, например KVALLI_OIDC_CLIENT_SECRET или GEMINI_API_KEY.

Свой Secret отключает Secret чарта целиком

С secrets.existingSecret чарт не создаёт свой Secret вообще — не только два ключа. Эти значения тогда молча игнорируются:

ЗначениеЧто положить в свой Secret
secrets.encryptionKey, secrets.jwtSecretENCRYPTION_KEY, JWT_SECRET
postgresql.passwordPOSTGRES_PASSWORD
database.urlDATABASE_URL
admin.passwordKVALLI_ADMIN_PASSWORD
license.keyKVALLI_LICENSE или license.existingSecret
каждый ключ secretEnvключ с тем же именем

admin.email — исключение: это обычная настройка, она по-прежнему приходит в под через ConfigMap. Поэтому установка, где email администратора указан, а пароль остался в незаполненном Secret, администратора не создаст и ничего не скажет. license.existingSecret работает независимо.

Проверки чарта пропускаются вместе с его Secret — заполнять свой Secret правильно вам.

Ограничение на символы пароля PostgreSQL остаётся. Со встроенным PostgreSQL чарт собирает DATABASE_URL, подставляя POSTGRES_PASSWORD в postgres://kvalli:$(POSTGRES_PASSWORD)@... как есть, откуда бы ни пришло значение. Для своего Secret чарт проверяет пароль на ^[A-Za-z0-9._~-]+$; ваш он проверить не может. Символы @, /, :, ? или # в пароле превратят строку подключения в другой хост, порт или базу, и процентное кодирование не поможет. Используйте только буквы, цифры и . _ ~ - — например, openssl rand -hex 24.

Внешний PostgreSQL ​

Встроенный PostgreSQL — один под с томом: этого хватит для пробы и для команды, с резервными копиями как ниже. Если платформенная команда предоставляет PostgreSQL (CloudNativePG, Zalando, облачная база), используйте его:

yaml
postgresql:
  enabled: false
database:
  url: postgres://kvalli:<пароль>@pg.acme.internal:5432/kvalli_db?sslmode=require

Нужен PostgreSQL 15 или новее.

Браузерный воркер ​

С модулем browser тарифа Pro Агент 2 проходит стенд в настоящем браузере, пока пишет автотест. Браузер работает в отдельном поде, до которого может достучаться только приложение, внутри кластера:

yaml
browser:
  enabled: true
  token: <openssl rand -hex 32>

С secrets.existingSecret положите токен в этот Secret как KVALLI_BROWSER_TOKEN. Под запрашивает 1 ГиБ и может использовать до 3 ГиБ (browser.resources); browser.sessions (по умолчанию 2) — сколько заданий агентов проходят стенд одновременно. Образ — ghcr.io/kvalli-ai/kvalli-browser, скачивается с теми же imagePullSecrets. Браузер каждого сеанса может обращаться только к адресам стенда, с которыми его открыли; все остальные запросы обрываются в браузере. Отвечает ли воркер, видно на странице «Система».

Воркер — один под, его сеансы живут в его памяти. Для пула стендов (копий тестового стенда в настройках, чтобы несколько заданий шли одновременно, каждое на своей копии) дайте хотя бы один сеанс на копию и около 1 ГиБ лимита памяти на сеанс. Если сеансов меньше, лишние задания ждут. Для трёх копий:

yaml
browser:
  enabled: true
  token: <openssl rand -hex 32>
  sessions: 3
  resources:
    requests: { cpu: "1", memory: 1Gi }
    limits: { memory: 3Gi }

Остальные настройки ​

Нечувствительные настройки из переменных окружения — под env (единый вход, лимиты, уровень логов), чувствительные — под secretEnv или в свой Secret. Лицензия — в license.key или в вашем Secret, смонтированном файлом, через license.existingSecret. Без неё Kvalli работает как Starter. Телеметрия выключена, пока не задано telemetry.enabled: true. Изменённая настройка перезапускает под при helm upgrade.

Без доступа к реестру ​

Если кластер не видит ghcr.io, перенесите образ и чарт:

bash
# там, где можно скачивать
docker pull ghcr.io/kvalli-ai/kvalli:2.3.0
docker tag ghcr.io/kvalli-ai/kvalli:2.3.0 registry.acme.internal/kvalli:2.3.0
docker save registry.acme.internal/kvalli:2.3.0 -o kvalli-2.3.0.tar
helm pull oci://ghcr.io/kvalli-ai/charts/kvalli --version 2.3.0   # kvalli-2.3.0.tgz
docker pull postgres:16-alpine && docker save postgres:16-alpine -o postgres-16.tar

# внутри: docker load, docker push во внутренний реестр, затем
helm install kvalli ./kvalli-2.3.0.tgz -n kvalli -f values.yaml \
  --set image.repository=registry.acme.internal/kvalli \
  --set postgresql.image.repository=registry.acme.internal/postgres

Обновление ​

Сначала сделайте резервную копию: миграции идут только вперёд. Затем:

bash
helm upgrade kvalli oci://ghcr.io/kvalli-ai/charts/kvalli --version 2.3.0 \
  -n kvalli -f values.yaml --wait --timeout 10m

Старый под останавливается, новый применяет миграции до того, как начнёт слушать порт (startup probe ждёт до 10 минут, startupProbe.failureThreshold), и затем принимает трафик. Если он не стал готов, helm upgrade --wait завершится ошибкой, а лог пода скажет почему.

helm rollback не откатывает схему базы

helm rollback возвращает прежний образ, но не прежнюю схему. Старая версия на мигрированной базе не поддерживается: чтобы вернуться, откатите релиз и восстановите копию, снятую перед обновлением.

Без Ingress ​

ingress.enabled: false — значение по умолчанию, и тогда вход через port-forward:

bash
kubectl -n kvalli port-forward svc/kvalli 8080:80
# http://localhost:8080/app

Это обычный http, а браузер молча выбрасывает cookie с пометкой Secure. Поэтому чарт выводит COOKIE_SECURE из ingress.enabled: выключено без Ingress, включено с ним. cookieSecure переопределяет это:

yaml
cookieSecure: "true"    # TLS завершает ваш балансировщик или mesh, а не Ingress чарта
cookieSecure: "false"   # Ingress сознательно оставлен на http (ingress.tls.enabled: false)

Оставьте пустым — решит чарт. Включает только строка "true"; любое другое непустое значение выключает. COOKIE_SECURE под env: тоже работает и важнее обоих.

Небезопасные cookie — чтобы добраться до установки, которую вы настраиваете, а не чтобы с ней работать. Настоящую установку держите за TLS.

Забыли пароль администратора ​

Новый пароль передаётся через stdin, чтобы не оказаться в командной строке:

bash
printf '%s\n' 'новый-пароль-от-12-символов' | kubectl -n kvalli exec -i deploy/kvalli -c kvalli -- node dist/admin.cjs reset-admin admin@acme.com

Аккаунт получает этот пароль и роль администратора (или создаётся), все его сеансы завершаются, сброс записывается в журнал аудита. KVALLI_ADMIN_* действуют, только пока администраторов нет совсем.

Резервные копии и восстановление ​

База встроенного PostgreSQL:

bash
kubectl -n kvalli exec kvalli-postgresql-0 -- pg_dump -U kvalli -d kvalli_db -Fc > kvalli-$(date +%F).dump

Вложения и артефакты на томе данных:

bash
kubectl -n kvalli exec deploy/kvalli -c kvalli -- tar -C /app/data -czf - . > kvalli-data-$(date +%F).tgz

Унесите оба файла из кластера и храните рядом values.yaml — прежде всего ENCRYPTION_KEY: база, восстановленная без него, содержит нечитаемые учётные данные. Если хранилище поддерживает VolumeSnapshot, снимки обоих томов сделают то же самое. Внешний PostgreSQL копируется так, как это делает его оператор.

Восстановление в существующую установку. Приложение для этого нужно остановить, а потом дождаться его: kubectl exec deploy/... нужен уже работающий под, а только что поднятый под сначала ждёт init-контейнер wait-for-postgresql, а затем startup probe, которая даёт на миграции до десяти минут.

bash
# 1. Остановить приложение. Это императивное изменение, о котором Helm не знает:
#    любой helm upgrade или rollback вернёт под. Не запускайте их до конца восстановления.
kubectl -n kvalli scale deploy/kvalli --replicas=0
kubectl -n kvalli rollout status deploy/kvalli --timeout=2m

# 2. Восстановить базу, пока к ней никто не подключён.
kubectl -n kvalli exec -i kvalli-postgresql-0 -- \
  pg_restore -U kvalli -d kvalli_db --clean --if-exists < kvalli-2026-10-05.dump

# 3. Восстановить файлы. Том данных ReadWriteOnce и смонтирован только в под
#    приложения, поэтому верните приложение и дождитесь готовности.
kubectl -n kvalli scale deploy/kvalli --replicas=1
kubectl -n kvalli rollout status deploy/kvalli --timeout=12m
kubectl -n kvalli exec -i deploy/kvalli -c kvalli -- \
  tar -C /app/data -xzf - < kvalli-data-2026-10-05.tgz

# 4. Под прочитал восстановленную базу при старте; перезапустите его,
#    чтобы не осталось ничего от состояния до прихода файлов.
kubectl -n kvalli rollout restart deploy/kvalli
kubectl -n kvalli rollout status deploy/kvalli --timeout=12m

Имена подов выше предполагают, что релиз называется kvalli.

Копируется и восстанавливается только /app/data. /app/.qa-agents в чарте — emptyDir, и в нём нет ничего, что установка могла бы потерять: встроенные навыки лежат в образе, а навыки проектов и пользовательские агенты — в базе.

Переезд с Docker Compose ​

  1. На сервере с compose: /opt/kvalli/install.sh backup. В каталоге копии будут kvalli-db.dump, kvalli-files.tgz и env.
  2. Поставьте чарт той же версии, что работает на сервере, с теми же ENCRYPTION_KEY и JWT_SECRET, что в env, и без admin.*: пользователи придут вместе с базой. Лицензии в копии нет: положите содержимое /opt/kvalli/license/license.key в license.key (или license.existingSecret) и не переносите KVALLI_LICENSE_FILE из env — такого пути в поде нет.
  3. Восстановите базу как выше из kvalli-db.dump, а файлы — с --strip-components=1 (в архиве лежит каталог data/). Под и здесь должен быть готов до exec:
    bash
    kubectl -n kvalli rollout status deploy/kvalli --timeout=12m
    kubectl -n kvalli exec -i deploy/kvalli -c kvalli -- tar -C /app/data --strip-components=1 -xzf - < kvalli-files.tgz
    kubectl -n kvalli rollout restart deploy/kvalli
  4. helm upgrade до нужной версии.

Удаление ​

helm uninstall kvalli -n kvalli удаляет приложение и оставляет оба тома: у тома данных стоит helm.sh/resource-policy: keep, а том PostgreSQL принадлежит StatefulSet. Удалите их через kubectl delete pvc, когда данные точно больше не нужны.

Проверка установки ​

bash
kubectl -n kvalli rollout status deploy/kvalli
helm test kvalli -n kvalli          # под обращается к /readyz через Service

/healthz — liveness probe, базу не трогает, поэтому сбой базы не приводит к перезапуску пода. /readyz — readiness probe, отвечает 503, пока база недоступна. Логи — JSON в stdout. Подробнее — в разделе Мониторинг и логи.

Документация описывает последний выпуск Kvalli.