chart 3.0

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