Helm chart versola разворачивает auth, central, edge и админ-консоль в кластере, который у вас уже есть. Базу данных, хранилище секретов и Prometheus он не включает: вы приносите их сами, а chart их использует. Эта страница проводит по установке и перечисляет значения chart. Для одного сервера без Kubernetes используйте Деплой на VPS; эти два пути разделяют только формат конфигурации (ни versola-cli, ни файлов секретов на базе OpenBao здесь нет).
Требования
- Kubernetes 1.28 или новее. (У самого chart
versolaтакого порога нет; требование 1.28 идёт от chart нагрузочного генератора в том же репозитории.) - cert-manager и trust-manager, установленные до chart:
pki.enabledпо умолчанию равноtrueи создаёт их ресурсыClusterIssuer,CertificateиBundle. Чтобы отказаться, задайтеpki.enabled: false. - PostgreSQL, который вы предоставляете и который доступен из кластера. Стандартная топология — отдельная база на каждый сервис: у
auth,centralиedgeсвоя база и своя история миграций, а также свои URL, пользователь и пароль (chart хранит три пароля Postgres). Одна база со схемой на сервис (?currentSchema=auth,central,edge) — простейшая топология, для небольшого кластера её достаточно. Каждый сервис при старте проверяет схему и отказывается стартовать, пока миграции не применены. - Ingress-контроллер и имя его IngressClass.
ingress.classNameобязателен (см. Ingress). Вместо Ingress можно использовать Gateway API черезingress.gatewayAPI. - Helm. Helm 4 применяет манифесты на стороне сервера, что важно для ошибки дублирующегося окружения в разделе про миграции.
- Необязательно: Stakater Reloader, чтобы перезапускать поды при изменении Secret или сертификата.
scala-cliна вашей рабочей машине для генерации конфигурации черезscripts/gen-env.scala. В кластере он не нужен.
Все образы публичны в ghcr.io/versolauth/ (versola-auth, versola-central, versola-edge, versola-tools), так что imagePullSecrets не нужны.
Получите chart
Chart не опубликован ни в Helm-репозитории, ни в OCI-реестре. Он лежит в репозитории продукта в k8s/versola. Переключитесь на тег релиза, который хотите запустить, и ставьте из этого пути:
git clone --branch <version> https://github.com/versolauth/versola.git
cd versola
Тег образа каждого компонента по умолчанию равен appVersion chart, и CI проверяет, что такой тег есть в реестре. На теге 0.7.1 chart имеет версию 0.3.0 и appVersion: "0.6.2", поэтому установка без переопределений разворачивает образы 0.6.2, а не тот релиз, который вы взяли. Задайте теги сами:
services:
auth: { image: { tag: "0.7.1" } }
central: { image: { tag: "0.7.1" } }
edge: { image: { tag: "0.7.1" } }
console:
image: { tag: "0.7.1" } # versola-tools, в котором лежит консоль
Если включаете cert-sync, так же задайте pki.certSync.image.tag.
Сгенерируйте конфигурацию
Каждый сервис читает один HOCON-файл, смонтированный в /app/config/env.conf, в котором каждый секрет — плейсхолдер ${VAR}, разрешаемый из переменных окружения. scripts/gen-env.scala генерирует эти файлы и значения за ними как единый согласованный набор; несколько значений должны совпадать у разных сервисов (CENTRAL_SECRET_KEY и CLIENT_SECRETS_SECRET общие у auth и central, а публичная половина ключа edge лежит в central).
scala-cli run scripts/gen-env.scala
На первый вопрос ответьте k8s. Скрипт остаётся интерактивным и спрашивает каждое значение. Читайте значения по умолчанию, прежде чем принимать их; эти три для кластера неверны:
| Вопрос | Не принимайте | Ответьте |
|---|---|---|
| Auth internal URL | публичный URL | Внутрикластерный адрес, например http://versola-auth:8080. |
| Postgres URL (три, по одному на сервис) | localhost | Настоящий JDBC URL базы каждого сервиса. TLS по умолчанию — sslmode=verify-full, а prod без него не стартует; смонтируйте CA-bundle через extraVolumes/extraVolumeMounts и задайте postgres.ssl-root-cert (Конфигурация). |
| Postgres password (три) | пароль-заглушка по умолчанию | Пароль, который на самом деле стоит у этой базы. |
Каждый вопрос принимает и --flag=value, так что запуск можно автоматизировать. Результат попадает в .local/env/k8s/: auth.conf, central.conf, edge.conf (с плейсхолдерами) и *.generated-secrets.env (значения), а также utils.private-key.jwk — приватный ключ клиента utils. Храните последний в надёжном месте и не загружайте его ни в один сервис; см. Секреты.
Два вида Secret
Chart использует два Secret, и они не взаимозаменяемы:
| Ключ values | Что хранит | Как доставляется |
|---|---|---|
services.<name>.config.existingSecret | Один ключ, env.conf. По одному Secret на сервис. | Смонтированный файл. |
secrets.existingSecret | Значения, на которые ссылаются плейсхолдеры ${VAR}. Один Secret на все три сервиса. | Отдельные записи secretKeyRef; каждый Deployment берёт только нужные ему ключи. |
Имена ключей не совпадают с именами переменных. Chart переводит переменную в kebab-case и добавляет к ней префикс с именем сервиса, если она с него ещё не начинается; два значения, общие для auth и central, остаются без префикса. Всего 29 ключей:
| Сервис | Ключи |
|---|---|
auth | auth-access-tokens-secret, auth-refresh-tokens-secret, auth-codes-secret, auth-sessions-secret, auth-passwords-secret, auth-conversation-cookie-secret, auth-session-cookie-secret, auth-user-agent-cookie-secret, auth-par-requests-secret, auth-dpop-nonces-secret, auth-jwt-private-key, auth-postgres-password, auth-admin-bootstrap-password |
central | central-account-resource-secret, central-resource-secret, central-jwks-json, central-edge-public-jwk, central-utility-client-public-jwk, central-postgres-password |
edge | edge-private-key, edge-key-id, edge-token-enc-key, edge-sessions-secret, edge-internal-secret, edge-dpop-nonce-salt, edge-native-blob-key, edge-postgres-password |
общие для auth и central | central-secret-key, client-secrets-secret |
Например, ACCESS_TOKENS_SECRET у auth — это auth-access-tokens-secret, AUTH_CODES_SECRET — auth-codes-secret (а не auth-auth-codes-secret), а EDGE_PUBLIC_JWK у central — central-edge-public-jwk. У каждого сервиса свой пароль Postgres. Отсутствие secrets.existingSecret ломает рендер шаблона, а недостающий ключ проявляется как неразрешённая подстановка при старте, так что обе ошибки заметны сразу. Что защищает каждый секрет — в разделе Секреты.
Создайте Secret’ы с конфигурацией из сгенерированных файлов так, чтобы значения не проходили через терминал:
kubectl create secret generic auth-config -n versola \
--from-file=env.conf=.local/env/k8s/auth.conf \
--dry-run=client -o yaml | kubectl apply -f -
Так же создайте central-config и edge-config. Общий Secret собирается так же, с одним --from-literal на ключ, а имена переменных отображаются в имена ключей по правилу выше.
secrets.restartStrategy определяет, как поды замечают изменение общего Secret:
| Значение | Поведение |
|---|---|
none (по умолчанию) | Ничего автоматически; после ротации выполните kubectl rollout restart. |
checksum | Chart добавляет аннотацию с контрольной суммой для каждого сервиса, считаемой только по ключам, которые сервис использует. Сервисному аккаунту Helm нужно право get на Secret’ы при рендере; под helm template это не работает. |
reloader | Добавляет reloader.stakater.com/auto: "true" для кластеров с Reloader. |
Запуск миграций
Chart не мигрирует. Сервисы при старте проверяют схему и отказываются стартовать, если миграций нет, а RUN_MIGRATIONS жёстко задан как "false" в шаблоне Deployment. Попытка задать его через services.<name>.extraEnv рендерит дублирующуюся запись env, которую server-side apply в Helm 4 отклоняет. Job миграций в chart отслеживается в #209.
Запускайте versola-tools migrate как собственный одноразовый Job. Он читает те же auth.conf, central.conf и edge.conf, с которых стартуют сервисы. Ему нужны два собственных Secret’а, потому что конфигурации ему нужны под своими именами, а значения — по именам переменных:
versola-migrate-config: ключиauth.conf,central.conf,edge.conf.versola-migrate-env: объединение трёх файлов*.generated-secrets.env. Инструмент разрешает документ целиком, поэтому у каждого плейсхолдера во всех трёх файлах должно быть значение, а не только в блоке Postgres.
apiVersion: batch/v1
kind: Job
metadata:
name: versola-migrate
namespace: versola
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: ghcr.io/versolauth/versola-tools:<version>
args: ["migrate"] # add "--dry-run" to report without applying
envFrom:
- secretRef:
name: versola-migrate-env
volumeMounts:
- name: config
mountPath: /opt/versola-tools/config
readOnly: true
volumes:
- name: config
secret:
secretName: versola-migrate-config
Он создаёт каждую схему, если её нет, и мигрирует сервисы независимо. --dry-run сообщает, что было бы применено, ничего не меняя. Сначала сделайте бэкап базы: миграции нельзя откатить.
Установка
helm upgrade --install versola k8s/versola --namespace versola --create-namespace \
--set services.auth.config.existingSecret=auth-config \
--set services.central.config.existingSecret=central-config \
--set services.edge.config.existingSecret=edge-config \
--set secrets.existingSecret=versola-secrets \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set 'ingress.hosts[0].host=id.example.com' \
--set 'ingress.hosts[0].routes={oidc,login,api}'
Запустите Job миграций до или сразу после, затем дождитесь подов. При первой установке auth и edge поднимаются раньше, чем central начинает отвечать, и ждут его: оба синхронизируют конфигурацию с central при старте, а порядок запуска трёх сервисов ничем не задан. Ожидающий сервис в статусе Running и 0/1 Ready и пишет в лог Still waiting for the source of cache .... Чем ожидание закончится, зависит от пула исходящего HTTP-клиента (HTTP_CLIENT_POOL_KIND, см. Конфигурацию):
- С
dynamic(иHTTP_CLIENT_POOL_MIN=0) сервис сам становится готовым, как толькоcentralзаймёт порт, иrestartCountостаётся 0. - С пулом
fixedпо умолчанию этого не происходит. Первый запрос кcentralоткрывает все соединения пула; покаcentralих отклоняет, пул хранит по неудавшемуся соединению на слот (по умолчанию 128), и каждая повторная попытка, раз в 5 секунд, тратит одно. Через две минуты сервис завершается с кодом 1, иrestartPolicy: Alwaysего перезапускает; новый процесс строит свежий пул и стартует нормально. Ожидайте один перезапуск при холодной установке.
Ожидание — две минуты и только для central, до которого нельзя достучаться; central, который отвечает и отклоняет запрос (неверный URL, неверный ключ синхронизации), приводит к ошибке старта за секунды. Переменные пула задавайте через services.<name>.extraEnv.
Справочник значений
Значения по умолчанию — из chart этого релиза. Поля размещения (nodeSelector, tolerations, affinity) есть в global и у каждого компонента; значение компонента заменяет глобальное, а не сливается с ним, и на верхнем уровне их нет.
Глобальные и rollout
| Ключ | По умолчанию | Значение |
|---|---|---|
global.imageRegistry, global.imageRepository | ghcr.io, versolauth | Откуда берутся образы. |
global.imagePullPolicy | IfNotPresent | |
global.imagePullSecrets | [] | |
preStopSleepSeconds | 5 | Откладывает SIGTERM, чтобы эндпоинты перестали маршрутизировать на завершающийся под. |
terminationGracePeriodSeconds | 30 | Должен покрывать pre-stop-паузу, 15 с graceful shutdown приложения и запас. |
diagnosticsSizeLimit | 1Gi | Размер emptyDir в /diagnostics (дамп кучи, GC-лог). Можно задать и для отдельного сервиса. |
metrics.prometheusAnnotations | true | Добавляет подам аннотации prometheus.io/scrape, port и path. |
secrets.existingSecret, secrets.restartStrategy | "", none | См. выше. |
Сервисы
Для каждого из services.auth, services.central, services.edge:
| Ключ | auth | central | edge |
|---|---|---|---|
replicaCount | 1 | 1 | 1 |
port (PORT) | 8080 | 8090 | 8095 |
diagnosticsPort (DPORT) | 8081 | 8091 | 8096 |
additionalPort (APORT) | 8082 | — | — |
mutualTlsPort (MPORT) | 8083 | — | — |
resources | request 250m CPU и 1Gi, limit 1Gi памяти | так же | так же |
Также у каждого сервиса: image.repository и image.tag, config.existingSecret и config.secretKey (env.conf), extraEnv, extraEnvFrom, а также extraVolumes и extraVolumeMounts (в values chart перечислены только у auth, но шаблон Deployment читает их для каждого сервиса). Лимит 1Gi рассчитан из настроек JVM по умолчанию: фиксированная куча 512 МиБ плюс ограниченная память вне кучи (см. Конфигурацию). Пробы используют порт диагностики: /liveness (начальная задержка 10 с, период 10 с) и /readiness (5 с, 5 с).
Доступность
Для каждого сервиса в services.<name> (распределение по узлам — также в global). Все значения по умолчанию — отправная точка, а не гарантия.
| Ключ | По умолчанию | Значение |
|---|---|---|
global.topologySpreadConstraints | мягкое распределение по kubernetes.io/hostname и topology.kubernetes.io/zone (maxSkew: 1, ScheduleAnyway) | Распределяет реплики каждого компонента по узлам и зонам. Мягкое, потому что жёсткое ограничение оставляет поды в Pending в кластере с меньшим числом узлов или зон, чем реплик. Ограничение без labelSelector получает метки самого компонента. Собственный непустой topologySpreadConstraints сервиса заменяет этот список; [] в global отключает распределение. |
services.<name>.podDisruptionBudget | enabled: true, maxUnavailable: 1 | Создаётся, только пока у сервиса больше одной реплики (replicaCount или autoscaling.minReplicas). Задавайте вместо него minAvailable (при пустом maxUnavailable), но не оба сразу. |
priorityClasses.create | false | true создаёт versola-customer-facing (значение 1000000, для auth и edge) и versola-control-plane (100000, для central), чтобы при нехватке ресурсов узла первой вытеснялась административная плоскость. По умолчанию выключено, потому что PriorityClass — cluster-scoped объект. services.<name>.priorityClassName в любом случае называет уже существующий класс. |
services.<name>.autoscaling | enabled: false; auth от 2 до 6, central от 1 до 2, edge от 2 до 6; targetCPUUtilizationPercentage: 70 | HorizontalPodAutoscaler. Когда включён, Deployment перестаёт задавать replicas. metrics заменяет метрику CPU по умолчанию списком autoscaling/v2, behavior замедляет уменьшение. |
Не масштабируйте эти сервисы по памяти: куча JVM фиксирована, поэтому память от нагрузки не зависит. CPU — слабый сигнал для auth и edge, потому что подписание RSA и хеширование Argon2id идут за семафорами (argon2.max-concurrent), и CPU упирается в то, что они пропускают, пока запросы стоят в очереди. Предпочитайте метрику типа Pods или External, например argon2_hashes_waiting, через адаптер метрик (Наблюдаемость). central — административная плоскость, и его лучше оставить с фиксированным числом реплик.
Консоль
Админ-консоль — это обычный Deployment с nginx (console.server.image, по умолчанию nginx:1.31-alpine). Init-контейнер копирует статическую сборку из образа versola-tools (console.image) в под, поэтому образа веб-сервера, специфичного для консоли, нет.
| Ключ | По умолчанию | Значение |
|---|---|---|
console.enabled | true | |
console.port | 2821 | |
console.basePath | / | Где консоль отвечает на своём хосте; / подходит для отдельного имени хоста. |
console.mode | direct | Как консоль обращается к API. direct вызывает настоящие маршруты edge на origin консоли и — единственный режим, который может обслужить Ingress chart. prefix — поведение VPS, для него нужна собственная конфигурация прокси через console.nginxConfig. |
console.nginxConfig.existingConfigMap, .content | "" | Ваша собственная конфигурация nginx. |
console.replicaCount, console.resources | 1, 100m / 128Mi | |
console.preStopSleepSeconds, console.terminationGracePeriodSeconds | 5, 20 |
Значения Ingress
| Ключ | По умолчанию | Значение |
|---|---|---|
ingress.enabled | false | |
ingress.className | "" | Обязателен при включении; см. ниже. |
ingress.annotations | {} | |
ingress.hosts | [] | Список { host, routes: [...] }. |
ingress.tls | [] | Стандартный TLS-блок Ingress, передаётся как есть. |
ingress.gatewayAPI.enabled, .parentRefs | false, [] | Создавать HTTPRoute вместо Ingress. |
PKI (mutual TLS)
| Ключ | По умолчанию | Значение |
|---|---|---|
pki.enabled | true | Нужен cert-manager. |
pki.issuerRef | {} | Использовать уже имеющийся у вас издатель вместо самоподписанного корня chart. |
pki.trustNamespace | cert-manager | Где лежит Secret с CA, чтобы trust-manager мог его прочитать. |
pki.ca | commonName: Versola Client CA, duration: 87600h, renewBefore: 8760h | Versola Client CA. |
pki.authServer | duration: 2160h, renewBefore: 720h, ECDSA 256 | Листовой сертификат mTLS-listener’а auth; secretName по умолчанию <fullname>-auth-mtls, dnsNames добавляет имена. |
pki.clients, pki.clientDefaults | [] | Сертификаты, выпущенные вне central, по одному Certificate cert-manager на клиента. |
pki.trustBundle.enabled, .sources | true, [] | Bundle trust-manager, который auth монтирует как trust anchors; sources добавляет CA, например новый при ротации. |
pki.reloader | true | Аннотации Reloader у auth и edge. |
pki.certSync | выключен | Job cert-sync, который копирует выпущенные снаружи сертификаты в central (intervalSeconds 300). |
Ingress
Chart предоставляет группы маршрутов, а не отдельные пути. Хост перечисляет группы, которые обслуживает; всё, что не перечислено, не публикуется, а группы «всё остальное» нет.
| Группа | Бэкенд | Пути |
|---|---|---|
oidc | auth | /authorize, /token, /par, /introspect, /revoke, /userinfo, /logout, /.well-known/, /challenge |
login | edge | /login/, /complete, /logout/ |
api | edge | /resources/, /permissions/ |
native | edge | /native/: то, что вызывает мобильное приложение при FAPI 2.0. Укажите её на любом хосте с native-клиентами. |
console | console | console.basePath |
users | auth | /users, /users/; обычно вместо этого обращаются по DNS кластера. |
service | auth | /service/: инструменты синхронизации конфигурации для не-prod, 404, когда env равен prod. |
settings | auth | /settings, на дополнительном порту auth. Учётные данные — секрет ресурса, а не пользовательские. |
/logout — единственное пересечение: auth владеет им точно, edge — префиксом /logout/. Группы для central нет; его административный API доступен через ресурсный прокси edge (api) или по DNS кластера изнутри кластера.
ingress.className обязателен. Шаблон падает, а не берёт IngressClass кластера по умолчанию. У стандартной установки ingress-nginx класса по умолчанию нет, поэтому Ingress, созданный без класса, не подхватывает ни один контроллер: его ADDRESS остаётся пустым, а запросы получают голый 404. Если отметить класс как default уже потом, существующий Ingress это не исправит; пересоздайте его или задайте spec.ingressClassName.
Типичное разделение — OIDC и вход на публичном хосте, консоль на своём:
ingress:
enabled: true
className: nginx
hosts:
- host: id.example.com
routes: [oidc, login]
- host: console.example.com
routes: [console, api, login]
Mutual TLS и PKI
При pki.enabled chart настраивает cert-manager для сертификатов, на которых держится mutual-TLS-listener auth на 8083 (порт контейнера и Service). Он создаёт самоподписанный корень и ClusterIssuer Versola Client CA (или использует pki.issuerRef), листовой сертификат listener’а, Bundle trust-manager, который auth монтирует как trust anchors, аннотации Reloader и сервисный аккаунт для central, которому разрешено создавать CertificateRequest cert-manager в namespace релиза и ничего больше. central использует его, чтобы выпускать и продлевать сертификаты клиентов, стоящих за edge: зарегистрируйте клиента с issueEdgeClientCertificate: true и mtlsAuth, и central запросит у издателя, названного в CLIENT_CERT_ISSUER_NAME (его задаёт chart), 14-дневный сертификат, продлеваемый при остатке 4 дня.
Chart не генерирует env.conf; направьте свой на файлы, которые он монтирует:
| Сервис | Смонтирован в | Использовать как |
|---|---|---|
auth | /app/mtls/server/tls.crt, tls.key | mutual-tls.certificate, mutual-tls.private-key |
auth | /app/mtls/trust/ca.crt | mutual-tls.trusted-certificates (trust bundle или выпускающий CA) |
edge | /app/mtls/auth-pin/tls.crt | native.trusted-certificates (pin листового сертификата; edge получает сертификат, но никогда не ключ) |
gen-env пишет блоки mutual-tls и native с этими путями. auth и edge читают файлы один раз при старте, поэтому после продления нужен перезапуск: используйте pki.reloader с Reloader или kubectl rollout restart после обновления сертификата cert-manager. Продление перезапускает оба сервиса вместе, поэтому для ротации сертификата listener’а auth без простоя опубликуйте следующий листовой сертификат вторым pin’ом на каждом edge, прежде чем auth переключится. Для ротации CA добавьте Secret нового CA в pki.trustBundle.sources рядом с текущим, перевыпустите сертификаты клиентов, затем уберите старый источник. Клиентские сертификаты и способы их получения описаны в разделе клиентские сертификаты edge.
Мониторинг
Chart не устанавливает Prometheus и не создаёт ServiceMonitor или PodMonitor. При metrics.prometheusAnnotations (по умолчанию true) каждый под несёт prometheus.io/scrape, prometheus.io/port (порт диагностики) и prometheus.io/path: /metrics, так что сборщик метрик на аннотациях (vmagent, обычный Prometheus) находит каждую реплику.
Не направляйте цели сбора на имена ClusterIP Service. Один Service стоит перед всеми репликами, поэтому instance цели остаётся неизменным, а под за ним меняется на каждом соединении: rate() занижается, а у серии нет метки pod. Используйте обнаружение подов по аннотациям. Рабочий пример — k8s/monitoring/vmagent-values.yaml в репозитории продукта. Что собирать и по каким сигналам настраивать алерты — в разделе Наблюдаемость.
Диагностика
Каждый под получает emptyDir в /diagnostics, куда JVM пишет дамп кучи при ошибке нехватки памяти и ротируемый GC-лог (diagnosticsSizeLimit). emptyDir переживает перезапуск контейнера, который вызывает OOM, поэтому дамп можно забрать через kubectl cp. См. Наблюдаемость.
Известные проблемы
| Проблема | Эффект |
|---|---|
| #380 | Секрет административного API central нельзя получить или ротировать после того, как его сгенерировал bootstrap. Задайте bootstrap.resource-secret до первого запуска. Та же проблема касается пересоздания пары ключей utils после того, как central её сохранил. |
| #209 | В chart нет Job миграций; запускайте собственный, как показано выше. |
Дальше
- Секреты: что защищают 29 ключей и какие из них нельзя пересоздавать.
- Конфигурация: HOCON-блоки за Secret’ами с конфигурацией.
- Наблюдаемость: health-пробы, метрики и логи.