Тема
Установка в 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-tlsbash
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.jwtSecret | ENCRYPTION_KEY, JWT_SECRET |
postgresql.password | POSTGRES_PASSWORD |
database.url | DATABASE_URL |
admin.password | KVALLI_ADMIN_PASSWORD |
license.key | KVALLI_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
- На сервере с compose:
/opt/kvalli/install.sh backup. В каталоге копии будутkvalli-db.dump,kvalli-files.tgzиenv. - Поставьте чарт той же версии, что работает на сервере, с теми же
ENCRYPTION_KEYиJWT_SECRET, что вenv, и безadmin.*: пользователи придут вместе с базой. Лицензии в копии нет: положите содержимое/opt/kvalli/license/license.keyвlicense.key(илиlicense.existingSecret) и не переноситеKVALLI_LICENSE_FILEизenv— такого пути в поде нет. - Восстановите базу как выше из
kvalli-db.dump, а файлы — с--strip-components=1(в архиве лежит каталогdata/). Под и здесь должен быть готов доexec:bashkubectl -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 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. Подробнее — в разделе Мониторинг и логи.