From 67786a01605f0c461274293ea10e560437f25c00 Mon Sep 17 00:00:00 2001 From: Dmitrii Sashchenko Date: Sat, 5 Sep 2026 19:07:31 +0600 Subject: [PATCH] chart 3.0 --- .helm/Chart.yaml | 4 - .helm/templates/00-app-secret.yaml | 12 - .../templates/00-nanabush-player-secret.yaml | 16 - .helm/templates/00-validate.yaml | 19 - .helm/templates/01-nats-configmap.yaml | 9 - .helm/templates/01-nginx-assetlinks.yaml | 8 - .helm/templates/01-nginx-configmap.yaml | 14 - .helm/templates/01-otel-configmap.yaml | 4 - .helm/templates/01-wait-configmap.yaml | 4 - .helm/templates/02-dragonfly.yaml | 7 - .helm/templates/02-nats-box.yaml | 12 - .helm/templates/02-web-pdb.yaml | 6 - .helm/templates/02-web.yaml | 12 - .helm/templates/03-chatbox.yaml | 4 - .helm/templates/04-worker.yaml | 8 - .helm/templates/05-conductor.yaml | 6 - .helm/templates/09-file-processor-video.yaml | 9 - .helm/templates/09-file-processor.yaml | 6 - .helm/templates/09-geonames.yaml | 3 - .helm/templates/09-goworker.yaml | 7 - .helm/templates/09-kedoca.yaml | 8 - .helm/templates/09-nanabush-player.yaml | 29 -- .helm/templates/10-app-ingress.yaml | 18 - .helm/templates/98-migrate-job.yaml | 9 - .helm/templates/99-update-cluster-job.yaml | 6 - .helm/templates/_envs.tpl | 63 +-- .helm/templates/_helpers.tpl | 101 ----- .helm/templates/_resources.tpl | 13 - .helm/values.yaml | 369 ++++++++---------- CHANGELOG.md | 183 +++++---- README.md | 152 ++++---- 31 files changed, 348 insertions(+), 773 deletions(-) diff --git a/.helm/Chart.yaml b/.helm/Chart.yaml index f0d591d..c76d31c 100644 --- a/.helm/Chart.yaml +++ b/.helm/Chart.yaml @@ -3,10 +3,7 @@ name: hrbox description: HRBox — HR-платформа для on-premise установки type: application -# Версия чарта. Увеличивается при каждом изменении чарта. version: 3.0.0 - -# Версия приложения. Совпадает с линейкой образов HRBox. appVersion: "3.1.0" keywords: @@ -19,7 +16,6 @@ keywords: home: https://hrbox.io annotations: - # Компоненты, которые разворачивает чарт hrbox.io/components: >- web, worker, conductor, chatbox, goworker, kedoca, geonames, file-processor, nanabush-player, dragonfly, nats diff --git a/.helm/templates/00-app-secret.yaml b/.helm/templates/00-app-secret.yaml index a67dce4..db1865a 100644 --- a/.helm/templates/00-app-secret.yaml +++ b/.helm/templates/00-app-secret.yaml @@ -1,16 +1,4 @@ {{- if not .Values.secrets.existingSecret }} -{{/* -Единый Secret с чувствительными настройками HRBox. - -В чарте 2.x пароли и ключи подставлялись прямо в env-блоки Deployment, -поэтому любой, кто мог сделать `kubectl get deploy -o yaml`, видел пароль -базы и ключ шифрования. Здесь значения лежат в одном Secret, а контейнеры -читают их через secretKeyRef (см. hrbox.secretEnv в _helpers.tpl). - -Если секретами управляет внешний инструмент, задайте secrets.existingSecret — -тогда чарт этот объект не создаёт и только ссылается на указанный. Набор -ключей должен совпадать с перечисленным ниже. -*/}} --- apiVersion: v1 kind: Secret diff --git a/.helm/templates/00-nanabush-player-secret.yaml b/.helm/templates/00-nanabush-player-secret.yaml index 95ca43f..8ac26d1 100644 --- a/.helm/templates/00-nanabush-player-secret.yaml +++ b/.helm/templates/00-nanabush-player-secret.yaml @@ -1,21 +1,5 @@ {{- $cfg := .Values.app.nanabushPlayer.deployment }} {{- if and $cfg.enabled $cfg.existingSecret.create }} -{{/* -Ключи Nanabush Player. - -ticketHs256 читают обе стороны: HRBox подписывает им launch-тикет -(NANABUSH_PLAYER_HS256_SECRET в поде web), плеер этим же ключом тикет -проверяет (PLAYER_HS256_SECRET). Поэтому ротация — одно значение на обе -стороны, а не два независимых. - -sessionHs256 принадлежит только плееру: им подписывается сессия учащегося -внутри плеера, HRBox её не читает. - -При existingSecret.create: false этот объект не создаётся — оба потребителя -всё так же ссылаются на existingSecret.name, но Secret должен быть заведён -вне чарта. При смене владельца уже существующего Secret берите новое имя, -чтобы Helm не удалил внешний объект во время перехода. -*/}} --- apiVersion: v1 kind: Secret diff --git a/.helm/templates/00-validate.yaml b/.helm/templates/00-validate.yaml index f96904b..de11381 100644 --- a/.helm/templates/00-validate.yaml +++ b/.helm/templates/00-validate.yaml @@ -1,19 +1,3 @@ -{{/* -============================================================================ - Проверка values.yaml -============================================================================ - -Этот файл не создаёт ни одного объекта. Он выполняется первым и падает с -понятным сообщением, если конфигурация неполная или противоречивая, — до -того, как в кластере появятся поды, которые всё равно не заработают. - -Проверяется три вещи: - 1) ключи, переехавшие в версии 3.0 (подсказка вместо тихого игнора); - 2) обязательные значения, без которых приложение не поднимется; - 3) инварианты Nanabush Player. -*/}} - -{{/* --- 1. Ключи из чарта 2.x ---------------------------------------------- */}} {{- if .Values.app.image }} {{- fail "app.image переехал в image.hrbox (см. CHANGELOG, миграция 2.x -> 3.0)" }} {{- end }} @@ -42,7 +26,6 @@ {{- fail "nats.image переехал в image.nats" }} {{- end }} -{{/* --- 2. Обязательные значения ------------------------------------------- */}} {{- if not .Values.ingress.host }} {{- fail "Заполните ingress.host — домен, по которому открывается HRBox" }} {{- end }} @@ -81,7 +64,6 @@ {{- end }} {{- end }} -{{/* --- 3. Внешние зависимости ---------------------------------------------- */}} {{- if and (not .Values.dragonfly.enabled) (not .Values.app.redis.host) }} {{- fail "dragonfly.enabled: false требует app.redis.host — иначе приложение обратится к сервису, которого нет в релизе" }} {{- end }} @@ -95,7 +77,6 @@ {{- end }} {{- end }} -{{/* --- 4. Nanabush Player -------------------------------------------------- */}} {{- $player := .Values.app.nanabushPlayer }} {{- if and $player.enabled (not $player.deployment.enabled) }} {{- fail "app.nanabushPlayer.enabled требует deployment.enabled: HRBox нельзя переключить на плеер, которого нет в кластере" }} diff --git a/.helm/templates/01-nats-configmap.yaml b/.helm/templates/01-nats-configmap.yaml index 0714b2b..98841d4 100644 --- a/.helm/templates/01-nats-configmap.yaml +++ b/.helm/templates/01-nats-configmap.yaml @@ -1,12 +1,4 @@ {{- if .Values.nats.enabled }} -{{/* -Конфигурация NATS JetStream. - -Маршруты кластера строятся из служебных DNS-имён StatefulSet, поэтому домен -кластера берётся из cluster.domain — в чарте 2.x он был захардкожен здесь и -одновременно настраивался в values, из-за чего нестандартный домен ломал -сборку кластера. -*/}} {{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}} --- apiVersion: v1 @@ -17,7 +9,6 @@ metadata: {{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }} data: nats.conf: | - # PID-файл, общий с перезагрузчиком конфигурации. pid_file: "/var/run/nats/nats.pid" ############### diff --git a/.helm/templates/01-nginx-assetlinks.yaml b/.helm/templates/01-nginx-assetlinks.yaml index efb30fd..901bec7 100644 --- a/.helm/templates/01-nginx-assetlinks.yaml +++ b/.helm/templates/01-nginx-assetlinks.yaml @@ -1,11 +1,3 @@ -{{/* -Привязка мобильных приложений к домену инсталляции. - -Android читает /.well-known/assetlinks.json, iOS — /.well-known/apple-app-site-association. -Без них ссылки из писем и уведомлений открываются в браузере, а не в приложении. -По умолчанию здесь идентификаторы штатных приложений HRBox; если у вас -собственная сборка, переопределите app.mobileApps в values.yaml. -*/}} --- apiVersion: v1 kind: ConfigMap diff --git a/.helm/templates/01-nginx-configmap.yaml b/.helm/templates/01-nginx-configmap.yaml index 8e183e9..e69e5c1 100644 --- a/.helm/templates/01-nginx-configmap.yaml +++ b/.helm/templates/01-nginx-configmap.yaml @@ -1,9 +1,3 @@ -{{/* -Конфигурация nginx в поде web. Отличия от чарта 2.x — набор location, -перенесённый из продового окружения: health-check для проб, потоковая -загрузка оргструктуры из 1С, длинные таймауты для AI-запросов, отдача -assetlinks мобильных приложений и корректные заголовки для service worker. -*/}} --- apiVersion: v1 kind: ConfigMap @@ -103,13 +97,11 @@ data: deny all; } - # Liveness: отвечает сам nginx, не дожидаясь PHP-FPM. location = /healthz { access_log off; return 200 "ok\n"; } - # Readiness: короткая проверка, что PHP-FPM отвечает. location = /health-check { fastcgi_pass 127.0.0.1:9000; fastcgi_connect_timeout 1s; @@ -121,9 +113,6 @@ data: fastcgi_param PATH_INFO /health-check; } - # Загрузка оргструктуры из 1С. PHP читает тело запроса потоком и - # проксирует в GoWorker без полной буферизации, поэтому nginx - # тоже не должен буферизовать тело. location = /api/v1/integration/commit { client_max_body_size 500m; fastcgi_request_buffering off; @@ -138,7 +127,6 @@ data: fastcgi_connect_timeout 60s; } - # Привязка мобильных приложений к домену location /.well-known/assetlinks.json { default_type "application/json"; alias /var/www/assetlinks/assetlinks.json; @@ -157,7 +145,6 @@ data: access_log off; } - # AI-запросы отвечают дольше обычных, им нужен свой таймаут. location ^~ /ai/ { add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; @@ -184,7 +171,6 @@ data: add_header X-Content-Type-Options "nosniff"; add_header Permissions-Policy "browsing-topics=()" always; - # Всё, что не является файлом, уходит в index.php try_files $uri $uri/ /index.php?$args; location ~* \.(js|css|png|jpg|gif|ico)$ { diff --git a/.helm/templates/01-otel-configmap.yaml b/.helm/templates/01-otel-configmap.yaml index 675e725..7fbd607 100644 --- a/.helm/templates/01-otel-configmap.yaml +++ b/.helm/templates/01-otel-configmap.yaml @@ -1,8 +1,4 @@ {{- if .Values.app.tracing.enabled }} -{{/* -Конфигурация sidecar-коллектора OpenTelemetry в поде web. -Создаётся только при app.tracing.enabled. -*/}} --- apiVersion: v1 kind: ConfigMap diff --git a/.helm/templates/01-wait-configmap.yaml b/.helm/templates/01-wait-configmap.yaml index e04d13d..d1eff32 100644 --- a/.helm/templates/01-wait-configmap.yaml +++ b/.helm/templates/01-wait-configmap.yaml @@ -1,7 +1,3 @@ -{{/* -Скрипт init-контейнера джобы миграций: ждёт, пока PostgreSQL начнёт принимать -подключения. Без этого миграции падают, если база стартует медленнее подов. -*/}} --- apiVersion: v1 kind: ConfigMap diff --git a/.helm/templates/02-dragonfly.yaml b/.helm/templates/02-dragonfly.yaml index 930055b..cb2b2d9 100644 --- a/.helm/templates/02-dragonfly.yaml +++ b/.helm/templates/02-dragonfly.yaml @@ -1,12 +1,5 @@ {{- if .Values.dragonfly.enabled }} {{- $name := include "hrbox.component" (dict "ctx" . "name" "dragonfly") -}} -{{/* -Redis-совместимый кеш: сессии, кеш приложения и очереди Nanabush Player. - -Одна реплика и стратегия Recreate — намеренно: данные хранятся в памяти, -второй экземпляр не даёт ни отказоустойчивости, ни консистентности. -Селектор оставлен как в чарте 2.x. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/02-nats-box.yaml b/.helm/templates/02-nats-box.yaml index cea0903..a610d96 100644 --- a/.helm/templates/02-nats-box.yaml +++ b/.helm/templates/02-nats-box.yaml @@ -1,9 +1,5 @@ {{- if .Values.nats.enabled }} {{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}} -{{/* -Брокер сообщений. Селектор StatefulSet намеренно оставлен таким же, как в -чарте 2.x: он неизменяем, и его правка потребовала бы пересоздания объекта. -*/}} --- apiVersion: apps/v1 kind: StatefulSet @@ -30,8 +26,6 @@ spec: name: {{ printf "%s-config" $name }} - name: pid emptyDir: {} - # Общее пространство процессов нужно перезагрузчику конфигурации: - # он посылает сигнал процессу nats-server из соседнего контейнера. shareProcessNamespace: true terminationGracePeriodSeconds: {{ .Values.nats.terminationGracePeriodSeconds }} {{- include "hrbox.imagePullSecrets" . | nindent 6 }} @@ -63,9 +57,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - # Адрес, который нода объявляет остальным участникам кластера. - # Сегмент svc обязателен: без него имя не резолвится и ноды - # не находят друг друга. - name: CLUSTER_ADVERTISE value: $(POD_NAME).{{ $name }}.$(POD_NAMESPACE).svc.{{ .Values.cluster.domain }} {{- include "hrbox.resources" (dict "ctx" . "component" "nats") | indent 10 }} @@ -91,8 +82,6 @@ spec: initialDelaySeconds: 10 timeoutSeconds: 5 lifecycle: - # Перевод ноды в lame duck: она перестаёт принимать новые - # подключения и даёт клиентам время переподключиться к соседям. preStop: exec: command: @@ -140,7 +129,6 @@ metadata: labels: {{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }} spec: - # Headless: клиентам нужны адреса конкретных нод, а не общий VIP. clusterIP: None selector: app.kubernetes.io/name: nats diff --git a/.helm/templates/02-web-pdb.yaml b/.helm/templates/02-web-pdb.yaml index 9653c3c..2849908 100644 --- a/.helm/templates/02-web-pdb.yaml +++ b/.helm/templates/02-web-pdb.yaml @@ -1,10 +1,4 @@ {{- $replicas := .Values.app.replicas.web | int }} -{{/* -Бюджет прерываний для web. - -Имеет смысл только начиная с двух реплик: при одной minAvailable округляется -до 1 и намертво блокирует drain ноды, ничего при этом не защищая. -*/}} {{- if gt $replicas 1 }} --- apiVersion: policy/v1 diff --git a/.helm/templates/02-web.yaml b/.helm/templates/02-web.yaml index f1d97e7..7b6c0de 100644 --- a/.helm/templates/02-web.yaml +++ b/.helm/templates/02-web.yaml @@ -1,12 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "web") -}} -{{/* -Веб-под: PHP-FPM, nginx перед ним и экспортёр метрик FPM. -При app.tracing.enabled рядом поднимается коллектор OpenTelemetry. - -Аннотации checksum перезапускают поды, когда меняется конфигурация nginx или -секрет приложения: без них правка ConfigMap остаётся незамеченной до -следующего рестарта. -*/}} --- apiVersion: apps/v1 kind: Deployment @@ -56,8 +48,6 @@ spec: {{- end }} {{- include "hrbox.resources" (dict "ctx" . "component" "app") | indent 10 }} lifecycle: - # Пауза даёт ingress-контроллеру убрать под из балансировки, - # прежде чем FPM начнёт корректное завершение. preStop: exec: command: @@ -92,8 +82,6 @@ spec: - sh - '-c' - sleep 15 && /usr/sbin/nginx -s quit - # /healthz отвечает сам nginx, /health-check проверяет, - # что за ним жив PHP-FPM. livenessProbe: httpGet: path: /healthz diff --git a/.helm/templates/03-chatbox.yaml b/.helm/templates/03-chatbox.yaml index 7ba3768..fdfc41c 100644 --- a/.helm/templates/03-chatbox.yaml +++ b/.helm/templates/03-chatbox.yaml @@ -1,8 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "chatbox") -}} -{{/* -Chatbox — вебсокет-сервер чатов и real-time уведомлений. -До версии 2.0 назывался websocket; имена объектов не менялись с тех пор. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/04-worker.yaml b/.helm/templates/04-worker.yaml index a3f28fb..8901c90 100644 --- a/.helm/templates/04-worker.yaml +++ b/.helm/templates/04-worker.yaml @@ -1,12 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "worker") -}} -{{/* -Обработчик фоновых задач: конвертация файлов, рассылка почты и уведомлений, -генерация отчётов, обслуживание Nanabush Player. - -Стратегия Recreate: воркеры разбирают задачи из общей очереди, и пересечение -старых и новых подов во время выката привело бы к двойной обработке. -Длинный terminationGracePeriodSeconds даёт закончить уже взятую задачу. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/05-conductor.yaml b/.helm/templates/05-conductor.yaml index d49cb99..bf93a28 100644 --- a/.helm/templates/05-conductor.yaml +++ b/.helm/templates/05-conductor.yaml @@ -1,10 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "conductor") -}} -{{/* -Планировщик задач по расписанию. - -Строго одна реплика: два планировщика запустили бы каждую задачу дважды. -По той же причине стратегия Recreate, а не RollingUpdate. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-file-processor-video.yaml b/.helm/templates/09-file-processor-video.yaml index 3288704..7b0b7bb 100644 --- a/.helm/templates/09-file-processor-video.yaml +++ b/.helm/templates/09-file-processor-video.yaml @@ -1,14 +1,5 @@ {{- if .Values.app.videoConverter.enabled }} {{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor-video") -}} -{{/* -Конвертация видео. - -Отдельный деплоймент, потому что профиль ресурсов у него другой на порядок: -несколько ядер, гигабайты памяти и десятки гигабайт временного диска на под. -Смешивать это с обычной обработкой файлов нельзя — либо ресурсы простаивают, -либо конвертация видео вытесняет всё остальное. Через nodeSelector и -tolerations пул выносится на выделенные ноды. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-file-processor.yaml b/.helm/templates/09-file-processor.yaml index 478a6ec..772a273 100644 --- a/.helm/templates/09-file-processor.yaml +++ b/.helm/templates/09-file-processor.yaml @@ -1,10 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor") -}} -{{/* -Конвертация документов и обработка изображений. - -Конвертация видео здесь всегда выключена: она требует принципиально другого -профиля ресурсов и живёт в отдельном деплойменте 09-file-processor-video. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-geonames.yaml b/.helm/templates/09-geonames.yaml index 3341e7b..306d6a1 100644 --- a/.helm/templates/09-geonames.yaml +++ b/.helm/templates/09-geonames.yaml @@ -1,7 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "geonames") -}} -{{/* -Справочник городов, стран и часовых поясов. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-goworker.yaml b/.helm/templates/09-goworker.yaml index e94c424..8186f4a 100644 --- a/.helm/templates/09-goworker.yaml +++ b/.helm/templates/09-goworker.yaml @@ -1,11 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "goworker") -}} -{{/* -Интеграции с мессенджерами, push-уведомления, синхронизация оргструктуры -с 1С и восстановление проекций Nanabush Player. - -Метрики Prometheus отдаются на том же порту 8686 по пути /metrics, поэтому -аннотации сбора висят на Service, а не на поде. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-kedoca.yaml b/.helm/templates/09-kedoca.yaml index b1c609e..46ea5c9 100644 --- a/.helm/templates/09-kedoca.yaml +++ b/.helm/templates/09-kedoca.yaml @@ -1,12 +1,4 @@ {{- $name := include "hrbox.component" (dict "ctx" . "name" "kedoca") -}} -{{/* -Сервис электронной подписи: КЭДО, МЧД, метки времени. -Умеет ГОСТ (через КриптоПро) и обычный OpenSSL — выбор в app.kedoca.engine. - -app.kedoca.secretName подключает Secret с сертификатами удостоверяющего -центра. Без него сервис создаёт собственный корневой сертификат при первом -запуске — это годится для теста, но не для юридически значимого документооборота. -*/}} --- apiVersion: apps/v1 kind: Deployment diff --git a/.helm/templates/09-nanabush-player.yaml b/.helm/templates/09-nanabush-player.yaml index fd7bedd..6de1890 100644 --- a/.helm/templates/09-nanabush-player.yaml +++ b/.helm/templates/09-nanabush-player.yaml @@ -2,22 +2,6 @@ {{- if $cfg.enabled }} {{- $name := include "nanabushPlayer.serviceName" . -}} {{- $secretName := include "nanabushPlayer.secretName" . -}} -{{/* -Nanabush Player — проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5, -xAPI, нативные курсы, тесты и эссе. - -Как это работает: - * HRBox выпускает launch-тикет, подписанный общим ключом ticketHs256, - и отправляет браузер на https://<домен>{{ include "nanabushPlayer.basePath" . }}/start; - * плеер проверяет подпись, audience и issuer, заводит свою сессию; - * за пакетом курса и для отправки отчётов плеер ходит обратно в HRBox по - внутрикластерному адресу — только он и разрешён в allowlist; - * учебные события копятся в Redis Streams и досылаются форвардером, - поэтому короткая недоступность HRBox не теряет прогресс. - -Ограничения on-premise описаны в values.yaml рядом с app.nanabushPlayer. -Полный контракт — docs/nanabush-player-deployment.md в репозитории HRBox. -*/}} --- apiVersion: apps/v1 kind: Deployment @@ -49,9 +33,6 @@ spec: prometheus.io/path: "/metrics" {{- end }} spec: - # Плееру не нужен доступ к API Kubernetes, а исполняет он содержимое, - # загруженное пользователями, — поэтому токен не монтируется, процесс - # не root, корневая файловая система только на чтение. automountServiceAccountToken: false terminationGracePeriodSeconds: {{ $cfg.terminationGracePeriodSeconds }} {{- include "hrbox.imagePullSecrets" . | nindent 6 }} @@ -89,8 +70,6 @@ spec: {{- else }} value: "off" {{- end }} - # Путь монтирования не входит в audience: audience — это origin - # браузера, иначе сломались бы проверки на стороне клиента. - name: PLAYER_BASE_PATH value: {{ include "nanabushPlayer.basePath" . | quote }} - name: PLAYER_AUDIENCE @@ -103,16 +82,12 @@ spec: name: {{ $secretName | quote }} key: {{ required "Нужно имя ключа ticketHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.ticketHs256 | quote }} {{- with $cfg.existingSecret.keys.ticketKeys }} - # Кольцо ключей: подпись проверяется ключом, выбранным по kid из - # тикета, поэтому во время ротации работают обе версии HRBox. - name: PLAYER_TICKET_KEYS valueFrom: secretKeyRef: name: {{ $secretName | quote }} key: {{ . | quote }} {{- with $cfg.ticket.keylessAcceptedUntil }} - # Только на первую раскатку: принимать тикеты без kid от версии - # HRBox, которая ещё не знает про кольцо ключей. - name: PLAYER_TICKET_KEYLESS_UNTIL value: {{ . | quote }} {{- end }} @@ -139,8 +114,6 @@ spec: {{- end }} - name: PLAYER_REDIS_KEY_PREFIX value: {{ include "nanabushPlayer.redisKeyPrefix" . | quote }} - # Обратный канал: единственный разрешённый адрес — внутрикластерный - # HRBox. Так пакет курса не сможет заставить плеер сходить наружу. - name: PLAYER_PACKAGE_FETCH_SCHEMES value: {{ $cfg.backchannel.schemes | quote }} - name: PLAYER_PACKAGE_FETCH_ALLOWLIST @@ -222,8 +195,6 @@ spec: type: ClusterIP selector: service: {{ $name }} - # Порт метрик наружу намеренно не публикуется: содержимое курсов приходит - # с того же origin и не должно читать эксплуатационные метрики. ports: - name: http port: {{ .Values.app.nanabushPlayer.service.port }} diff --git a/.helm/templates/10-app-ingress.yaml b/.helm/templates/10-app-ingress.yaml index 9f359a0..7252fa2 100644 --- a/.helm/templates/10-app-ingress.yaml +++ b/.helm/templates/10-app-ingress.yaml @@ -1,18 +1,5 @@ {{- if .Values.ingress.enabled }} {{- $player := .Values.app.nanabushPlayer -}} -{{/* -Единая точка входа. - -Все три маршрута живут на одном домене, потому что в on-premise выделенных -поддоменов под сервисы нет: - - {{ include "nanabushPlayer.basePath" . }}/ -> плеер учебных материалов - /chatbox/ -> вебсокеты чата - / -> само приложение - -Длинные таймауты нужны вебсокетам и плееру: и то и другое держит соединение -открытым, пока пользователь работает. -*/}} --- apiVersion: networking.k8s.io/v1 kind: Ingress @@ -21,12 +8,7 @@ metadata: labels: {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} annotations: - # Аннотация класса устарела в пользу spec.ingressClassName ниже, но - # оставлена: контроллеры старше ingress-nginx 1.0 понимают только её. - # Когда заданы обе, современный контроллер использует ingressClassName. kubernetes.io/ingress.class: {{ .Values.ingress.className | quote }} - # Аннотация NGINX Inc.; ingress-nginx её игнорирует, вебсокеты у него - # работают за счёт proxy-read-timeout выше. nginx.org/websocket-services: {{ include "hrbox.component" (dict "ctx" . "name" "chatbox") | quote }} nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.maxBodySize | quote }} nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" diff --git a/.helm/templates/98-migrate-job.yaml b/.helm/templates/98-migrate-job.yaml index c822357..9d3eb6f 100644 --- a/.helm/templates/98-migrate-job.yaml +++ b/.helm/templates/98-migrate-job.yaml @@ -1,12 +1,3 @@ -{{/* -Миграции базы данных. - -Хук post-install/post-upgrade с весом 10: выполняется после того, как Helm -создал ConfigMap и Secret, но до джобы обновления кластера (вес 20). -Имя джобы содержит номер ревизии релиза, поэтому в истории видно, какой -выкат её запускал, а повторный upgrade не спотыкается об уже существующий -объект. -*/}} --- apiVersion: batch/v1 kind: Job diff --git a/.helm/templates/99-update-cluster-job.yaml b/.helm/templates/99-update-cluster-job.yaml index 9aaac8f..360d09d 100644 --- a/.helm/templates/99-update-cluster-job.yaml +++ b/.helm/templates/99-update-cluster-job.yaml @@ -1,9 +1,3 @@ -{{/* -Обновление данных кластера после миграций: пересчёт справочников, -перерегистрация фоновых задач, обновление настроек арендаторов. - -Вес 20 — строго после джобы миграций (вес 10). -*/}} --- apiVersion: batch/v1 kind: Job diff --git a/.helm/templates/_envs.tpl b/.helm/templates/_envs.tpl index f28f4d6..e694f65 100644 --- a/.helm/templates/_envs.tpl +++ b/.helm/templates/_envs.tpl @@ -1,18 +1,5 @@ -{{/* -============================================================================ - Переменные окружения HRBox -============================================================================ - -envs_all включается во все контейнеры, которым нужен конфиг приложения: -web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames -и джобы миграций. Один общий блок проще расходящихся наборов, и именно так -устроено продовое окружение. - -Чувствительные значения приходят через secretKeyRef из секрета приложения -(00-app-secret.yaml), а не открытым текстом в спеке пода. -*/}} {{- define "envs_all" }} -# --- Приложение ----------------------------------------------------------- +# Приложение - name: INSTANCE_NAME value: {{ .Values.app.instanceName | quote }} - name: DEFAULT_TENANT_HOSTNAME @@ -45,7 +32,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames {{- include "hrbox.secretEnv" (dict "ctx" . "env" "APP_ENCRYPTION_KEY" "key" "encryption-key") | nindent 0 }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "SERVICES_INTERNAL_SECRET" "key" "services-internal-secret") | nindent 0 }} -# --- Своя политика конфиденциальности ------------------------------------- +# Своя политика конфиденциальности - name: ALLOW_CUSTOM_POLICY_URLS value: {{ .Values.app.customPolicy.enabled | quote }} {{- if .Values.app.customPolicy.enabled }} @@ -60,7 +47,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames value: "" {{- end }} -# --- PostgreSQL ----------------------------------------------------------- +# PostgreSQL - name: POSTGRES_VHOST value: {{ .Values.app.postgres.host | quote }} - name: POSTGRES_PORT @@ -73,7 +60,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames value: {{ .Values.app.postgres.citus | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "POSTGRES_PASSWORD" "key" "postgres-password") | nindent 0 }} -# --- Redis / Dragonfly ---------------------------------------------------- +# Redis / Dragonfly - name: REDIS_HOST value: {{ include "hrbox.redisHost" . | quote }} - name: REDIS_PORT @@ -81,13 +68,11 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames - name: REDIS_CONNECTION value: {{ printf "tcp://%s:%s" (include "hrbox.redisHost" .) (.Values.app.redis.port | toString) | quote }} -# --- NATS ----------------------------------------------------------------- +# NATS - name: BROKER value: "nats" - name: NATS_URL value: {{ include "hrbox.natsUrl" . | quote }} -# Число реплик JetStream-стримов должно совпадать с размером кластера NATS, -# иначе стрим создаётся в одном экземпляре и теряется вместе со своей нодой. - name: NATS_STREAM_REPLICAS {{- if and .Values.nats.enabled .Values.nats.cluster.enabled }} value: {{ .Values.nats.cluster.replicas | quote }} @@ -95,7 +80,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames value: "1" {{- end }} -# --- S3 ------------------------------------------------------------------- +# S3 - name: S3_ENDPOINT value: {{ .Values.app.s3.endpoint | quote }} - name: S3_BUCKET @@ -113,7 +98,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames {{- include "hrbox.secretEnv" (dict "ctx" . "env" "S3_KEY" "key" "s3-key") | nindent 0 }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "S3_SECRET" "key" "s3-secret") | nindent 0 }} -# --- Почта ---------------------------------------------------------------- +# Почта - name: SENDER_ENV value: "PROD" - name: SENDER_DEV_EMAIL @@ -136,7 +121,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames value: {{ .Values.app.smtp.sslVerifyPeer | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "SMTP_PASSWORD" "key" "smtp-password") | nindent 0 }} -# --- Внутренние сервисы --------------------------------------------------- +# Внутренние сервисы - name: GEONAMES_URL value: {{ include "hrbox.geonamesUrl" . | quote }} - name: GOWORKER_URL @@ -160,27 +145,27 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames - name: DOCCONV_MAX_PARALLEL_TASKS value: {{ .Values.app.fileProcessor.docconvMaxParallelTasks | quote }} -# --- HRBox Hub ------------------------------------------------------------ +# HRBox Hub - name: HUB_URL value: {{ .Values.app.hub.url | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "HUB_TOKEN" "key" "hub-token") | nindent 0 }} -# --- Nanabush Player ------------------------------------------------------ +# Nanabush Player {{- include "envs_nanabush_player" . }} -# --- Чат ------------------------------------------------------------------ +# Чат - name: CHATBOX_SENTRY_DSN value: {{ .Values.app.sentry.chatboxDsn | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "CHATBOX_WRAPPING_KEY" "key" "chatbox-wrapping-key") | nindent 0 }} -# --- Видеозвонки (LiveKit) ------------------------------------------------ +# Видеозвонки (LiveKit) - name: LIVEKIT_URL value: {{ .Values.app.livekit.url | quote }} - name: LIVEKIT_API_KEY value: {{ .Values.app.livekit.apiKey | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "LIVEKIT_API_SECRET" "key" "livekit-api-secret") | nindent 0 }} -# --- AI ------------------------------------------------------------------- +# AI - name: AI_DOCS_INDEX_ENABLED value: {{ .Values.app.ai.docsIndexEnabled | quote }} - name: AI_SEMANTIC_INDEX_ENABLED @@ -191,7 +176,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames value: {{ .Values.app.ai.yandex.folderId | quote }} {{- include "hrbox.secretEnv" (dict "ctx" . "env" "YANDEX_CLOUD_AI_API_KEY" "key" "yandex-ai-api-key") | nindent 0 }} -# --- Внешние интеграции --------------------------------------------------- +# Внешние интеграции - name: TELEGRAM_API_URL value: {{ .Values.app.integrations.telegram.apiUrl | quote }} - name: AUTH_GOOGLE_ENABLED @@ -211,7 +196,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames - name: GLAGOL_API_URL value: {{ .Values.app.integrations.glagol.apiUrl | quote }} -# --- Sentry --------------------------------------------------------------- +# Sentry - name: SENTRY_DSN value: {{ .Values.app.sentry.dsn | quote }} - name: SENTRY_DSN_LAUNCHPAD @@ -223,7 +208,7 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames - name: SENTRY_RELEASE value: {{ .Values.image.hrbox | quote }} -# --- Метаданные пода ------------------------------------------------------ +# Метаданные пода - name: POD_NAME valueFrom: fieldRef: @@ -242,13 +227,6 @@ web, worker, conductor, chatbox, goworker, kedoca, file-processor, geonames fieldPath: spec.nodeName {{- end }} -{{/* -Настройки Nanabush Player на стороне HRBox. - -NANABUSH_PLAYER_AUDIENCE и NANABUSH_PLAYER_PUBLIC_ORIGIN всегда равны: -audience тикета — это origin браузера без пути, а путь монтирования едет -отдельной переменной NANABUSH_PLAYER_BASE_PATH. -*/}} {{- define "envs_nanabush_player" -}} {{- $player := .Values.app.nanabushPlayer }} - name: NANABUSH_PLAYER_ENABLED @@ -291,10 +269,6 @@ audience тикета — это origin браузера без пути, а п {{- end }} {{- end -}} -{{/* -Ключ подписи launch-тикета. Живёт только в PHP-контейнерах, которые выпускают -тикеты: nginx и вспомогательным контейнерам он не нужен. -*/}} {{- define "envs_nanabush_player_signing" -}} {{- $cfg := .Values.app.nanabushPlayer.deployment }} {{- if $cfg.enabled }} @@ -304,9 +278,6 @@ audience тикета — это origin браузера без пути, а п name: {{ include "nanabushPlayer.secretName" . | quote }} key: {{ required "Нужно имя ключа ticketHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.ticketHs256 | quote }} {{- with $cfg.existingSecret.keys.ticketKeys }} -# Кольцо ключей launch-тикета: HRBox подписывает первым ключом, Player -# принимает все. Тот же ключ секрета читает и сам Player, поэтому ротация — -# это одно значение на обе стороны. - name: NANABUSH_PLAYER_TICKET_KEYS valueFrom: secretKeyRef: @@ -316,7 +287,6 @@ audience тикета — это origin браузера без пути, а п {{- end }} {{- end -}} -{{/* Криптографические настройки сервиса электронной подписи. */}} {{- define "envs_kedoca" }} - name: ROOT_CA_PASS value: {{ .Values.app.kedoca.rootCaPass | quote }} @@ -328,7 +298,6 @@ audience тикета — это origin браузера без пути, а п value: {{ .Values.app.kedoca.pkeyopt | quote }} {{- end }} -{{/* OpenTelemetry — только когда app.tracing.enabled. */}} {{- define "envs_tracing" }} - name: OTEL_PHP_AUTOLOAD_ENABLED value: "true" diff --git a/.helm/templates/_helpers.tpl b/.helm/templates/_helpers.tpl index 642a440..2d1d224 100644 --- a/.helm/templates/_helpers.tpl +++ b/.helm/templates/_helpers.tpl @@ -1,49 +1,19 @@ -{{/* -============================================================================ - Общие хелперы чарта HRBox -============================================================================ - -Правило именования: все объекты называются "<имя чарта>-<компонент>" -(hrbox-web, hrbox-worker, ...). Имя чарта можно переопределить через -nameOverride, но по умолчанию оно равно "hrbox" — так же, как в чарте 2.x, -поэтому обновление 2.x -> 3.x не пересоздаёт объекты. -*/}} - -{{/* Базовое имя релиза: "hrbox" или nameOverride. */}} {{- define "hrbox.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} -{{/* Имя чарта с версией — для лейбла helm.sh/chart. */}} {{- define "hrbox.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} -{{/* -Полное имя объекта компонента. - -Usage: {{ include "hrbox.component" (dict "ctx" . "name" "web") }} -> hrbox-web -*/}} {{- define "hrbox.component" -}} {{- printf "%s-%s" (include "hrbox.name" .ctx) .name | trunc 63 | trimSuffix "-" -}} {{- end -}} -{{/* -Селектор пода. ВАЖНО: spec.selector в Deployment неизменяем, поэтому набор -лейблов здесь совпадает с чартом 2.x (один лейбл "service") и меняться не должен. - -Usage: {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 6 }} -*/}} {{- define "hrbox.selectorLabels" -}} service: {{ include "hrbox.component" (dict "ctx" .ctx "name" .name) }} {{- end -}} -{{/* -Лейблы объекта. Лейбл "app" сохранён из чарта 2.x — на него опираются -привычные команды вида "kubectl get pods -l app=worker". - -Usage: {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} -*/}} {{- define "hrbox.labels" -}} app: {{ .name }} app.kubernetes.io/name: {{ include "hrbox.name" .ctx }} @@ -54,11 +24,6 @@ app.kubernetes.io/version: {{ .ctx.Chart.AppVersion | quote }} helm.sh/chart: {{ include "hrbox.chart" .ctx }} {{- end -}} -{{/* -Секреты для доступа к приватному Docker-реестру. - -Usage: {{- include "hrbox.imagePullSecrets" . | nindent 6 }} -*/}} {{- define "hrbox.imagePullSecrets" -}} {{- if .Values.imagePullSecrets }} imagePullSecrets: @@ -68,11 +33,6 @@ imagePullSecrets: {{- end }} {{- end -}} -{{/* -Тег из ссылки на образ: "registry/repo:1.2.3" -> "1.2.3". -Двоеточие ищется только в последнем сегменте пути, чтобы не спутать тег -с портом реестра ("registry:5000/repo"). -*/}} {{- define "hrbox.imageTag" -}} {{- $lastSegment := splitList "/" . | last -}} {{- if contains ":" $lastSegment -}} @@ -80,27 +40,6 @@ imagePullSecrets: {{- end -}} {{- end -}} -{{/* -============================================================================ - Секрет приложения -============================================================================ - -Чувствительные значения (пароли БД и SMTP, ключи шифрования, токены) не -попадают в env-блоки Deployment открытым текстом: чарт складывает их в один -Secret, а контейнеры читают его через secretKeyRef. Полный список ключей — -в README, раздел "Секреты". - -secrets.existingSecret позволяет подставить Secret, созданный вне чарта -(например, из внешнего vault-оператора) — тогда чарт свой Secret не создаёт. -*/}} -{{/* -Адреса внутренних сервисов. - -Сервисы релиза адресуются по именам, которые создаёт этот же чарт, поэтому -пустое значение в values резолвится в имя из hrbox.component: адреса не -разъезжаются при nameOverride и их не нужно править вручную. Непустое -значение используется как есть — для сервиса за пределами релиза. -*/}} {{- define "hrbox.geonamesUrl" -}} {{- .Values.app.geonames.url | default (printf "http://%s:8181" (include "hrbox.component" (dict "ctx" . "name" "geonames"))) -}} {{- end -}} @@ -129,11 +68,6 @@ secrets.existingSecret позволяет подставить Secret, созд {{- end -}} {{- end -}} -{{/* -Одна переменная окружения из секрета приложения. - -Usage: {{ include "hrbox.secretEnv" (dict "ctx" . "env" "POSTGRES_PASSWORD" "key" "postgres-password") }} -*/}} {{- define "hrbox.secretEnv" -}} - name: {{ .env }} valueFrom: @@ -142,23 +76,6 @@ Usage: {{ include "hrbox.secretEnv" (dict "ctx" . "env" "POSTGRES_PASSWORD" "key key: {{ .key | quote }} {{- end -}} -{{/* -============================================================================ - Nanabush Player -============================================================================ - -On-premise-топология Player'а отличается от облачной: выделенного поддомена -нет, поэтому Player живёт на основном домене HRBox под путём basePath -(по умолчанию /nanabush-player). Отсюда следуют три инварианта, которые -проверяет 00-validate.yaml: - - * audience тикета = https:// (origin без пути) - * basePath = отдельная переменная, в aud не входит - * issuer тикета = https:///lms/player - -Подробности контракта — docs/nanabush-player-deployment.md в репозитории HRBox. -*/}} - {{- define "nanabushPlayer.host" -}} {{- required "Для Nanabush Player нужен непустой ingress.host" .Values.ingress.host -}} {{- end -}} @@ -167,10 +84,6 @@ On-premise-топология Player'а отличается от облачно {{- printf "https://%s" (include "nanabushPlayer.host" .) -}} {{- end -}} -{{/* -Путь монтирования Player'а на основном домене. Пустое значение и "/" запрещены: -это отдало бы Player'у корень, занятый самим HRBox. -*/}} {{- define "nanabushPlayer.basePath" -}} {{- $path := .Values.app.nanabushPlayer.basePath | default "/nanabush-player" -}} {{- $path | trimSuffix "/" -}} @@ -188,11 +101,6 @@ On-premise-топология Player'а отличается от облачно {{- .Values.app.nanabushPlayer.service.name | default (include "hrbox.component" (dict "ctx" . "name" "nanabush-player")) -}} {{- end -}} -{{/* -Хост обратного канала Player -> HRBox. Только внутрикластерное имя: по нему -Player забирает пакеты курсов и отправляет отчёты, и оно же попадает в -allowlist самого Player'а. -*/}} {{- define "nanabushPlayer.backchannelHost" -}} {{- .Values.app.nanabushPlayer.deployment.backchannel.host | default (include "hrbox.component" (dict "ctx" . "name" "web")) -}} {{- end -}} @@ -201,10 +109,6 @@ allowlist самого Player'а. {{- printf "http://%s" (include "nanabushPlayer.backchannelHost" .) -}} {{- end -}} -{{/* -Версия Player'а берётся из тега образа, а не из отдельной настройки: тикет -не должен объявлять версию, отличную от той, что реально запущена в кластере. -*/}} {{- define "nanabushPlayer.version" -}} {{- $tag := include "hrbox.imageTag" .Values.image.nanabushPlayer -}} {{- if not $tag -}} @@ -217,15 +121,10 @@ allowlist самого Player'а. {{- required "app.nanabushPlayer.deployment.existingSecret.name обязателен, когда Player включён" .Values.app.nanabushPlayer.deployment.existingSecret.name -}} {{- end -}} -{{/* URL Redis, который чарт собирает из штатных настроек app.redis. */}} {{- define "nanabushPlayer.redisUrl" -}} {{- printf "redis://%s:%s/%d" (include "hrbox.redisHost" .) (.Values.app.redis.port | toString) (.Values.app.nanabushPlayer.deployment.redis.database | int) -}} {{- end -}} -{{/* -Префикс ключей Redis. Namespace в префиксе разводит несколько релизов HRBox, -живущих на одном Dragonfly. -*/}} {{- define "nanabushPlayer.redisKeyPrefix" -}} {{- printf "hrbox:%s:%s" .Release.Namespace .Values.app.nanabushPlayer.deployment.redis.keyPrefixSuffix -}} {{- end -}} diff --git a/.helm/templates/_resources.tpl b/.helm/templates/_resources.tpl index 243d176..9d423b3 100644 --- a/.helm/templates/_resources.tpl +++ b/.helm/templates/_resources.tpl @@ -1,16 +1,3 @@ -{{/* -============================================================================ - resources: для контейнера -============================================================================ - -В чарте 2.x на каждый компонент был отдельный define из восьми одинаковых -строк. Здесь один хелпер, который читает resources.<компонент> из values.yaml. -Незаданные поля просто не рендерятся, поэтому компоненту можно указать, -например, только requests. - -Usage: - {{- include "hrbox.resources" (dict "ctx" . "component" "app") | indent 10 }} -*/}} {{- define "hrbox.resources" -}} {{- $r := index .ctx.Values.resources .component -}} {{- if $r }} diff --git a/.helm/values.yaml b/.helm/values.yaml index 3892c80..ffe8cc5 100644 --- a/.helm/values.yaml +++ b/.helm/values.yaml @@ -1,57 +1,44 @@ --- -# ============================================================================ -# HRBox — конфигурация on-premise установки -# ============================================================================ +# Конфигурация HRBox для установки в своём кластере. # -# Минимум, который нужно заполнить перед первым деплоем: +# Заполните перед первой установкой: +# ingress.host домен, по которому будет открываться HRBox +# app.defaultTenantHostname тот же домен +# app.postgres.* подключение к PostgreSQL +# app.s3.* хранилище файлов +# app.smtp.* почта +# app.hub.token токен HRBox Hub, выдают сотрудники HRBox +# secrets.* три ключа, генерируются openssl +# app.chatbox.wrappingKey ключ шифрования чатов +# app.nanabushPlayer.keys.* два ключа плеера курсов # -# ingress.host домен инсталляции -# app.defaultTenantHostname тот же домен -# app.postgres.* подключение к PostgreSQL -# app.s3.* S3-совместимое хранилище -# app.smtp.* почта -# app.hub.token токен HRBox Hub (выдаёт HRBox) -# secrets.* три сгенерированных ключа -# app.chatbox.wrappingKey ключ шифрования чатов -# app.nanabushPlayer.keys.* два ключа плеера курсов -# -# Чарт проверяет их на этапе рендера и падает с понятной ошибкой, если -# что-то не заполнено, — до того, как в кластере появятся поды. -# -# Команды генерации ключей перечислены рядом с каждым полем и целиком — -# в README, раздел «Секреты». -# ============================================================================ +# Если что-то из этого не заполнено, helm остановится и напишет, чего не +# хватает. Команды генерации ключей — в README, раздел «Секреты». -# Переопределение базового имени объектов. По умолчанию — имя чарта (hrbox), -# то есть hrbox-web, hrbox-worker и так далее. Менять на работающей установке -# нельзя: selector у Deployment неизменяем. +# Базовое имя объектов. Пусто — имя чарта: hrbox-web, hrbox-worker и т.д. +# На уже работающей установке менять нельзя. nameOverride: "" -# ============================================================================ -# Образы -# ============================================================================ -# Все образы собраны в одном месте: обновление версии сервиса — это правка -# одной строки здесь, а не поиск по шаблонам. +# --- Образы --------------------------------------------------------------- image: - # Основное приложение (PHP-FPM, nginx, worker, conductor-задачи, миграции). + # Приложение: PHP-FPM, nginx, worker, миграции. # Доступ к реестру выдают сотрудники HRBox. hrbox: "cr.yandex/crphnph9c2s776j32sjd/hrbox:latest" # Чат и вебсокеты chatbox: "cr.yandex/crphnph9c2s776j32sjd/hrbox-chatbox:5.18" - # Интеграции с мессенджерами, пуши, синхронизация оргструктуры + # Мессенджеры, пуши, синхронизация оргструктуры goworker: "cr.yandex/crphnph9c2s776j32sjd/hrbox-goworker:3.21.2" - # Электронная подпись (КЭДО, МЧД) + # Электронная подпись: КЭДО, МЧД kedoca: "cr.yandex/crphnph9c2s776j32sjd/hrbox-kedoca:1.0.6" - # Справочник городов, стран и часовых поясов + # Справочник городов, стран, часовых поясов geonames: "cr.yandex/crphnph9c2s776j32sjd/hrbox-geonames:3.3" - # Конвертация документов и обработка изображений + # Конвертация документов и изображений fileProcessor: "cr.yandex/crphnph9c2s776j32sjd/hrbox-file-processor:1.4.6" # Планировщик задач по расписанию conductor: "cr.yandex/crphnph9c2s776j32sjd/hrbox-conductor:2.2.0" - # Плеер учебных материалов (SCORM, cmi5, xAPI, нативные курсы). - # Тег обязателен и не может быть latest: из него выводится версия плеера, - # которую HRBox кладёт в launch-тикет. + # Плеер курсов. Нужен конкретный тег, latest не подходит: + # из тега берётся версия плеера. nanabushPlayer: "cr.yandex/crphnph9c2s776j32sjd/nanabush-player:1.0.11" # Инфраструктура @@ -59,48 +46,43 @@ image: nats: "nats:2.10" natsConfigReloader: "natsio/nats-server-config-reloader:0.6.2" phpFpmExporter: "cr.yandex/crpnn6fi85p3dauha6nc/php-fpm_exporter:latest" - # Используется только init-контейнером джобы миграций (ждёт готовности БД) + # Только для джобы миграций: ждёт готовности базы postgresClient: "postgres:14-alpine" - # Используется только при app.tracing.enabled + # Только при app.tracing.enabled otelCollector: "otel/opentelemetry-collector:0.109.0" pullPolicy: IfNotPresent -# Секреты доступа к приватному Docker-реестру. -# Создаются отдельно, см. README, шаг 1. +# Secret с доступом к реестру образов. Создаётся отдельно, см. README, шаг 1. imagePullSecrets: - name: regsecret -# ============================================================================ -# Приложение -# ============================================================================ +# --- Приложение ----------------------------------------------------------- app: - # Основной домен инсталляции. Должен совпадать с ingress.host. + # Домен установки. Должен совпадать с ingress.host. defaultTenantHostname: "hrbox.example.com" - # Имя инстанса — попадает в логи и метрики, помогает различать установки. + # Имя установки, видно в логах и метриках instanceName: "onprem" - # Режим одного арендатора. "1" — вся установка обслуживает одну компанию. + # "1" — установка обслуживает одну компанию singleTenant: "0" - # Frontend 2: "true" — включён (по умолчанию), "false" — старый интерфейс. + # "true" — новый интерфейс, "false" — старый front2: "true" - # Отдельный домен авторизации. Для on-premise почти всегда пусто. + # Отдельный домен авторизации. Для on-premise обычно пусто. authHost: "" - # "0" — production, "1" — отладка. В production всегда "0". + # "0" — production, "1" — отладка debug: "0" - # Принудительный https во всех сгенерированных ссылках. + # Подставлять https во всех ссылках, которые генерирует приложение forceHttps: "true" - # Количество реплик. - # web 2-10, по числу пользователей - # worker 2-5, фоновые задачи - # chatbox 2-6, real-time - # остальные обычно хватает 1-2 + # Число подов каждого сервиса. + # web — по числу пользователей, worker — по объёму фоновых задач, + # chatbox — по числу открытых вкладок, остальным обычно хватает 1-2. replicas: web: 2 worker: 2 @@ -111,43 +93,41 @@ app: fileProcessor: 1 fileProcessorVideo: 1 - # Своя политика конфиденциальности и правила использования. - # При enabled: false приложение показывает ссылки HRBox. + # Свои ссылки на политику конфиденциальности и правила использования. + # false — показываются ссылки HRBox. customPolicy: enabled: false # privacyUrl: "https://example.com/privacy" # termsUrl: "https://example.com/terms" - # --- PostgreSQL ----------------------------------------------------------- - # Требуется PostgreSQL 14+. Для отказоустойчивости настройте репликацию. + # --- PostgreSQL --------------------------------------------------------- + # Нужен PostgreSQL 14 или новее. Чарт базу не разворачивает. postgres: host: "postgresql" port: "5432" database: "hrbox" user: "hrbox" - # Пароль попадает в Secret чарта, а не в env-блок Deployment. + # Попадает в Secret, в спеке подов пароля нет password: "" - # "true", если база работает на расширении Citus (шардирование). + # "true", если база на Citus (шардирование) citus: "false" - # --- Redis / Dragonfly ---------------------------------------------------- - # Хранит сессии, кеш и очереди плеера курсов. Потеря данных не критична, - # но разлогинивает пользователей и сбрасывает незавершённые события плеера. - # - # Пусто — используется Dragonfly из этого же релиза. Заполняйте, только - # если Redis внешний (тогда обычно dragonfly.enabled: false). + # --- Redis / Dragonfly -------------------------------------------------- + # Сессии, кеш и очереди плеера курсов. + # Пусто — используется Dragonfly из этого же релиза. + # Адрес указывают, если Redis внешний (тогда обычно dragonfly.enabled: false). redis: host: "" port: "6379" - # --- NATS JetStream ------------------------------------------------------- - # Пусто — NATS из этого же релиза. + # --- NATS --------------------------------------------------------------- + # Пусто — используется NATS из этого же релиза. # Внешний кластер: nats://node1:4222,nats://node2:4222,nats://node3:4222 nats: url: "" - # --- S3 ------------------------------------------------------------------- - # Подходят AWS S3, MinIO, Yandex Object Storage, VK Cloud Storage. + # --- S3 ----------------------------------------------------------------- + # Подойдёт AWS S3, MinIO, Yandex Object Storage, VK Cloud Storage. s3: # MinIO: http://minio:9000 # Yandex: https://storage.yandexcloud.net @@ -158,12 +138,12 @@ app: secret: "" region: "us-east-1" version: "latest" - # "true" для MinIO и self-hosted S3, "false" для AWS S3 + # "true" для MinIO и своего S3, "false" для AWS S3 pathStyle: "true" multipart: "true" prefix: "" - # --- Почта ---------------------------------------------------------------- + # --- Почта -------------------------------------------------------------- smtp: host: "smtp.example.com" # 25 — без шифрования, 587 — STARTTLS, 465 — SSL/TLS @@ -177,33 +157,33 @@ app: sslAllowSelfSigned: "true" sslVerifyPeer: "false" - # --- Мониторинг ошибок ---------------------------------------------------- + # --- Sentry ------------------------------------------------------------- sentry: dsn: "" environment: "production" - # Отдельные DSN для подсистем. Пусто — используется общий dsn. + # Пусто — используется общий dsn launchpadDsn: "" conductorDsn: "" - # У этих двух общего fallback нет: пусто означает «не отправлять». + # Здесь пусто означает «не отправлять» goworkerDsn: "" chatboxDsn: "" - # --- Внутренние сервисы --------------------------------------------------- - # Пустой url означает сервис из этого же релиза: адрес собирается из имени, - # которое создаёт чарт. Заполняйте, только если вынесли сервис наружу. + # --- Внутренние сервисы ------------------------------------------------- + # Пустой url — сервис берётся из этого же релиза. + # Заполняют, если сервис вынесен наружу. geonames: url: "" goworker: url: "" - # Минимальный интервал между push-уведомлениями одному пользователю + # Не чаще одного push одному пользователю за этот интервал pushMinInterval: "5m" kedoca: url: "" # Пароль корневого сертификата, который сервис создаёт при первом запуске rootCaPass: "hrbox" - # "gost" (ГОСТ, КриптоПро) или "openssl" + # "gost" (КриптоПро) или "openssl" engine: "gost" # "gost2012_256" для ГОСТ, "rsa" для OpenSSL algorithm: "gost2012_256" @@ -211,81 +191,79 @@ app: pkeyopt: "paramset:A" # Служба меток времени tspUrl: "http://qs.cryptopro.ru/tsp/tsp.srf" - # Kubernetes Secret с сертификатами УЦ. Если задан — монтируется в /ca-cert. + # Secret с сертификатами УЦ. Если задан, монтируется в /ca-cert. secretName: "" fileProcessor: # Сколько документов конвертировать одновременно в одном поде docconvMaxParallelTasks: "1" - # Сколько изображений ресайзить одновременно (пул конвертации видео) + # Сколько изображений обрабатывать одновременно в поде конвертации видео imageResizeMaxParallel: "20" - # --- Конвертация видео ---------------------------------------------------- - # Отдельный пул подов: конвертация видео требует много CPU и диска и - # выносится на выделенные ноды, чтобы не мешать остальным задачам. + # --- Конвертация видео -------------------------------------------------- + # Отдельные поды: видео требует много CPU и диска, поэтому его выносят + # на выделенные ноды. videoConverter: enabled: false - # Внутренний адрес HRBox, по которому конвертер забирает и отдаёт файлы. - # Пусто — берётся http://<релиз>-web. + # Внутренний адрес HRBox, откуда конвертер берёт и куда отдаёт файлы. + # Пусто — http://<релиз>-web. internalUrl: "" - # Куда планировать поды конвертера. Пусто — без ограничений. + # На каких нодах запускать. Пусто — на любых. + # Пример: role: converter-dedicated nodeSelector: {} - # role: converter-dedicated - tolerations: [] + # Пример: # - key: dedicated # operator: Equal # value: video-converter # effect: NoSchedule + tolerations: [] - # --- Чат ------------------------------------------------------------------ + # --- Чат ---------------------------------------------------------------- chatbox: - # Часть ключа асимметричного шифрования чатов, 32 символа. + # Часть ключа шифрования чатов, 32 символа. # Генерация: openssl rand -hex 16 wrappingKey: "" - # --- Видеозвонки ---------------------------------------------------------- + # --- Видеозвонки -------------------------------------------------------- livekit: url: "" apiKey: "" apiSecret: "" - # --- Nanabush Player ------------------------------------------------------ - # Плеер учебных материалов: SCORM 1.2/2004, cmi5, xAPI, нативные курсы, - # тесты и эссе. Полное описание контракта — docs/nanabush-player-deployment.md - # в репозитории HRBox. + # --- Nanabush Player ---------------------------------------------------- + # Плеер учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные курсы, + # тесты и эссе. # - # В облаке плеер живёт на отдельном поддомене. В on-premise выделенного - # поддомена нет, поэтому плеер монтируется на основной домен под путём - # basePath. Практическое следствие: cookie HRBox видны содержимому курсов - # (один origin). Если для сторонних SCORM-пакетов нужна изоляция — заводите - # отдельный поддомен и обращайтесь в HRBox за облачной схемой. + # В облаке плеер стоит на отдельном поддомене, в on-premise поддомена нет, + # поэтому он живёт на основном домене по пути basePath. Домен общий, значит + # cookie HRBox доступны содержимому курсов. Если нужна изоляция сторонних + # SCORM-пакетов, обратитесь в HRBox за схемой с отдельным поддоменом. nanabushPlayer: - # Отдавать ли учебные материалы через плеер. false — старый проигрыватель. + # false — материалы открываются старым проигрывателем enabled: true - # Путь монтирования на основном домене. Пустой и "/" запрещены: корень - # занят самим HRBox. + # Путь на основном домене. Корень "/" занят самим HRBox. basePath: "/nanabush-player" - # Issuer launch-тикета. Пусто — https:///lms/player. + # Issuer launch-тикета. Пусто — https:///lms/player issuer: "" contractVersion: "v0-current" - # Ключи плеера. Оба обязательны и должны отличаться друг от друга. + # Оба ключа обязательны и должны быть разными. # Генерация каждого: openssl rand -base64 48 keys: ticketHs256: "" sessionHs256: "" - # Необязательное кольцо ключей для ротации без простоя: - # "kid1:секрет1,kid2:секрет2", текущий ключ подписи — первым. - # Секрет не должен содержать запятую (используйте base64url). + # Набор ключей для ротации без простоя, необязательный: + # "kid1:секрет1,kid2:секрет2". Первым идёт ключ, которым подписывают + # сейчас. Запятая внутри секрета недопустима, используйте base64url. ticketKeys: "" - # Доля запусков, уходящих в новый плеер. 100 — все. rollout: + # Какая доля запусков идёт в новый плеер. 100 — все. percent: 100 - # Разрешить откат на старый проигрыватель, если плеер недоступен + # Открывать материал старым проигрывателем, если плеер не ответил legacyFallback: true allowedContentTypes: - document @@ -299,7 +277,7 @@ app: - native - native_quiz - native_essay - # "*" — все арендаторы, включая будущих + # "*" — все компании, включая новые allowedTenants: - "*" @@ -308,26 +286,25 @@ app: name: "" port: 8094 - # Публиковать ли путь плеера на основном Ingress. - # Выключать имеет смысл только при аварийном откате. + # Публиковать ли путь плеера на общем Ingress. + # Выключают только при откате. ingress: enabled: true deployment: - # Создавать ли Deployment и Service плеера. + # Создавать ли Deployment и Service плеера enabled: true replicas: 1 terminationGracePeriodSeconds: 120 - # Образ плеера должен уметь работать под путём (а не только в корне - # домена). Если ставите старый образ без этой поддержки — переключите - # в false, и чарт остановит рендер с объяснением вместо неработающего - # плеера в кластере. + # Умеет ли образ плеера работать не в корне домена, а по пути. + # Со старым образом без такой поддержки поставьте false — чарт + # остановит установку вместо того, чтобы развернуть нерабочий плеер. binaryCapabilities: basePath: true - # Secret с ключами плеера. create: false — Secret создаётся вне чарта - # (например, внешним vault-оператором), чарт только ссылается на него. + # Secret с ключами плеера. + # create: false — Secret заводят вне чарта, чарт только ссылается. existingSecret: create: true name: "hrbox-nanabush-player" @@ -338,48 +315,46 @@ app: ticketKeys: "" ticket: - # Время жизни launch-тикета, секунды + # Сколько живёт launch-тикет, секунды ttlSeconds: 300 - # Время жизни учётных данных обратного канала: сессия плеера + 2 минуты - # на расхождение часов + # Сколько живут учётные данные обратного канала: сессия плеера плюс + # 2 минуты на расхождение часов credentialTtlSeconds: 28920 maxLifetime: "5m" sessionTtl: "8h" - # Только на время первой раскатки кольца ключей: до какого момента + # Нужно только при первом переходе на набор ключей: до какого момента # (RFC3339) принимать тикеты без kid. Пусто — не принимать. keylessAcceptedUntil: "" redis: - # false — URL собирается из app.redis. - # true — полный redis:// или rediss:// URL читается из внешнего - # Secret (нужно для ACL, пароля или TLS) и требует - # existingSecret.create: false. + # false — адрес собирается из app.redis. + # true — полный redis:// или rediss:// URL берётся из внешнего Secret + # (нужно для пароля, ACL или TLS), требует existingSecret.create: false urlFromExistingSecret: false database: 0 - # Итоговый префикс: hrbox:: — разводит несколько - # релизов HRBox на одном Dragonfly. + # Полный префикс: hrbox::<суффикс>. + # Разделяет несколько установок HRBox на одном Redis. keyPrefixSuffix: "nanabush-player" + # Обращения плеера обратно в HRBox: за пакетом курса и с отчётами backchannel: - # Внутрикластерный адрес HRBox. Пусто — <релиз>-web. + # Внутренний адрес HRBox. Пусто — <релиз>-web. host: "" schemes: "http" allowPrivateIps: true packageFetchMaxBytes: "5242880" - # Транспорт должен вмещать самый большой поддерживаемый медиафайл + # Должно вмещать самый большой медиафайл курса packageContentMaxBytes: "2147483648" scormPackageMaxBytes: "1073741824" - # Пусто — ограничение по числу файлов в SCORM-пакете берётся из - # умолчаний приложения (4000) + # Пусто — берётся значение приложения (4000 файлов) scormPackageMaxFiles: "" packageFetchTimeout: "15s" - # Таймаут покрывает всю передачу, включая отдачу байтов браузеру - # учащегося, поэтому упирается в скорость его канала. Двух минут - # не хватало на крупное видео. + # Считается до конца передачи файла в браузер учащегося, поэтому + # зависит от скорости его интернета packageStreamTimeout: "20m" reportTimeout: "30s" - # Очередь отправки учебных событий в HRBox. + # Очередь отправки учебных событий в HRBox forwarder: queueSize: "1000" workers: "4" @@ -398,8 +373,7 @@ app: level: info format: json - # Метрики отдаются на отдельном порту, который не публикуется наружу: - # содержимое курсов не должно читать эксплуатационные метрики. + # Метрики отдаются на отдельном порту и наружу не публикуются metrics: enabled: true port: 9090 @@ -418,22 +392,22 @@ app: timeoutSeconds: 2 failureThreshold: 3 - # --- Фоновые задачи ------------------------------------------------------- - # Формат: "топик:количество-воркеров" + # --- Фоновые задачи ----------------------------------------------------- + # Формат: "очередь:сколько воркеров" launchpad: topics: "launcher:1,file-processor:1,image-processor:1,send-mail:1,send-im:1,send-bell:1,send-push:1,backup:1,cleaning:1" sms: senderName: "HRBOX" - # --- Внешние интеграции --------------------------------------------------- + # --- Внешние интеграции ------------------------------------------------- integrations: telegram: - # Прямой API Telegram; для установок без доступа к нему укажите прокси + # Если прямого доступа к Telegram нет, укажите адрес прокси apiUrl: "https://api.telegram.org/bot" google: enabled: "false" - # Client ID публичный (уходит в браузер), secret — в Secret чарта + # clientId уходит в браузер, clientSecret попадает в Secret clientId: "692956564858-ccapt35khk7tmadl17b3879ht2cropp6.apps.googleusercontent.com" clientSecret: "" glagol: @@ -441,8 +415,8 @@ app: apiUrl: "https://api.glagol.online/glagol-api" # Привязка мобильных приложений к домену: без неё ссылки из писем и пушей - # открываются в браузере вместо приложения. По умолчанию — штатные - # приложения HRBox; меняйте, только если у вас собственная сборка. + # открываются в браузере, а не в приложении. По умолчанию указаны + # приложения HRBox, меняйте только для своей сборки. mobileApps: android: - packageName: "io.hrbox.hrboxapp" @@ -461,13 +435,13 @@ app: clientSecret: "" verificationToken: "" - # --- HRBox Hub ------------------------------------------------------------ + # --- HRBox Hub ---------------------------------------------------------- # Лицензирование и общие сервисы. Токен выдают сотрудники HRBox. hub: url: "https://hub.hrbox.io" token: "" - # --- AI ------------------------------------------------------------------- + # --- AI ----------------------------------------------------------------- ai: docsIndexEnabled: "true" semanticIndexEnabled: "true" @@ -476,82 +450,74 @@ app: folderId: "" apiKey: "" - # --- Трассировка ---------------------------------------------------------- - # Включает OpenTelemetry в PHP и sidecar-коллектор в поде web. + # --- Трассировка -------------------------------------------------------- + # Включает OpenTelemetry в PHP и коллектор рядом с подом web tracing: enabled: false exporterUrl: "http://localhost:4318" - # Куда коллектор пересылает трейсы (Tempo, Jaeger) + # Куда коллектор отправляет трейсы: Tempo, Jaeger tempoUrl: "" - # --- Безопасность --------------------------------------------------------- + # --- Безопасность ------------------------------------------------------- security: - # "true" разрешает встроенному iframe полный доступ к родительскому окну. - # Для production рекомендуется "false". + # "true" даёт встроенному iframe полный доступ к родительскому окну. + # Для production лучше "false". iframeFullAccess: "false" -# ============================================================================ -# Секреты приложения -# ============================================================================ +# --- Секреты -------------------------------------------------------------- # Эти значения вместе с паролями из app.* складываются в один Kubernetes -# Secret, а контейнеры читают его через secretKeyRef. +# Secret, поды читают их оттуда. secrets: - # Имя Secret, созданного вне чарта. Если задано — чарт свой Secret не - # создаёт и берёт все ключи из указанного. Список ключей — в README. + # Имя Secret, созданного вне чарта. Если задано, чарт свой Secret не + # создаёт и берёт ключи из указанного. Список ключей — в README. existingSecret: "" # Защита cookie от подделки, 32 символа. # Генерация: openssl rand -hex 16 cookieValidationKey: "" - # Шифрование данных: пароли интеграций (S3, ADFS и другие). - # ВАЖНО: без этого ключа данные не расшифровать. Не теряйте его. + # Шифрование паролей интеграций в базе. + # Без этого ключа данные не расшифровать, храните копию отдельно. # Генерация: openssl rand -base64 48 encryptionKey: "" - # Аутентификация вызовов между микросервисами релиза. + # Проверка вызовов между сервисами внутри релиза. # Генерация: openssl rand -base64 48 servicesInternalSecret: "" -# ============================================================================ -# Ingress -# ============================================================================ +# --- Ingress -------------------------------------------------------------- ingress: enabled: true # Обычно "nginx" или "traefik" className: "nginx" - # Домен инсталляции. Wildcard (*.hrbox.company.com) — для мультиарендности. + # Домен установки. Для мультиарендности можно wildcard: *.hrbox.company.com host: "hrbox.example.com" # Дополнительные аннотации ingress-контроллера annotations: {} - # Максимальный размер загружаемого файла на общих путях. - # Загрузка оргструктуры из 1С идёт потоком и имеет собственный лимит. + # Максимальный размер загружаемого файла. + # У загрузки оргструктуры из 1С отдельный лимит, он выше. maxBodySize: "10m" tls: enabled: true # kubectl create secret tls hrbox-tls --cert=cert.pem --key=key.pem - # либо выпуск через cert-manager + # или выпустить сертификат через cert-manager secretName: "hrbox-tls" -# ============================================================================ -# Кластер -# ============================================================================ +# --- Кластер -------------------------------------------------------------- cluster: - # Домен служебных имён Kubernetes. Меняйте, только если он нестандартный. + # Домен служебных имён Kubernetes. Менять только если он нестандартный. domain: "cluster.local" -# ============================================================================ -# Ресурсы -# ============================================================================ -# Значения близки к продовым. Незаданные поля не рендерятся, поэтому -# компоненту можно оставить только requests. +# --- Ресурсы -------------------------------------------------------------- +# Значения близки к тем, что используются в облаке HRBox. +# Можно оставить только requests: незаполненные поля в манифест не попадают. resources: - # PHP-FPM: HTTP-запросы, API, веб-интерфейс + # PHP-FPM: веб-интерфейс и API app: requests: cpu: "1" @@ -562,7 +528,7 @@ resources: memory: "2Gi" ephemeralStorage: "2Gi" - # nginx в поде web: проксирует запросы в PHP-FPM + # nginx в поде web nginx: requests: cpu: "0.1" @@ -573,7 +539,7 @@ resources: memory: "256Mi" ephemeralStorage: "512Mi" - # Фоновые задачи: конвертация файлов, почта, отчёты + # Фоновые задачи: файлы, почта, отчёты worker: requests: cpu: "0.6" @@ -645,7 +611,7 @@ resources: memory: "1Gi" ephemeralStorage: "2Gi" - # Конвертация видео: тяжёлый профиль, отдельные ноды + # Конвертация видео: тяжёлые поды на отдельных нодах fileProcessorVideo: requests: cpu: "4" @@ -679,7 +645,7 @@ resources: cpu: "1" memory: "2Gi" - # Разовые задачи: миграции, обновление кластера + # Разовые задачи: миграции и обновление кластера jobs: requests: cpu: "0.5" @@ -690,31 +656,29 @@ resources: memory: "4Gi" ephemeralStorage: "256Mi" -# ============================================================================ -# NATS JetStream -# ============================================================================ +# --- NATS ----------------------------------------------------------------- nats: - # Обязателен для работы HRBox. Выключайте только если ставите NATS отдельно - # и указали его адрес в app.nats.url. + # HRBox без брокера не работает. Выключают, только если NATS ставят + # отдельно и указали его адрес в app.nats.url. enabled: true - # Для отказоустойчивости — 3 или 5. Должно совпадать с cluster.replicas. + # Для отказоустойчивости 3 или 5. Должно совпадать с cluster.replicas. replicas: 3 jetstream: enabled: true - # Хранение в памяти — для небольших установок + # Хранение в памяти: подходит небольшим установкам memStorage: enabled: true size: "2Gi" - # Хранение на диске — для production + # Хранение на диске: для production fileStorage: enabled: false size: "4Gi" storageDirectory: "/data" - # Для JetStream нужен быстрый диск + # JetStream нужен быстрый диск storageClassName: "fast-ssd" cluster: @@ -722,17 +686,16 @@ nats: # Должно совпадать с nats.replicas replicas: 3 - # Автоматическая перезагрузка конфигурации без рестарта сервера + # Подхватывает изменения конфигурации без перезапуска NATS reloader: enabled: true terminationGracePeriodSeconds: 120 -# ============================================================================ -# Dragonfly (Redis-совместимый кеш) -# ============================================================================ +# --- Dragonfly ------------------------------------------------------------ +# Redis-совместимый кеш. dragonfly: - # false — если Redis разворачивается отдельно; тогда укажите его в app.redis. + # false — если Redis ставят отдельно; тогда заполните app.redis.host enabled: true - # Ограничение памяти. Раскомментируйте при необходимости. + # Ограничение памяти # maxmemory: "256M" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eed1b3..5755dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,63 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.0.0] - 2026-09-02 -Синхронизация с продовым окружением HRBox и приведение чарта в порядок: -добавлен плеер учебных материалов, секреты перестали лежать открытым текстом -в спеках подов, вместо девяти одинаковых блоков ресурсов — один хелпер, -а неполная конфигурация теперь останавливает установку с внятным текстом. +Чарт подтянут к тому, как HRBox работает в облаке: добавлен плеер учебных +материалов, пароли и ключи уехали в Secret, повторяющиеся куски шаблонов +заменены одним хелпером, а неполная конфигурация теперь останавливает +установку с понятным сообщением. ### Added #### Nanabush Player -Проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные -курсы, тесты и эссе. +Плеер учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные курсы, +тесты и эссе. - `09-nanabush-player.yaml` — Deployment и Service, образ `nanabush-player:1.0.11` - `00-nanabush-player-secret.yaml` — ключи launch-тикета и сессии -- маршрут `/nanabush-player` на основном Ingress -- настройки: `app.nanabushPlayer` в values.yaml, ресурсы — `resources.nanabushPlayer` +- маршрут `/nanabush-player` на общем Ingress +- настройки `app.nanabushPlayer`, ресурсы `resources.nanabushPlayer` - переменные `NANABUSH_PLAYER_*` в HRBox и `PLAYER_*` в самом плеере -On-premise-топология отличается от облачной: выделенного поддомена нет, -плеер монтируется на основной домен под путём. Отсюда два ограничения, -описанные в README: нет изоляции cookie между HRBox и содержимым курсов, -и нужны синхронизированные часы (тикет живёт 5 минут). +В on-premise выделенного поддомена нет, поэтому плеер стоит на основном +домене по пути. Отсюда два ограничения, они описаны в README: cookie HRBox +доступны содержимому курсов, и нужны синхронизированные часы — тикет живёт +5 минут. -Три независимых переключателя (`enabled`, `ingress.enabled`, -`deployment.enabled`) сохранены для аварийного отката; чарт не даёт оставить -маршрут без пода или раскатку без маршрута. +Для быстрого отката оставлены три переключателя: `enabled`, +`ingress.enabled`, `deployment.enabled`. Маршрут без пода или включённый +плеер без маршрута чарт не пропустит. #### Проверка конфигурации -- `00-validate.yaml` — объектов не создаёт, но останавливает рендер, если - не заполнены обязательные значения, использованы ключи из версии 2.x или - нарушены инварианты плеера. Сообщения на русском, с указанием, что править. +- `00-validate.yaml` — объектов не создаёт, но останавливает установку, если + не заполнены обязательные значения, остались ключи из версии 2.x или + нарушены требования плеера. Сообщения на русском, с указанием, что править. -#### Секрет приложения +#### Secret приложения -- `00-app-secret.yaml` — единый Secret с паролями и ключами. Контейнеры +- `00-app-secret.yaml` — один Secret со всеми паролями и ключами. Поды читают его через `secretKeyRef`, поэтому `kubectl get deploy -o yaml` больше не показывает пароль базы и ключ шифрования. -- `secrets.existingSecret` — подстановка Secret, созданного вне чарта. +- `secrets.existingSecret` — можно подставить Secret, созданный вне чарта. -#### Новые компоненты и возможности +#### Новые компоненты -- `09-file-processor-video.yaml` — отдельный пул конвертации видео с - собственным профилем ресурсов, `nodeSelector` и `tolerations` - (`app.videoConverter.enabled`, по умолчанию выключен) -- `02-web-pdb.yaml` — PodDisruptionBudget для `web`, создаётся начиная с - двух реплик -- `01-otel-configmap.yaml` и sidecar-коллектор в поде `web` +- `09-file-processor-video.yaml` — отдельные поды для конвертации видео, + со своими ресурсами, `nodeSelector` и `tolerations` + (`app.videoConverter.enabled`, по умолчанию выключено) +- `02-web-pdb.yaml` — PodDisruptionBudget для `web`, создаётся от двух реплик +- `01-otel-configmap.yaml` и коллектор OpenTelemetry рядом с `web` (`app.tracing.enabled`) - `01-nginx-assetlinks.yaml` — привязка мобильных приложений к домену (`app.mobileApps`) - `templates/NOTES.txt` — что делать после установки, с готовыми командами - и предупреждениями о небезопасных настройках #### Новые переменные окружения -- `NATS_STREAM_REPLICAS` — число реплик JetStream-стримов по размеру кластера +- `NATS_STREAM_REPLICAS` — число реплик стримов JetStream по размеру кластера - `TELEGRAM_API_URL`, `AUTH_GOOGLE_ENABLED` - `LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` — видеозвонки - `APP_INTERNAL_URL` — внутренний адрес HRBox для конвертера @@ -72,52 +70,53 @@ On-premise-топология отличается от облачной: выд ### Changed -#### Структура values.yaml +#### values.yaml -- все образы собраны в блок `image` — обновление версии сервиса стало - правкой одной строки -- `imagePullSecrets` вынесен в настройки, `regsecret` больше не захардкожен +- все образы собраны в блок `image`: сменить версию сервиса — это правка + одной строки +- `imagePullSecrets` вынесен в настройки, `regsecret` больше не зашит + в шаблоны - ключи приведены к camelCase: `file_processor` → `fileProcessor`, `custom_policy` → `customPolicy` -- `resources` дополнен компонентами `fileProcessor`, `fileProcessorVideo`, +- в `resources` добавлены `fileProcessor`, `fileProcessorVideo`, `nanabushPlayer`, `dragonfly`, `nats` - адреса внутренних сервисов (`app.geonames.url`, `app.goworker.url`, - `app.kedoca.url`, `app.redis.host`, `app.nats.url`) по умолчанию пустые и - выводятся из имён, которые создаёт сам чарт. Раньше это были захардкоженные - строки `hrbox-*`, которые молча ломались при `nameOverride`. Явно заданное - значение по-прежнему используется как есть, поэтому старые values работают - без правок. Выключение `dragonfly` или `nats` без указания внешнего адреса - теперь останавливает установку + `app.kedoca.url`, `app.redis.host`, `app.nats.url`) по умолчанию пустые + и собираются из имён, которые создаёт сам чарт. Раньше это были строки + `hrbox-*`, зашитые в шаблоны: при `nameOverride` они молча переставали + работать. Заданное значение по-прежнему используется как есть, поэтому + старые values работают без правок. Выключить `dragonfly` или `nats`, + не указав внешний адрес, теперь нельзя. #### Шаблоны - `_resources.tpl`: девять почти одинаковых define заменены одним - `hrbox.resources`; незаданные поля не рендерятся -- `_helpers.tpl`: убрано около сотни строк мёртвого кода (хелперы `nats.*`, - которыми ничего не пользовалось и которые ссылались на несуществующий - `nats.k8sClusterDomain`); добавлены `hrbox.component`, `hrbox.labels`, - `hrbox.selectorLabels`, `hrbox.secretEnv` и хелперы плеера + `hrbox.resources`, незаполненные поля не рендерятся +- `_helpers.tpl`: удалено около сотни строк неиспользуемого кода (хелперы + `nats.*`, которые ссылались на несуществующий `nats.k8sClusterDomain`); + добавлены `hrbox.component`, `hrbox.labels`, `hrbox.selectorLabels`, + `hrbox.secretEnv` и хелперы плеера - у всех объектов появились стандартные лейблы `app.kubernetes.io/*`; - лейбл `app` сохранён, привычные `kubectl get pods -l app=worker` работают -- селекторы Deployment не менялись — обновление 2.x → 3.0 не пересоздаёт + лейбл `app` остался, привычный `kubectl get pods -l app=worker` работает +- селекторы Deployment не менялись, обновление 2.x → 3.0 не пересоздаёт объекты -- аннотации `checksum/*` перезапускают поды при изменении конфигурации nginx - или секрета приложения +- аннотации `checksum/*` перезапускают поды при изменении конфигурации + nginx или Secret приложения - образы, порты и таймауты вынесены из шаблонов в values.yaml #### nginx -Набор `location` синхронизирован с продовым: +Набор `location` синхронизирован с облачным: -- `/healthz` и `/health-check` — под них настроены пробы пода `web` +- `/healthz` и `/health-check` — на них настроены пробы пода `web` (раньше проверялся только TCP-порт) -- `/api/v1/integration/commit` — потоковая загрузка оргструктуры из 1С без - буферизации тела, лимит 500 МБ +- `/api/v1/integration/commit` — потоковая загрузка оргструктуры из 1С + без буферизации, лимит 500 МБ - `/ai/` — увеличенные таймауты - `/.well-known/assetlinks.json` и `/.well-known/apple-app-site-association` -- `/frontend/sw.js` — корректные заголовки service worker +- `/frontend/sw.js` — правильные заголовки service worker - кеширование шрифтов -- убран TLSv1 и TLSv1.1 +- убраны TLSv1 и TLSv1.1 #### Версии образов @@ -132,56 +131,56 @@ On-premise-топология отличается от облачной: выд #### Джобы -- имя содержит номер ревизии релиза: в `kubectl get jobs` видно, какой выкат - запускал миграции, а повторный `upgrade` не спотыкается об уже существующий +- в имени появился номер ревизии релиза: в `kubectl get jobs` видно, какой + выкат запускал миграции, а повторный `upgrade` не спотыкается о старый объект -- `ttlSecondsAfterFinished` — час у обеих джоб (было 100 секунд у миграций и - ничего у обновления кластера): логи упавшей джобы успевают дожить до того, - как их посмотрят +- `ttlSecondsAfterFinished` — час у обеих джоб (было 100 секунд у миграций + и ничего у обновления кластера), логи упавшей джобы успевают дожить до + того, как их посмотрят #### Переименования файлов -- `03-websocket.yaml` → `03-chatbox.yaml` (содержимое было chatbox с версии 2.0) +- `03-websocket.yaml` → `03-chatbox.yaml` (внутри был chatbox с версии 2.0) - `09-geonames.yml` → `09-geonames.yaml` - `99-update-cluser-job.yaml` → `99-update-cluster-job.yaml` ### Fixed -- **Общий секрет в коде чарта.** `SERVICES_INTERNAL_SECRET` имел захардкоженное - значение по умолчанию, то есть все установки, где его не заполнили, работали +- **Общий секрет у всех установок.** У `SERVICES_INTERNAL_SECRET` было + значение по умолчанию, зашитое в чарт: все, кто его не заполнил, работали на одном и том же секрете межсервисной аутентификации. Теперь значение - обязательно. -- **Домен кластера игнорировался.** Маршруты кластера NATS были захардкожены - на `cluster.local`, хотя `cluster.domain` присутствовал в values и - использовался в соседней переменной. На кластере с нестандартным доменом - ноды не находили друг друга. -- **`CLUSTER_ADVERTISE` без сегмента `svc`.** Имя не резолвилось, и ноды NATS - объявляли соседям недостижимый адрес. -- **Рассинхрон реплик NATS.** `nats.replicas` и `nats.cluster.replicas` - задавались независимо; при расхождении часть нод не входила в кластер. - Теперь чарт требует их совпадения. + обязательное. +- **`cluster.domain` не использовался.** Маршруты кластера NATS были зашиты + на `cluster.local`, хотя параметр в values был и применялся в соседней + переменной. На кластере с другим доменом ноды не находили друг друга. +- **`CLUSTER_ADVERTISE` без сегмента `svc`.** Имя не резолвилось, ноды NATS + сообщали соседям недостижимый адрес. +- **Разные значения реплик NATS.** `nats.replicas` и `nats.cluster.replicas` + задавались независимо, при расхождении часть нод не входила в кластер. + Теперь чарт требует, чтобы они совпадали. - **Пробы `web` не проверяли приложение.** Проверялся TCP-порт 9000, поэтому - под с неработающим PHP считался готовым. Теперь readiness ходит в - `/health-check` через FPM. + под с неработающим PHP считался готовым. Теперь readiness ходит + в `/health-check` через FPM. - Пароли и ключи больше не видны в спеках Deployment. -- `imagePullPolicy` у части сервисов был `Always` на закреплённых тегах — +- У части сервисов `imagePullPolicy` был `Always` на фиксированных тегах — лишние обращения к реестру при каждом рестарте пода. ### Removed -- `secrets` больше не имеет значений по умолчанию — пустые ключи - останавливают установку вместо тихого запуска на предсказуемых секретах -- убраны неиспользуемые хелперы `nats.name`, `nats.fullname`, `nats.chart`, +- у `secrets` больше нет значений по умолчанию: с пустыми ключами установка + останавливается, а не запускается на предсказуемых секретах +- удалены неиспользуемые хелперы `nats.name`, `nats.fullname`, `nats.chart`, `nats.labels`, `nats.selectorLabels`, `nats.clusterAdvertise`, - `nats.clusterRoutes`, `nats.extraRoutes`, `nats.tlsConfig`, `tplvalues.render` -- `SENDGRID_API_KEY` — не используется в on-premise + `nats.clusterRoutes`, `nats.extraRoutes`, `nats.tlsConfig`, + `tplvalues.render` +- `SENDGRID_API_KEY` — в on-premise не используется ### Migration Guide #### Переход с 2.x на 3.0 -Все переехавшие ключи чарт распознаёт и сообщает, куда именно они переехали, -поэтому надёжнее всего запустить `helm template` и править по сообщениям. +Все переехавшие ключи чарт узнаёт и пишет, куда именно они переехали, +поэтому проще всего запустить `helm template` и править по сообщениям. **Шаг 1. Перенесите образы в блок `image`** @@ -225,20 +224,20 @@ app: | `app.chatbox.sentryDsn` | `app.sentry.chatboxDsn` | | `nats.image` | `image.nats` | -**Шаг 3. Заполните ключи плеера** +**Шаг 3. Сгенерируйте ключи плеера** ```bash openssl rand -base64 48 # app.nanabushPlayer.keys.ticketHs256 openssl rand -base64 48 # app.nanabushPlayer.keys.sessionHs256 ``` -Ключи должны отличаться друг от друга. +Ключи должны быть разными. **Шаг 4. Проверьте `secrets.servicesInternalSecret`** Если поле было пустым, установка работала на общем значении по умолчанию. Сгенерируйте своё: `openssl rand -base64 48`. После обновления перезапустятся -все сервисы, обменивающиеся внутренними вызовами, — это ожидаемо. +все сервисы, которые ходят друг к другу, — это нормально. **Шаг 5. Проверьте рендер и обновитесь** @@ -249,21 +248,21 @@ helm upgrade hrbox .helm -n hrbox -f values.yaml **Шаг 6. Включите плеер** -Порядок первого включения и проверки — в README, раздел «Nanabush Player». +Порядок включения и проверки — в README, раздел «Nanabush Player». #### Breaking Changes 1. **Ключи `values.yaml` переехали.** Установка останавливается с подсказкой, молча ничего не игнорируется. -2. **`secrets.*` стали обязательными.** Установка без них не пройдёт. +2. **`secrets.*` стали обязательными.** Без них установка не пройдёт. 3. **Появился Nanabush Player.** Это дополнительный под, Service, Secret и - маршрут; учтите ресурсы кластера. Плеер требует синхронизированного времени + маршрут, учтите ресурсы кластера. Плееру нужно синхронизированное время на нодах. 4. **Пробы `web` стали строже.** Под, где PHP-FPM не отвечает по HTTP, теперь не проходит readiness. Если приложение отдаёт `/health-check` иначе, проверьте это до обновления. -5. **`app.replicas` расширился** полями `goworker`, `kedoca`, `fileProcessor`, - `fileProcessorVideo`; соответствующие `replicas` внутри сервисов удалены. +5. **В `app.replicas` добавились** `goworker`, `kedoca`, `fileProcessor`, + `fileProcessorVideo`; поля `replicas` внутри самих сервисов удалены. --- diff --git a/README.md b/README.md index 8de1465..35b3fbf 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # HRBox Helm Chart -Helm-чарт для развёртывания HRBox в собственном Kubernetes-кластере. +Чарт для установки HRBox в свой Kubernetes-кластер. -Чарт лежит в каталоге [`.helm`](.helm), настройки — в [`.helm/values.yaml`](.helm/values.yaml). -Каждый параметр там прокомментирован; ниже — только то, что нужно сделать руками. +Сам чарт лежит в [`.helm`](.helm), настройки — в [`.helm/values.yaml`](.helm/values.yaml). +Каждый параметр там подписан, ниже — только то, что нужно сделать руками. --- @@ -11,32 +11,32 @@ Helm-чарт для развёртывания HRBox в собственном | Компонент | Что делает | Порт | |---|---|---| -| `web` | PHP-FPM + nginx: веб-интерфейс и API | 80 | +| `web` | PHP-FPM и nginx: веб-интерфейс и API | 80 | | `worker` | фоновые задачи: файлы, почта, отчёты | — | -| `conductor` | планировщик задач по расписанию (строго 1 реплика) | 8080 | -| `chatbox` | вебсокеты чата и real-time уведомлений | 80 → 9797 | -| `nanabush-player` | проигрыватель курсов: SCORM, cmi5, xAPI, нативные материалы | 8094 | -| `goworker` | мессенджеры, push, синхронизация оргструктуры с 1С | 8686 | +| `conductor` | задачи по расписанию, всегда 1 под | 8080 | +| `chatbox` | вебсокеты чата и уведомлений | 80 → 9797 | +| `nanabush-player` | плеер курсов: SCORM, cmi5, xAPI, нативные материалы | 8094 | +| `goworker` | мессенджеры, пуши, синхронизация оргструктуры с 1С | 8686 | | `kedoca` | электронная подпись: КЭДО, МЧД | 8558 | | `geonames` | справочник городов, стран, часовых поясов | 8181 | | `file-processor` | конвертация документов и изображений | — | -| `file-processor-video` | конвертация видео, выключена по умолчанию | — | +| `file-processor-video` | конвертация видео, по умолчанию выключен | — | | `dragonfly` | Redis-совместимый кеш и сессии | 6379 | | `nats` | брокер сообщений JetStream | 4222 | -Все три публичных маршрута живут на одном домене: `/` — приложение, +Публичных маршрутов три, и все на одном домене: `/` — приложение, `/chatbox/` — вебсокеты, `/nanabush-player` — плеер курсов. -## Что нужно от вас +## Что нужно подготовить - Kubernetes 1.21+ и ingress-контроллер (nginx или traefik); -- PostgreSQL 14+ (с расширением Citus, если планируется шардирование); +- PostgreSQL 14+ (с Citus, если планируется шардирование); - S3-совместимое хранилище: AWS S3, MinIO, Yandex Object Storage, VK Cloud; - SMTP-сервер; -- доступ к Docker-реестру HRBox и токен HRBox Hub — выдают сотрудники HRBox. +- доступ к реестру образов HRBox и токен HRBox Hub — выдают сотрудники HRBox. -PostgreSQL и S3 чарт не разворачивает: это состояние, которое переживает -установку и требует собственного резервного копирования. +Базу и хранилище чарт не разворачивает: это данные, которые живут дольше +установки и требуют своего резервного копирования. --- @@ -52,13 +52,13 @@ kubectl create secret docker-registry regsecret \ --docker-email=not@used.com ``` -### 2. TLS-сертификат домена +### 2. TLS-сертификат ```bash kubectl create secret tls hrbox-tls --cert=cert.pem --key=key.pem ``` -Или выпустите сертификат через cert-manager и укажите его Secret +Либо выпустите сертификат через cert-manager и укажите его Secret в `ingress.tls.secretName`. ### 3. Ключи @@ -72,15 +72,15 @@ openssl rand -hex 16 openssl rand -base64 48 ``` -`secrets.encryptionKey` — единственный ключ, потеря которого необратима: -им зашифрованы пароли интеграций в базе. Сохраните его отдельно от кластера. +`secrets.encryptionKey` теряется безвозвратно: им зашифрованы пароли +интеграций в базе. Сохраните копию отдельно от кластера. -Ключи плеера `ticketHs256` и `sessionHs256` должны отличаться друг от друга — -это ключи разных контуров, и чарт откажется ставить одинаковые. +Ключи плеера `ticketHs256` и `sessionHs256` должны быть разными, одинаковые +чарт не примет. ### 4. Заполните `values.yaml` -Обязательный минимум: +Минимум: ```yaml ingress: @@ -121,7 +121,7 @@ secrets: servicesInternalSecret: "..." ``` -Всё остальное имеет рабочие значения по умолчанию. +У остальных параметров есть рабочие значения по умолчанию. Проверить конфигурацию, ничего не устанавливая: @@ -129,8 +129,7 @@ secrets: helm template hrbox .helm -f values.yaml > /dev/null ``` -Если чего-то не хватает, чарт скажет об этом текстом, а не оставит -неработающие поды в кластере. +Если чего-то не хватает, команда остановится и напишет, что заполнить. ### 5. Установка @@ -144,7 +143,7 @@ helm upgrade --install hrbox .helm -n hrbox --create-namespace -f values.yaml kubectl -n hrbox wait --for=condition=complete --timeout=30m job -l app=jobs ``` -### 7. Первый администратор +### 7. Создайте администратора ```bash kubectl -n hrbox exec -it deploy/hrbox-worker -- \ @@ -155,14 +154,13 @@ kubectl -n hrbox exec -it deploy/hrbox-worker -- \ ## Секреты -Чувствительные значения из `values.yaml` чарт складывает в один Secret -`hrbox-secrets` и монтирует контейнерам через `secretKeyRef` — в спеке -Deployment паролей нет. +Пароли и ключи из `values.yaml` чарт кладёт в один Secret `hrbox-secrets`, +поды читают их оттуда. В спеке Deployment паролей нет. Если секретами управляет внешний инструмент, создайте Secret сами и укажите -его имя в `secrets.existingSecret`. Ключи должны называться так: +имя в `secrets.existingSecret`. Ключи должны называться так: -| Ключ | Откуда берётся при `existingSecret: ""` | +| Ключ | Что кладут | |---|---| | `cookie-validation-key` | `secrets.cookieValidationKey` | | `encryption-key` | `secrets.encryptionKey` | @@ -180,63 +178,63 @@ Deployment паролей нет. | `slack-client-secret` | `app.slack.clientSecret` | | `slack-verification-token` | `app.slack.verificationToken` | -Ключи плеера лежат в отдельном Secret `hrbox-nanabush-player` — им управляет -`app.nanabushPlayer.deployment.existingSecret`. +Ключи плеера лежат в отдельном Secret `hrbox-nanabush-player`, за него +отвечает `app.nanabushPlayer.deployment.existingSecret`. --- ## Nanabush Player -Проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные -курсы, тесты и эссе. +Плеер учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные курсы, +тесты и эссе. -В облаке HRBox плеер живёт на отдельном поддомене. В on-premise выделенного -поддомена нет, поэтому плеер монтируется на основной домен под путём -`/nanabush-player`. Что из этого следует: +В облаке HRBox плеер стоит на отдельном поддомене. В on-premise поддомена +нет, поэтому плеер живёт на основном домене по пути `/nanabush-player`. +Из этого следуют две вещи: -- **Изоляции cookie нет.** Плеер и HRBox делят один origin, поэтому cookie - сессии HRBox отправляются браузером и на запросы к содержимому курсов, - включая сторонние SCORM-пакеты. Если нужна настоящая изоляция — - запрашивайте у HRBox схему с отдельным поддоменом. -- **Часы должны быть синхронизированы.** Launch-тикет живёт 5 минут, сессия - проверяется с допуском 2 минуты. Настройте NTP на нодах и сервере БД; - если это невозможно, выключите плеер (`app.nanabushPlayer.enabled: false`). +- **Cookie общие.** Плеер и HRBox на одном домене, поэтому cookie сессии + HRBox уходят и в запросы к содержимому курсов, включая сторонние + SCORM-пакеты. Если такая изоляция нужна, запросите у HRBox схему + с отдельным поддоменом. +- **Часы должны быть синхронны.** Launch-тикет живёт 5 минут, у сессии + допуск 2 минуты. Настройте NTP на нодах и сервере базы. Если это + невозможно, выключите плеер: `app.nanabushPlayer.enabled: false`. Порядок первого включения: -1. Убедитесь, что миграции применились: плееру нужны таблицы проекций. -2. Дождитесь, пока **все** поды `web` перейдут на новую ревизию. Смешанный - выкат при `rollout.percent > 0` может создать вторую активную попытку по - одному назначению. -3. Проверьте, что плеер отвечает сам, а не HRBox через общий маршрут: +1. Убедитесь, что миграции применились: плееру нужны свои таблицы. +2. Дождитесь, пока **все** поды `web` перейдут на новую версию. Пока часть + подов старая, при `rollout.percent > 0` по одному назначению может + открыться вторая попытка. +3. Проверьте, что отвечает именно плеер, а не HRBox через общий маршрут: ```bash kubectl -n hrbox exec deploy/hrbox-worker -- \ curl -s http://hrbox-nanabush-player:8094/readyz ``` - Настоящий плеер вернёт JSON. HTML означает, что запрос ушёл в HRBox. + Плеер вернёт JSON. HTML означает, что запрос ушёл в HRBox. -4. Проверьте публичный путь: `https://<домен>/nanabush-player/start`, - статику и API плеера под этим путём. +4. Откройте `https://<домен>/nanabush-player/start` и проверьте статику + и API плеера по этому пути. -Служебные пути (`/healthz`, `/readyz`, метрики) живут в корне и не зависят -от `basePath`. Порт метрик наружу не публикуется намеренно. +`/healthz`, `/readyz` и метрики отвечают в корне и от `basePath` не зависят. +Порт метрик наружу не публикуется. -Аварийное отключение — три независимых переключателя: +Быстро выключить плеер можно тремя переключателями: ```yaml app: nanabushPlayer: - enabled: false # пользователи возвращаются на старый проигрыватель + enabled: false # пользователи вернутся на старый проигрыватель ingress: enabled: false # маршрут снимается deployment: enabled: false # под останавливается ``` -Выключать нужно сверху вниз: чарт не даст оставить включённым маршрут -без пода или раскатку без маршрута. +Выключать нужно сверху вниз: чарт не даст оставить маршрут без пода +или включённый плеер без маршрута. Подробности контракта — `docs/nanabush-player-deployment.md` в репозитории HRBox. @@ -250,9 +248,9 @@ app: helm upgrade hrbox .helm -n hrbox -f values.yaml ``` -Миграции и обновление кластера запускаются автоматически как хуки -`post-upgrade` и выполняются строго по порядку. Имя джобы содержит номер -ревизии релиза, поэтому в `kubectl get jobs` видно, какой выкат её запускал. +Миграции и обновление кластера запускаются сами как хуки `post-upgrade`, +строго по очереди. В имени джобы есть номер ревизии релиза, поэтому +в `kubectl get jobs` видно, к какому выкату она относится. ### Масштабирование @@ -264,16 +262,16 @@ app: chatbox: 4 # по числу открытых вкладок ``` -`conductor` всегда в одном экземпляре: второй планировщик запустил бы -каждую задачу дважды. +`conductor` всегда в одном экземпляре: два планировщика запустят каждую +задачу дважды. -Для `web` при двух и более репликах создаётся PodDisruptionBudget -(`minAvailable: 75%`), чтобы drain ноды не уронил приложение целиком. +Для `web` от двух реплик создаётся PodDisruptionBudget (`minAvailable: 75%`), +чтобы вывод ноды из обслуживания не уронил приложение целиком. ### Конвертация видео -Выключена по умолчанию: профиль ресурсов у неё на порядок тяжелее обычной -обработки файлов. Включение: +Выключена по умолчанию, потому что требует заметно больше ресурсов, чем +остальная обработка файлов. Включение: ```yaml app: @@ -297,7 +295,7 @@ app: tempoUrl: "http://tempo.monitoring:4317" ``` -Рядом с `web` поднимется коллектор OpenTelemetry, PHP начнёт слать трейсы. +Рядом с `web` появится коллектор OpenTelemetry, PHP начнёт слать трейсы. ### Внешние Redis и NATS @@ -323,18 +321,18 @@ kubectl -n hrbox logs -l app=nanabush-player --tail=200 kubectl -n hrbox get jobs ``` -| Симптом | Куда смотреть | +| Что видно | Куда смотреть | |---|---| | Поды `web` не готовы | пробы `/healthz` и `/health-check`, логи контейнера `fpm` | -| Джоба миграций висит | init-контейнер `wait-postgres`: доступность и права в БД | -| Курс не открывается | логи `nanabush-player`, совпадение домена в `ingress.host` и `app.defaultTenantHostname` | +| Джоба миграций висит | init-контейнер `wait-postgres`: доступность базы и права | +| Курс не открывается | логи `nanabush-player`, совпадают ли `ingress.host` и `app.defaultTenantHostname` | | Не приходят уведомления | логи `goworker`, очереди NATS, `app.launchpad.topics` | -| Пользователей разлогинивает | перезапуски `dragonfly`: сессии живут в памяти | +| Пользователей разлогинивает | перезапуски `dragonfly`: сессии хранятся в памяти | --- -## Миграция с версии 2.x +## Обновление с версии 2.x -Часть ключей `values.yaml` переехала. Чарт проверяет старые имена и -останавливает установку с подсказкой, поэтому незамеченным ничего не -останется. Полный список изменений и порядок перехода — в [CHANGELOG.md](CHANGELOG.md). +Часть ключей `values.yaml` переехала. Старые имена чарт узнаёт и +останавливает установку с подсказкой, поэтому ничего не потеряется. +Список изменений и порядок перехода — в [CHANGELOG.md](CHANGELOG.md).