DEVOPS-117: чарт on-premise 3.0 — Nanabush Player, секреты в Secret, валидация values
Актуализация чарта по продовому .helm из HRBOX. Nanabush Player перенесён из прода и упрощён под one-release-топологию on-premise: без карт env/ci_dc, но с сохранёнными инвариантами (audience = https://<host>, basePath отдельно от aud, allowlist обратного канала только на внутрикластерный web, метрики на непубликуемом порту). Выделенного поддомена в on-premise нет, поэтому плеер монтируется на основной домен под /nanabush-player. Три переключателя для аварийного отката сохранены. Из прода также перенесены: пул конвертации видео, PodDisruptionBudget для web, OpenTelemetry-сайдкар, assetlinks, набор location в nginx (/healthz, /health-check, потоковый /api/v1/integration/commit, /ai/, sw.js, шрифты). Версии образов подтянуты к продовым. Приведение чарта в порядок: все образы в блоке image, imagePullSecrets в настройках, девять одинаковых define ресурсов заменены одним хелпером, удалено ~100 строк мёртвого кода в _helpers.tpl, пароли и ключи уехали в Secret и читаются через secretKeyRef, добавлены 00-validate.yaml и NOTES.txt. Исправлено в 2.x: - SERVICES_INTERNAL_SECRET имел захардкоженное значение по умолчанию, то есть все установки, где его не заполнили, работали на одном секрете межсервисной аутентификации; - cluster.domain игнорировался: маршруты кластера NATS были захардкожены на cluster.local; - CLUSTER_ADVERTISE без сегмента svc — ноды NATS объявляли соседям недостижимый адрес; - nats.replicas и nats.cluster.replicas задавались независимо, при расхождении часть нод не входила в кластер; - пробы web проверяли только TCP-порт 9000, поэтому под с неработающим PHP считался готовым; - внутренние URL были захардкожены строками hrbox-* и молча ломались при nameOverride. Имена объектов и селекторы Deployment не менялись — обновление 2.x -> 3.0 не пересоздаёт объекты. Переехавшие ключи values.yaml чарт распознаёт и останавливает установку с подсказкой; порядок перехода — в CHANGELOG.md. Проверено: helm lint чистый; отрендерены 4 конфигурации (дефолт, всё включено, плеер и инфра выключены, nameOverride); структурная проверка рендера без замечаний; 20 негативных сценариев срабатывают с ожидаемыми сообщениями; реалистичный values.yaml из 2.x последовательно ловится всеми шестью guard'ами. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -1,19 +1,25 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: hrbox
|
name: hrbox
|
||||||
description: Simplified HRBox application Helm chart
|
description: HRBox — HR-платформа для on-premise установки
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
# Версия самого чарта. Увеличивается при каждом изменении чарта
|
# Версия чарта. Увеличивается при каждом изменении чарта.
|
||||||
version: 2.0.0
|
version: 3.0.0
|
||||||
|
|
||||||
# Версия приложения, которое деплоится. Не обязательно увеличивать при изменении чарта
|
# Версия приложения. Совпадает с линейкой образов HRBox.
|
||||||
appVersion: "3.0.0"
|
appVersion: "3.1.0"
|
||||||
|
|
||||||
# Ключевые слова для поиска
|
|
||||||
keywords:
|
keywords:
|
||||||
- hrbox
|
- hrbox
|
||||||
- hr
|
- hr
|
||||||
- application
|
- lms
|
||||||
|
- kedo
|
||||||
|
- on-premise
|
||||||
|
|
||||||
# Домашняя страница проекта
|
|
||||||
home: https://hrbox.io
|
home: https://hrbox.io
|
||||||
|
|
||||||
|
annotations:
|
||||||
|
# Компоненты, которые разворачивает чарт
|
||||||
|
hrbox.io/components: >-
|
||||||
|
web, worker, conductor, chatbox, goworker, kedoca, geonames,
|
||||||
|
file-processor, nanabush-player, dragonfly, nats
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{{- if not .Values.secrets.existingSecret }}
|
||||||
|
{{/*
|
||||||
|
Единый Secret с чувствительными настройками HRBox.
|
||||||
|
|
||||||
|
В чарте 2.x пароли и ключи подставлялись прямо в env-блоки Deployment,
|
||||||
|
поэтому любой, кто мог сделать `kubectl get deploy -o yaml`, видел пароль
|
||||||
|
базы и ключ шифрования. Здесь значения лежат в одном Secret, а контейнеры
|
||||||
|
читают их через secretKeyRef (см. hrbox.secretEnv в _helpers.tpl).
|
||||||
|
|
||||||
|
Если секретами управляет внешний инструмент, задайте secrets.existingSecret —
|
||||||
|
тогда чарт этот объект не создаёт и только ссылается на указанный. Набор
|
||||||
|
ключей должен совпадать с перечисленным ниже.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: Opaque
|
||||||
|
metadata:
|
||||||
|
name: {{ include "hrbox.secretName" . | quote }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "app") | nindent 4 }}
|
||||||
|
stringData:
|
||||||
|
cookie-validation-key: {{ .Values.secrets.cookieValidationKey | quote }}
|
||||||
|
encryption-key: {{ .Values.secrets.encryptionKey | quote }}
|
||||||
|
services-internal-secret: {{ .Values.secrets.servicesInternalSecret | quote }}
|
||||||
|
postgres-password: {{ .Values.app.postgres.password | quote }}
|
||||||
|
s3-key: {{ .Values.app.s3.key | quote }}
|
||||||
|
s3-secret: {{ .Values.app.s3.secret | quote }}
|
||||||
|
smtp-password: {{ .Values.app.smtp.password | quote }}
|
||||||
|
hub-token: {{ .Values.app.hub.token | quote }}
|
||||||
|
chatbox-wrapping-key: {{ .Values.app.chatbox.wrappingKey | quote }}
|
||||||
|
livekit-api-secret: {{ .Values.app.livekit.apiSecret | quote }}
|
||||||
|
yandex-client-password: {{ .Values.app.yandex.clientPassword | quote }}
|
||||||
|
yandex-ai-api-key: {{ .Values.app.ai.yandex.apiKey | quote }}
|
||||||
|
google-client-secret: {{ .Values.app.integrations.google.clientSecret | quote }}
|
||||||
|
slack-client-secret: {{ .Values.app.slack.clientSecret | quote }}
|
||||||
|
slack-verification-token: {{ .Values.app.slack.verificationToken | quote }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{{- $cfg := .Values.app.nanabushPlayer.deployment }}
|
||||||
|
{{- if and $cfg.enabled $cfg.existingSecret.create }}
|
||||||
|
{{/*
|
||||||
|
Ключи Nanabush Player.
|
||||||
|
|
||||||
|
ticketHs256 читают обе стороны: HRBox подписывает им launch-тикет
|
||||||
|
(NANABUSH_PLAYER_HS256_SECRET в поде web), плеер этим же ключом тикет
|
||||||
|
проверяет (PLAYER_HS256_SECRET). Поэтому ротация — одно значение на обе
|
||||||
|
стороны, а не два независимых.
|
||||||
|
|
||||||
|
sessionHs256 принадлежит только плееру: им подписывается сессия учащегося
|
||||||
|
внутри плеера, HRBox её не читает.
|
||||||
|
|
||||||
|
При existingSecret.create: false этот объект не создаётся — оба потребителя
|
||||||
|
всё так же ссылаются на existingSecret.name, но Secret должен быть заведён
|
||||||
|
вне чарта. При смене владельца уже существующего Secret берите новое имя,
|
||||||
|
чтобы Helm не удалил внешний объект во время перехода.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: Opaque
|
||||||
|
metadata:
|
||||||
|
name: {{ include "nanabushPlayer.secretName" . | quote }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nanabush-player") | nindent 4 }}
|
||||||
|
stringData:
|
||||||
|
{{ required "Нужно имя ключа ticketHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.ticketHs256 }}: {{ .Values.app.nanabushPlayer.keys.ticketHs256 | quote }}
|
||||||
|
{{ required "Нужно имя ключа sessionHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.sessionHs256 }}: {{ .Values.app.nanabushPlayer.keys.sessionHs256 | quote }}
|
||||||
|
{{- with $cfg.existingSecret.keys.ticketKeys }}
|
||||||
|
{{ . }}: {{ $.Values.app.nanabushPlayer.keys.ticketKeys | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
{{/*
|
||||||
|
============================================================================
|
||||||
|
Проверка values.yaml
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Этот файл не создаёт ни одного объекта. Он выполняется первым и падает с
|
||||||
|
понятным сообщением, если конфигурация неполная или противоречивая, — до
|
||||||
|
того, как в кластере появятся поды, которые всё равно не заработают.
|
||||||
|
|
||||||
|
Проверяется три вещи:
|
||||||
|
1) ключи, переехавшие в версии 3.0 (подсказка вместо тихого игнора);
|
||||||
|
2) обязательные значения, без которых приложение не поднимется;
|
||||||
|
3) инварианты Nanabush Player.
|
||||||
|
*/}}
|
||||||
|
|
||||||
|
{{/* --- 1. Ключи из чарта 2.x ---------------------------------------------- */}}
|
||||||
|
{{- if .Values.app.image }}
|
||||||
|
{{- fail "app.image переехал в image.hrbox (см. CHANGELOG, миграция 2.x -> 3.0)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.chatbox.image }}
|
||||||
|
{{- fail "app.chatbox.image переехал в image.chatbox" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.goworker.image }}
|
||||||
|
{{- fail "app.goworker.image переехал в image.goworker" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.kedoca.image }}
|
||||||
|
{{- fail "app.kedoca.image переехал в image.kedoca" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.file_processor }}
|
||||||
|
{{- fail "app.file_processor переименован в app.fileProcessor, а образ переехал в image.fileProcessor" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.custom_policy }}
|
||||||
|
{{- fail "app.custom_policy переименован в app.customPolicy" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.app.replicas.websocket }}
|
||||||
|
{{- fail "app.replicas.websocket переименован в app.replicas.chatbox ещё в версии 2.0" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.resources.websocket }}
|
||||||
|
{{- fail "resources.websocket переименован в resources.chatbox ещё в версии 2.0" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.nats.image }}
|
||||||
|
{{- fail "nats.image переехал в image.nats" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* --- 2. Обязательные значения ------------------------------------------- */}}
|
||||||
|
{{- if not .Values.ingress.host }}
|
||||||
|
{{- fail "Заполните ingress.host — домен, по которому открывается HRBox" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.defaultTenantHostname }}
|
||||||
|
{{- fail "Заполните app.defaultTenantHostname (обычно совпадает с ingress.host)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.postgres.password }}
|
||||||
|
{{- fail "Заполните app.postgres.password" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.s3.key }}
|
||||||
|
{{- fail "Заполните app.s3.key — без хранилища файлов HRBox не работает" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.s3.secret }}
|
||||||
|
{{- fail "Заполните app.s3.secret" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.hub.token }}
|
||||||
|
{{- fail "Заполните app.hub.token — токен выдают сотрудники HRBox" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.secrets.existingSecret }}
|
||||||
|
{{- if not .Values.secrets.cookieValidationKey }}
|
||||||
|
{{- fail "Заполните secrets.cookieValidationKey (openssl rand -hex 16)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.secrets.encryptionKey }}
|
||||||
|
{{- fail "Заполните secrets.encryptionKey (openssl rand -base64 48). Потеря ключа делает зашифрованные данные нечитаемыми" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.secrets.servicesInternalSecret }}
|
||||||
|
{{- fail "Заполните secrets.servicesInternalSecret (openssl rand -base64 48)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.app.chatbox.wrappingKey }}
|
||||||
|
{{- fail "Заполните app.chatbox.wrappingKey (openssl rand -hex 16)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
|
{{- if not .Values.ingress.tls.secretName }}
|
||||||
|
{{- fail "При ingress.tls.enabled нужен ingress.tls.secretName с TLS-сертификатом" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* --- 3. Внешние зависимости ---------------------------------------------- */}}
|
||||||
|
{{- if and (not .Values.dragonfly.enabled) (not .Values.app.redis.host) }}
|
||||||
|
{{- fail "dragonfly.enabled: false требует app.redis.host — иначе приложение обратится к сервису, которого нет в релизе" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and (not .Values.nats.enabled) (not .Values.app.nats.url) }}
|
||||||
|
{{- fail "nats.enabled: false требует app.nats.url — иначе приложение обратится к брокеру, которого нет в релизе" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.nats.enabled .Values.nats.cluster.enabled }}
|
||||||
|
{{- if ne (.Values.nats.replicas | int) (.Values.nats.cluster.replicas | int) }}
|
||||||
|
{{- fail (printf "nats.replicas (%d) и nats.cluster.replicas (%d) должны совпадать: иначе часть нод не войдёт в кластер, а реплики JetStream-стримов будут посчитаны неверно" (.Values.nats.replicas | int) (.Values.nats.cluster.replicas | int)) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* --- 4. Nanabush Player -------------------------------------------------- */}}
|
||||||
|
{{- $player := .Values.app.nanabushPlayer }}
|
||||||
|
{{- if and $player.enabled (not $player.deployment.enabled) }}
|
||||||
|
{{- fail "app.nanabushPlayer.enabled требует deployment.enabled: HRBox нельзя переключить на плеер, которого нет в кластере" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and $player.enabled (not $player.ingress.enabled) }}
|
||||||
|
{{- fail "app.nanabushPlayer.enabled требует ingress.enabled: иначе HRBox выдаст ссылку на недоступный адрес" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and $player.ingress.enabled (not $player.deployment.enabled) }}
|
||||||
|
{{- fail "app.nanabushPlayer.ingress.enabled требует deployment.enabled: маршрут вёл бы в несуществующий Service" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and $player.ingress.enabled (not .Values.ingress.enabled) }}
|
||||||
|
{{- fail "Путь плеера публикуется на основном Ingress, поэтому нужен ingress.enabled: true" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $player.deployment.enabled }}
|
||||||
|
{{- $basePath := $player.basePath | default "/nanabush-player" }}
|
||||||
|
{{- if eq (trimSuffix "/" $basePath) "" }}
|
||||||
|
{{- fail "app.nanabushPlayer.basePath не может быть корнем: корень домена занят самим HRBox" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not (hasPrefix "/" $basePath) }}
|
||||||
|
{{- fail "app.nanabushPlayer.basePath должен начинаться со слэша, например /nanabush-player" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not (include "hrbox.imageTag" .Values.image.nanabushPlayer) }}
|
||||||
|
{{- fail "image.nanabushPlayer должен содержать явный тег версии, например nanabush-player:1.0.11 — из него выводится версия плеера в launch-тикете" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $player.deployment.binaryCapabilities.basePath }}
|
||||||
|
{{- fail "On-premise монтирует плеер под путём, поэтому нужен образ с поддержкой base path. Проверьте сборку и верните deployment.binaryCapabilities.basePath: true" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $player.deployment.existingSecret.name }}
|
||||||
|
{{- fail "Заполните app.nanabushPlayer.deployment.existingSecret.name" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $player.deployment.existingSecret.create }}
|
||||||
|
{{- if not $player.keys.ticketHs256 }}
|
||||||
|
{{- fail "Заполните app.nanabushPlayer.keys.ticketHs256 (openssl rand -base64 48)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $player.keys.sessionHs256 }}
|
||||||
|
{{- fail "Заполните app.nanabushPlayer.keys.sessionHs256 (openssl rand -base64 48)" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $player.keys.ticketHs256 $player.keys.sessionHs256 }}
|
||||||
|
{{- fail "app.nanabushPlayer.keys.ticketHs256 и sessionHs256 должны отличаться: это ключи разных контуров" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and $player.deployment.existingSecret.keys.ticketKeys (not $player.keys.ticketKeys) }}
|
||||||
|
{{- fail "Указано имя ключа ticketKeys в секрете, но само кольцо app.nanabushPlayer.keys.ticketKeys пустое" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and $player.deployment.redis.urlFromExistingSecret $player.deployment.existingSecret.create }}
|
||||||
|
{{- fail "redis.urlFromExistingSecret: true требует existingSecret.create: false — Secret, создаваемый чартом, не содержит URL Redis" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $player.deployment.metrics.enabled }}
|
||||||
|
{{- if eq ($player.deployment.metrics.port | int) ($player.service.port | int) }}
|
||||||
|
{{- fail "Порт метрик плеера должен отличаться от публичного порта сервиса, иначе метрики окажутся доступны содержимому курсов" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
|
{{- if .Values.nats.enabled }}
|
||||||
|
{{/*
|
||||||
|
Конфигурация NATS JetStream.
|
||||||
|
|
||||||
|
Маршруты кластера строятся из служебных DNS-имён StatefulSet, поэтому домен
|
||||||
|
кластера берётся из cluster.domain — в чарте 2.x он был захардкожен здесь и
|
||||||
|
одновременно настраивался в values, из-за чего нестандартный домен ломал
|
||||||
|
сборку кластера.
|
||||||
|
*/}}
|
||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}}
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-nats-config
|
name: {{ printf "%s-config" $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }}
|
||||||
data:
|
data:
|
||||||
nats.conf: |
|
nats.conf: |
|
||||||
# PID file shared with configuration reloader.
|
# PID-файл, общий с перезагрузчиком конфигурации.
|
||||||
pid_file: "/var/run/nats/nats.pid"
|
pid_file: "/var/run/nats/nats.pid"
|
||||||
|
|
||||||
###############
|
###############
|
||||||
@@ -42,11 +54,11 @@ data:
|
|||||||
###################################
|
###################################
|
||||||
cluster {
|
cluster {
|
||||||
port: 6222
|
port: 6222
|
||||||
name: {{ .Chart.Name }}-nats-cluster
|
name: {{ printf "%s-cluster" $name }}
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
{{- range $i := until (int .Values.nats.cluster.replicas) }}
|
{{- range $i := until (int .Values.nats.cluster.replicas) }}
|
||||||
nats://{{ $.Chart.Name }}-nats-{{ $i }}.{{ $.Chart.Name }}-nats.{{ $.Release.Namespace }}.svc.cluster.local:6222,
|
nats://{{ $name }}-{{ $i }}.{{ $name }}.{{ $.Release.Namespace }}.svc.{{ $.Values.cluster.domain }}:6222,
|
||||||
{{- end }}
|
{{- end }}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -55,4 +67,5 @@ data:
|
|||||||
}
|
}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
lame_duck_duration: 120s
|
lame_duck_duration: {{ .Values.nats.terminationGracePeriodSeconds }}s
|
||||||
|
{{- end }}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{{/*
|
||||||
|
Привязка мобильных приложений к домену инсталляции.
|
||||||
|
|
||||||
|
Android читает /.well-known/assetlinks.json, iOS — /.well-known/apple-app-site-association.
|
||||||
|
Без них ссылки из писем и уведомлений открываются в браузере, а не в приложении.
|
||||||
|
По умолчанию здесь идентификаторы штатных приложений HRBox; если у вас
|
||||||
|
собственная сборка, переопределите app.mobileApps в values.yaml.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: assetlinks
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
|
data:
|
||||||
|
assetlinks.json: |
|
||||||
|
[
|
||||||
|
{{- range $i, $app := .Values.app.mobileApps.android }}
|
||||||
|
{{- if $i }},{{ end }}
|
||||||
|
{
|
||||||
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": {{ $app.packageName | quote }},
|
||||||
|
"sha256_cert_fingerprints": {{ toJson $app.sha256CertFingerprints }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
]
|
||||||
|
|
||||||
|
apple-app-site-association: |
|
||||||
|
{
|
||||||
|
"applinks": {
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"appIDs": {{ toJson .Values.app.mobileApps.apple.appIds }},
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"/": "/*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
|
{{/*
|
||||||
|
Конфигурация nginx в поде web. Отличия от чарта 2.x — набор location,
|
||||||
|
перенесённый из продового окружения: health-check для проб, потоковая
|
||||||
|
загрузка оргструктуры из 1С, длинные таймауты для AI-запросов, отдача
|
||||||
|
assetlinks мобильных приложений и корректные заголовки для service worker.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: web-configuration
|
name: web-configuration
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
data:
|
data:
|
||||||
nginx.conf: |
|
nginx.conf: |
|
||||||
user www-data;
|
user www-data;
|
||||||
@@ -43,7 +51,6 @@ data:
|
|||||||
client_header_buffer_size 1024k;
|
client_header_buffer_size 1024k;
|
||||||
large_client_header_buffers 16 1024k;
|
large_client_header_buffers 16 1024k;
|
||||||
|
|
||||||
|
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
@@ -51,7 +58,7 @@ data:
|
|||||||
# SSL Settings
|
# SSL Settings
|
||||||
##
|
##
|
||||||
|
|
||||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
ssl_session_cache shared:SSL:10m;
|
ssl_session_cache shared:SSL:10m;
|
||||||
ssl_session_timeout 10m;
|
ssl_session_timeout 10m;
|
||||||
@@ -62,7 +69,6 @@ data:
|
|||||||
|
|
||||||
access_log /var/log/nginx/access.log;
|
access_log /var/log/nginx/access.log;
|
||||||
error_log /var/log/nginx/error.log;
|
error_log /var/log/nginx/error.log;
|
||||||
rewrite_log on;
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Gzip Settings
|
# Gzip Settings
|
||||||
@@ -97,6 +103,79 @@ data:
|
|||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Liveness: отвечает сам nginx, не дожидаясь PHP-FPM.
|
||||||
|
location = /healthz {
|
||||||
|
access_log off;
|
||||||
|
return 200 "ok\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Readiness: короткая проверка, что PHP-FPM отвечает.
|
||||||
|
location = /health-check {
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_connect_timeout 1s;
|
||||||
|
fastcgi_read_timeout 3s;
|
||||||
|
fastcgi_send_timeout 1s;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /index.php;
|
||||||
|
fastcgi_param PATH_INFO /health-check;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Загрузка оргструктуры из 1С. PHP читает тело запроса потоком и
|
||||||
|
# проксирует в GoWorker без полной буферизации, поэтому nginx
|
||||||
|
# тоже не должен буферизовать тело.
|
||||||
|
location = /api/v1/integration/commit {
|
||||||
|
client_max_body_size 500m;
|
||||||
|
fastcgi_request_buffering off;
|
||||||
|
|
||||||
|
fastcgi_pass localhost:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_read_timeout 300s;
|
||||||
|
fastcgi_send_timeout 300s;
|
||||||
|
fastcgi_connect_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Привязка мобильных приложений к домену
|
||||||
|
location /.well-known/assetlinks.json {
|
||||||
|
default_type "application/json";
|
||||||
|
alias /var/www/assetlinks/assetlinks.json;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /.well-known/apple-app-site-association {
|
||||||
|
default_type "application/json";
|
||||||
|
alias /var/www/assetlinks/apple-app-site-association;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /frontend/sw.js {
|
||||||
|
add_header Service-Worker-Allowed /;
|
||||||
|
add_header Cache-Control "no-cache, must-revalidate, public";
|
||||||
|
expires off;
|
||||||
|
try_files $uri $uri/ /index.php?$args;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# AI-запросы отвечают дольше обычных, им нужен свой таймаут.
|
||||||
|
location ^~ /ai/ {
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header X-Content-Type-Options "nosniff";
|
||||||
|
add_header Permissions-Policy "browsing-topics=()" always;
|
||||||
|
|
||||||
|
fastcgi_pass localhost:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_read_timeout 300s;
|
||||||
|
fastcgi_connect_timeout 60s;
|
||||||
|
fastcgi_send_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
|
||||||
@@ -105,11 +184,9 @@ 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;
|
||||||
|
|
||||||
# Redirect everything that isn't a real file to index.php
|
# Всё, что не является файлом, уходит в index.php
|
||||||
try_files $uri $uri/ /index.php?$args;
|
try_files $uri $uri/ /index.php?$args;
|
||||||
|
|
||||||
# uncomment to avoid processing of calls to non-existing static files by Yii
|
|
||||||
#location ~* \.(js|css|png|jpg|gif|ico|pdf|mov|fla|zip|rar)$ {
|
|
||||||
location ~* \.(js|css|png|jpg|gif|ico)$ {
|
location ~* \.(js|css|png|jpg|gif|ico)$ {
|
||||||
|
|
||||||
expires 30d;
|
expires 30d;
|
||||||
@@ -127,7 +204,15 @@ data:
|
|||||||
open_file_cache_errors off;
|
open_file_cache_errors off;
|
||||||
}
|
}
|
||||||
|
|
||||||
#error_page 404 /404.html;
|
location ~* \.(woff|woff2|ttf|otf|eot)$ {
|
||||||
|
|
||||||
|
expires 30d;
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
try_files $uri $uri/ /index.php?$args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{{- if .Values.app.tracing.enabled }}
|
||||||
|
{{/*
|
||||||
|
Конфигурация sidecar-коллектора OpenTelemetry в поде web.
|
||||||
|
Создаётся только при app.tracing.enabled.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: otel-config
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
|
data:
|
||||||
|
config.yml: |
|
||||||
|
receivers:
|
||||||
|
otlp:
|
||||||
|
protocols:
|
||||||
|
grpc:
|
||||||
|
endpoint: "0.0.0.0:4317"
|
||||||
|
http:
|
||||||
|
endpoint: "0.0.0.0:4318"
|
||||||
|
|
||||||
|
processors:
|
||||||
|
batch:
|
||||||
|
|
||||||
|
exporters:
|
||||||
|
otlp:
|
||||||
|
endpoint: {{ required "При app.tracing.enabled нужен app.tracing.tempoUrl — адрес приёмника трейсов" .Values.app.tracing.tempoUrl | quote }}
|
||||||
|
tls:
|
||||||
|
insecure: true
|
||||||
|
|
||||||
|
service:
|
||||||
|
pipelines:
|
||||||
|
traces:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [batch]
|
||||||
|
exporters: [otlp]
|
||||||
|
{{- end }}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
|
{{/*
|
||||||
|
Скрипт init-контейнера джобы миграций: ждёт, пока PostgreSQL начнёт принимать
|
||||||
|
подключения. Без этого миграции падают, если база стартует медленнее подов.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: job-scripts
|
name: job-scripts
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 4 }}
|
||||||
data:
|
data:
|
||||||
wait.sh: |
|
wait.sh: |
|
||||||
until pg_isready -h $POSTGRES_VHOST -p $POSTGRES_PORT -U $POSTGRES_USER
|
until pg_isready -h "$POSTGRES_VHOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER"
|
||||||
do
|
do
|
||||||
echo "Waiting for postgres"
|
echo "Waiting for postgres"
|
||||||
sleep 2;
|
sleep 2
|
||||||
done
|
done
|
||||||
@@ -1,46 +1,68 @@
|
|||||||
{{- if .Values.dragonfly.enabled }}
|
{{- if .Values.dragonfly.enabled }}
|
||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "dragonfly") -}}
|
||||||
|
{{/*
|
||||||
|
Redis-совместимый кеш: сессии, кеш приложения и очереди Nanabush Player.
|
||||||
|
|
||||||
|
Одна реплика и стратегия Recreate — намеренно: данные хранятся в памяти,
|
||||||
|
второй экземпляр не даёт ни отказоустойчивости, ни консистентности.
|
||||||
|
Селектор оставлен как в чарте 2.x.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-dragonfly
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "dragonfly") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
revisionHistoryLimit: 3
|
revisionHistoryLimit: 3
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
component: {{ .Chart.Name }}-dragonfly
|
component: {{ $name }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
component: {{ .Chart.Name }}-dragonfly
|
component: {{ $name }}
|
||||||
|
app: dragonfly
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: dragonfly
|
- name: dragonfly
|
||||||
image: cr.yandex/crpnn6fi85p3dauha6nc/dragonflydb:1.15.1
|
image: {{ .Values.image.dragonfly | quote }}
|
||||||
{{- if .Values.dragonfly.maxmemory }}
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
{{- with .Values.dragonfly.maxmemory }}
|
||||||
args:
|
args:
|
||||||
- --maxmemory={{ .Values.dragonfly.maxmemory }}
|
- --maxmemory={{ . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- name: dragonfly
|
- name: dragonfly
|
||||||
containerPort: 6379
|
containerPort: 6379
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "dragonfly") | indent 10 }}
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 6379
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
port: 6379
|
port: 6379
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-dragonfly
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "dragonfly") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
clusterIP: None
|
clusterIP: None
|
||||||
selector:
|
selector:
|
||||||
component: {{ .Chart.Name }}-dragonfly
|
component: {{ $name }}
|
||||||
ports:
|
ports:
|
||||||
- port: 6379
|
- name: dragonfly
|
||||||
|
port: 6379
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
|
{{- if .Values.nats.enabled }}
|
||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}}
|
||||||
|
{{/*
|
||||||
|
Брокер сообщений. Селектор StatefulSet намеренно оставлен таким же, как в
|
||||||
|
чарте 2.x: он неизменяем, и его правка потребовала бы пересоздания объекта.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-nats
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: nats
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: nats
|
app.kubernetes.io/name: nats
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
replicas: {{ .Values.nats.replicas }}
|
replicas: {{ .Values.nats.replicas }}
|
||||||
serviceName: {{ .Chart.Name }}-nats
|
serviceName: {{ $name }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@@ -22,17 +27,18 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: config-volume
|
- name: config-volume
|
||||||
configMap:
|
configMap:
|
||||||
name: {{ .Chart.Name }}-nats-config
|
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 }}
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: nats
|
- name: nats
|
||||||
image: {{ .Values.nats.image }}
|
image: {{ .Values.image.nats | quote }}
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4222
|
- containerPort: 4222
|
||||||
name: client
|
name: client
|
||||||
@@ -57,15 +63,19 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: metadata.namespace
|
fieldPath: metadata.namespace
|
||||||
|
# Адрес, который нода объявляет остальным участникам кластера.
|
||||||
|
# Сегмент svc обязателен: без него имя не резолвится и ноды
|
||||||
|
# не находят друг друга.
|
||||||
- name: CLUSTER_ADVERTISE
|
- name: CLUSTER_ADVERTISE
|
||||||
value: $(POD_NAME).{{ .Chart.Name }}-nats.$(POD_NAMESPACE).{{ .Values.cluster.domain | default "cluster.local" }}
|
value: $(POD_NAME).{{ $name }}.$(POD_NAMESPACE).svc.{{ .Values.cluster.domain }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "nats") | indent 10 }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config-volume
|
- name: config-volume
|
||||||
mountPath: /etc/nats-config
|
mountPath: /etc/nats-config
|
||||||
- name: pid
|
- name: pid
|
||||||
mountPath: /var/run/nats
|
mountPath: /var/run/nats
|
||||||
{{- if .Values.nats.jetstream.fileStorage.enabled }}
|
{{- if .Values.nats.jetstream.fileStorage.enabled }}
|
||||||
- name: {{ .Chart.Name }}-nats-js-pvc
|
- name: {{ printf "%s-js-pvc" $name }}
|
||||||
mountPath: {{ .Values.nats.jetstream.fileStorage.storageDirectory }}
|
mountPath: {{ .Values.nats.jetstream.fileStorage.storageDirectory }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
@@ -81,17 +91,19 @@ spec:
|
|||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
lifecycle:
|
lifecycle:
|
||||||
|
# Перевод ноды в lame duck: она перестаёт принимать новые
|
||||||
|
# подключения и даёт клиентам время переподключиться к соседям.
|
||||||
preStop:
|
preStop:
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
- "/bin/sh"
|
- "/bin/sh"
|
||||||
- "-c"
|
- "-c"
|
||||||
- "nats-server -sl=ldm=/var/run/nats/nats.pid && /bin/sleep 120"
|
- "nats-server -sl=ldm=/var/run/nats/nats.pid && /bin/sleep {{ .Values.nats.terminationGracePeriodSeconds }}"
|
||||||
|
|
||||||
{{- if .Values.nats.reloader.enabled }}
|
{{- if .Values.nats.reloader.enabled }}
|
||||||
- name: reloader
|
- name: reloader
|
||||||
image: natsio/nats-server-config-reloader:0.6.2
|
image: {{ .Values.image.natsConfigReloader | quote }}
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command:
|
command:
|
||||||
- "nats-server-config-reloader"
|
- "nats-server-config-reloader"
|
||||||
- "-pid"
|
- "-pid"
|
||||||
@@ -108,15 +120,15 @@ spec:
|
|||||||
{{- if .Values.nats.jetstream.fileStorage.enabled }}
|
{{- if .Values.nats.jetstream.fileStorage.enabled }}
|
||||||
volumeClaimTemplates:
|
volumeClaimTemplates:
|
||||||
- metadata:
|
- metadata:
|
||||||
name: {{ .Chart.Name }}-nats-js-pvc
|
name: {{ printf "%s-js-pvc" $name }}
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .Values.nats.jetstream.fileStorage.size }}
|
storage: {{ .Values.nats.jetstream.fileStorage.size }}
|
||||||
{{- if .Values.nats.jetstream.fileStorage.storageClassName }}
|
{{- with .Values.nats.jetstream.fileStorage.storageClassName }}
|
||||||
storageClassName: {{ .Values.nats.jetstream.fileStorage.storageClassName }}
|
storageClassName: {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
@@ -124,15 +136,15 @@ spec:
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-nats
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: nats
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
spec:
|
spec:
|
||||||
|
# Headless: клиентам нужны адреса конкретных нод, а не общий VIP.
|
||||||
|
clusterIP: None
|
||||||
selector:
|
selector:
|
||||||
app.kubernetes.io/name: nats
|
app.kubernetes.io/name: nats
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
clusterIP: None
|
|
||||||
ports:
|
ports:
|
||||||
- name: client
|
- name: client
|
||||||
port: 4222
|
port: 4222
|
||||||
@@ -142,3 +154,4 @@ spec:
|
|||||||
port: 8222
|
port: 8222
|
||||||
- name: metrics
|
- name: metrics
|
||||||
port: 7777
|
port: 7777
|
||||||
|
{{- end }}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{{- $replicas := .Values.app.replicas.web | int }}
|
||||||
|
{{/*
|
||||||
|
Бюджет прерываний для web.
|
||||||
|
|
||||||
|
Имеет смысл только начиная с двух реплик: при одной minAvailable округляется
|
||||||
|
до 1 и намертво блокирует drain ноды, ничего при этом не защищая.
|
||||||
|
*/}}
|
||||||
|
{{- if gt $replicas 1 }}
|
||||||
|
---
|
||||||
|
apiVersion: policy/v1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "web") }}-pdb
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
minAvailable: 75%
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
+95
-16
@@ -1,48 +1,69 @@
|
|||||||
|
{{- $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
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-web
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: web
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
strategy:
|
strategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
rollingUpdate:
|
rollingUpdate:
|
||||||
maxSurge: 1
|
maxSurge: 1
|
||||||
maxUnavailable: 1
|
maxUnavailable: 0
|
||||||
replicas: {{ .Values.app.replicas.web }}
|
replicas: {{ .Values.app.replicas.web }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ .Chart.Name }}-web
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ .Chart.Name }}-web
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 8 }}
|
||||||
|
app: web
|
||||||
annotations:
|
annotations:
|
||||||
prometheus.io/scrape: 'true'
|
prometheus.io/scrape: 'true'
|
||||||
prometheus.io/port: '9253'
|
prometheus.io/port: '9253'
|
||||||
|
checksum/nginx-config: {{ include (print $.Template.BasePath "/01-nginx-configmap.yaml") . | sha256sum }}
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
terminationGracePeriodSeconds: 30
|
||||||
- name: regsecret
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
containers:
|
containers:
|
||||||
- name: fpm
|
- name: fpm
|
||||||
image: {{ .Values.app.image }}
|
image: {{ .Values.image.hrbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command: ["/usr/local/sbin/php-fpm", "-c", "/usr/local/etc/php-fpm"]
|
command: ["/usr/local/sbin/php-fpm", "-c", "/usr/local/etc/php-fpm"]
|
||||||
ports:
|
ports:
|
||||||
- name: fpm
|
- name: fpm
|
||||||
containerPort: 9000
|
containerPort: 9000
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "app_resources" . | indent 10 }}
|
{{- include "envs_nanabush_player_signing" . | indent 12 }}
|
||||||
|
- name: VIDEO_CONVERTER_ENABLED
|
||||||
|
value: {{ .Values.app.videoConverter.enabled | quote }}
|
||||||
|
{{- if .Values.app.tracing.enabled }}
|
||||||
|
{{- include "envs_tracing" . | indent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "app") | indent 10 }}
|
||||||
lifecycle:
|
lifecycle:
|
||||||
|
# Пауза даёт ingress-контроллеру убрать под из балансировки,
|
||||||
|
# прежде чем FPM начнёт корректное завершение.
|
||||||
preStop:
|
preStop:
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- '-c'
|
- '-c'
|
||||||
- sleep 5 && kill -SIGQUIT 1
|
- sleep 15 && kill -SIGQUIT 1
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
port: 9000
|
port: 9000
|
||||||
@@ -53,47 +74,105 @@ spec:
|
|||||||
port: 9000
|
port: 9000
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
|
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: {{ .Values.app.image }}
|
image: {{ .Values.image.hrbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
|
command: ["/usr/sbin/nginx", "-g", "daemon off;"]
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 80
|
containerPort: 80
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "nginx_resources" . | indent 10}}
|
{{- include "hrbox.resources" (dict "ctx" . "component" "nginx") | indent 10 }}
|
||||||
lifecycle:
|
lifecycle:
|
||||||
preStop:
|
preStop:
|
||||||
exec:
|
exec:
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- '-c'
|
- '-c'
|
||||||
- sleep 5 && /usr/sbin/nginx -s quit
|
- sleep 15 && /usr/sbin/nginx -s quit
|
||||||
|
# /healthz отвечает сам nginx, /health-check проверяет,
|
||||||
|
# что за ним жив PHP-FPM.
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 5
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health-check
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: configs
|
- name: configs
|
||||||
mountPath: /etc/nginx/nginx.conf
|
mountPath: /etc/nginx/nginx.conf
|
||||||
subPath: nginx.conf
|
subPath: nginx.conf
|
||||||
|
- name: assetlinks
|
||||||
|
mountPath: /var/www/assetlinks/assetlinks.json
|
||||||
|
subPath: assetlinks.json
|
||||||
|
- name: assetlinks
|
||||||
|
mountPath: /var/www/assetlinks/apple-app-site-association
|
||||||
|
subPath: apple-app-site-association
|
||||||
|
|
||||||
|
{{- if .Values.app.tracing.enabled }}
|
||||||
|
- name: otel-collector
|
||||||
|
image: {{ .Values.image.otelCollector | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
args: ["--config=/etc/otel-config/otel-collector-config.yaml"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 4317
|
||||||
|
name: otlp-grpc
|
||||||
|
- containerPort: 4318
|
||||||
|
name: otlp-http
|
||||||
|
volumeMounts:
|
||||||
|
- name: otel-config
|
||||||
|
mountPath: /etc/otel-config/otel-collector-config.yaml
|
||||||
|
subPath: config.yml
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
- name: phpfpm-exporter
|
- name: phpfpm-exporter
|
||||||
image: cr.yandex/crpnn6fi85p3dauha6nc/php-fpm_exporter:latest
|
image: {{ .Values.image.phpFpmExporter | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 9253
|
- containerPort: 9253
|
||||||
|
name: metrics
|
||||||
env:
|
env:
|
||||||
- name: PHP_FPM_SCRAPE_URI
|
- name: PHP_FPM_SCRAPE_URI
|
||||||
value: "tcp://127.0.0.1:9000/hrbox-fpm-status"
|
value: "tcp://127.0.0.1:9000/hrbox-fpm-status"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: configs
|
- name: configs
|
||||||
configMap:
|
configMap:
|
||||||
name: web-configuration
|
name: web-configuration
|
||||||
defaultMode: 420
|
defaultMode: 420
|
||||||
|
- name: assetlinks
|
||||||
|
configMap:
|
||||||
|
name: assetlinks
|
||||||
|
defaultMode: 420
|
||||||
|
{{- if .Values.app.tracing.enabled }}
|
||||||
|
- name: otel-config
|
||||||
|
configMap:
|
||||||
|
name: otel-config
|
||||||
|
defaultMode: 420
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-web
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
service: {{ .Chart.Name }}-web
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: 80
|
port: 80
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "chatbox") -}}
|
||||||
|
{{/*
|
||||||
|
Chatbox — вебсокет-сервер чатов и real-time уведомлений.
|
||||||
|
До версии 2.0 назывался websocket; имена объектов не менялись с тех пор.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "chatbox") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
minReadySeconds: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 1
|
||||||
|
replicas: {{ .Values.app.replicas.chatbox }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "chatbox") | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "chatbox") | nindent 8 }}
|
||||||
|
app: chatbox
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: 'true'
|
||||||
|
prometheus.io/port: '9797'
|
||||||
|
prometheus.io/path: "/info"
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
|
spec:
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: chatbox
|
||||||
|
image: {{ .Values.image.chatbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 9797
|
||||||
|
env:
|
||||||
|
{{- include "envs_all" . | indent 12 }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "chatbox") | indent 10 }}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "chatbox") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "chatbox") | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 9797
|
||||||
|
protocol: TCP
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
# Chatbox (WebSocket server) - обеспечивает real-time функционал
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ .Chart.Name }}-chatbox
|
|
||||||
labels:
|
|
||||||
app: chatbox
|
|
||||||
spec:
|
|
||||||
minReadySeconds: 5
|
|
||||||
strategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
rollingUpdate:
|
|
||||||
maxSurge: 1
|
|
||||||
maxUnavailable: 1
|
|
||||||
replicas: {{ .Values.app.replicas.chatbox }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
service: {{ .Chart.Name }}-chatbox
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
service: {{ .Chart.Name }}-chatbox
|
|
||||||
annotations:
|
|
||||||
prometheus.io/scrape: 'true'
|
|
||||||
prometheus.io/port: '9797'
|
|
||||||
prometheus.io/path: "/info"
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: regsecret
|
|
||||||
containers:
|
|
||||||
- name: chatbox
|
|
||||||
image: {{ .Values.app.chatbox.image }}
|
|
||||||
imagePullPolicy: Always
|
|
||||||
env:
|
|
||||||
{{- include "envs_all" . | indent 12 }}
|
|
||||||
{{- include "chatbox_resources" . | indent 10 }}
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ .Chart.Name }}-chatbox
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
service: {{ .Chart.Name }}-chatbox
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: 9797
|
|
||||||
protocol: TCP
|
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "worker") -}}
|
||||||
|
{{/*
|
||||||
|
Обработчик фоновых задач: конвертация файлов, рассылка почты и уведомлений,
|
||||||
|
генерация отчётов, обслуживание Nanabush Player.
|
||||||
|
|
||||||
|
Стратегия Recreate: воркеры разбирают задачи из общей очереди, и пересечение
|
||||||
|
старых и новых подов во время выката привело бы к двойной обработке.
|
||||||
|
Длинный terminationGracePeriodSeconds даёт закончить уже взятую задачу.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-worker
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: worker
|
{{- include "hrbox.labels" (dict "ctx" . "name" "worker") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
strategy:
|
strategy:
|
||||||
@@ -12,22 +21,26 @@ spec:
|
|||||||
replicas: {{ .Values.app.replicas.worker }}
|
replicas: {{ .Values.app.replicas.worker }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ .Chart.Name }}-worker
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "worker") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ .Chart.Name }}-worker
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "worker") | nindent 8 }}
|
||||||
|
app: worker
|
||||||
annotations:
|
annotations:
|
||||||
prometheus.io/scrape: 'true'
|
prometheus.io/scrape: 'true'
|
||||||
prometheus.io/port: '9253'
|
prometheus.io/port: '9253'
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: worker
|
- name: worker
|
||||||
image: {{ .Values.app.image }}
|
image: {{ .Values.image.hrbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command: ["hrbox-worker"]
|
command: ["hrbox-worker"]
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "worker_resources" . | indent 10}}
|
- name: VIDEO_CONVERTER_ENABLED
|
||||||
|
value: {{ .Values.app.videoConverter.enabled | quote }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "worker") | indent 10 }}
|
||||||
terminationGracePeriodSeconds: 600
|
terminationGracePeriodSeconds: 600
|
||||||
@@ -1,21 +1,34 @@
|
|||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "conductor") -}}
|
||||||
|
{{/*
|
||||||
|
Планировщик задач по расписанию.
|
||||||
|
|
||||||
|
Строго одна реплика: два планировщика запустили бы каждую задачу дважды.
|
||||||
|
По той же причине стратегия Recreate, а не RollingUpdate.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-conductor
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: conductor
|
{{- include "hrbox.labels" (dict "ctx" . "name" "conductor") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ .Chart.Name }}-conductor
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "conductor") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ .Chart.Name }}-conductor
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "conductor") | nindent 8 }}
|
||||||
|
app: conductor
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: 'true'
|
||||||
|
prometheus.io/port: '8080'
|
||||||
|
prometheus.io/path: '/metrics'
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
affinity:
|
affinity:
|
||||||
podAntiAffinity:
|
podAntiAffinity:
|
||||||
@@ -29,12 +42,37 @@ spec:
|
|||||||
values:
|
values:
|
||||||
- conductor
|
- conductor
|
||||||
topologyKey: kubernetes.io/hostname
|
topologyKey: kubernetes.io/hostname
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: conductor
|
- name: conductor
|
||||||
{{- include "conductor_resources" . | indent 10 }}
|
image: {{ .Values.image.conductor | quote }}
|
||||||
image: cr.yandex/crphnph9c2s776j32sjd/hrbox-conductor:2.2.0
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
imagePullPolicy: IfNotPresent
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "conductor") | indent 10 }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
lifecycle:
|
||||||
|
preStop:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- "sleep 5"
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
{{- if .Values.app.videoConverter.enabled }}
|
||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor-video") -}}
|
||||||
|
{{/*
|
||||||
|
Конвертация видео.
|
||||||
|
|
||||||
|
Отдельный деплоймент, потому что профиль ресурсов у него другой на порядок:
|
||||||
|
несколько ядер, гигабайты памяти и десятки гигабайт временного диска на под.
|
||||||
|
Смешивать это с обычной обработкой файлов нельзя — либо ресурсы простаивают,
|
||||||
|
либо конвертация видео вытесняет всё остальное. Через nodeSelector и
|
||||||
|
tolerations пул выносится на выделенные ноды.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "file-processor-video") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
minReadySeconds: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 1
|
||||||
|
replicas: {{ .Values.app.replicas.fileProcessorVideo }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "file-processor-video") | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "file-processor-video") | nindent 8 }}
|
||||||
|
app: file-processor
|
||||||
|
annotations:
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.app.videoConverter.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.app.videoConverter.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: file-processor
|
||||||
|
image: {{ .Values.image.fileProcessor | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
env:
|
||||||
|
{{- include "envs_all" . | indent 12 }}
|
||||||
|
- name: VIDEO_CONVERTER_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: IMAGE_RESIZE_MAX_PARALLEL
|
||||||
|
value: {{ .Values.app.fileProcessor.imageResizeMaxParallel | quote }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "fileProcessorVideo") | indent 10 }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor") -}}
|
||||||
|
{{/*
|
||||||
|
Конвертация документов и обработка изображений.
|
||||||
|
|
||||||
|
Конвертация видео здесь всегда выключена: она требует принципиально другого
|
||||||
|
профиля ресурсов и живёт в отдельном деплойменте 09-file-processor-video.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
{{ $name := printf "%s-file-processor" (.Chart.Name) }}
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ $name }}
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: file-processor
|
{{- include "hrbox.labels" (dict "ctx" . "name" "file-processor") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
strategy:
|
strategy:
|
||||||
@@ -13,22 +19,25 @@ spec:
|
|||||||
rollingUpdate:
|
rollingUpdate:
|
||||||
maxSurge: 1
|
maxSurge: 1
|
||||||
maxUnavailable: 1
|
maxUnavailable: 1
|
||||||
replicas: {{ .Values.app.file_processor.replicas }}
|
replicas: {{ .Values.app.replicas.fileProcessor }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ $name }}
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "file-processor") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ $name }}
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "file-processor") | nindent 8 }}
|
||||||
|
app: file-processor
|
||||||
|
annotations:
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: file-processor
|
- name: file-processor
|
||||||
image: {{ .Values.app.file_processor.image }}
|
image: {{ .Values.image.fileProcessor | quote }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
- name: VIDEO_CONVERTER_ENABLED
|
- name: VIDEO_CONVERTER_ENABLED
|
||||||
value: "false"
|
value: "false"
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "fileProcessor") | indent 10 }}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{{- $name := include "hrbox.component" (dict "ctx" . "name" "geonames") -}}
|
||||||
|
{{/*
|
||||||
|
Справочник городов, стран и часовых поясов.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "geonames") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
minReadySeconds: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 1
|
||||||
|
replicas: {{ .Values.app.replicas.geonames }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "geonames") | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "geonames") | nindent 8 }}
|
||||||
|
app: geonames
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: 'true'
|
||||||
|
prometheus.io/port: '9797'
|
||||||
|
prometheus.io/path: "/info"
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
|
spec:
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: geonames
|
||||||
|
image: {{ .Values.image.geonames | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8181
|
||||||
|
env:
|
||||||
|
{{- include "envs_all" . | indent 12 }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "geonames") | indent 10 }}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "geonames") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "geonames") | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8181
|
||||||
|
targetPort: 8181
|
||||||
|
protocol: TCP
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ .Chart.Name }}-geonames
|
|
||||||
labels:
|
|
||||||
app: geonames
|
|
||||||
spec:
|
|
||||||
minReadySeconds: 5
|
|
||||||
strategy:
|
|
||||||
type: RollingUpdate
|
|
||||||
rollingUpdate:
|
|
||||||
maxSurge: 1
|
|
||||||
maxUnavailable: 1
|
|
||||||
replicas: {{ .Values.app.replicas.geonames }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
service: {{ .Chart.Name }}-geonames
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
service: {{ .Chart.Name }}-geonames
|
|
||||||
annotations:
|
|
||||||
prometheus.io/scrape: 'true'
|
|
||||||
prometheus.io/port: '9797'
|
|
||||||
prometheus.io/path: "/info"
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: regsecret
|
|
||||||
containers:
|
|
||||||
- name: geonames
|
|
||||||
{{- include "geonames_resources" . | indent 10}}
|
|
||||||
image: cr.yandex/crphnph9c2s776j32sjd/hrbox-geonames:3.1
|
|
||||||
imagePullPolicy: Always
|
|
||||||
env:
|
|
||||||
{{- include "envs_all" . | indent 12 }}
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ .Chart.Name }}-geonames
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
service: {{ .Chart.Name }}-geonames
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 8181
|
|
||||||
targetPort: 8181
|
|
||||||
protocol: TCP
|
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
|
{{- $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
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-goworker
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: goworker
|
{{- include "hrbox.labels" (dict "ctx" . "name" "goworker") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
strategy:
|
strategy:
|
||||||
@@ -12,39 +20,46 @@ spec:
|
|||||||
rollingUpdate:
|
rollingUpdate:
|
||||||
maxSurge: 1
|
maxSurge: 1
|
||||||
maxUnavailable: 1
|
maxUnavailable: 1
|
||||||
replicas: {{ .Values.app.goworker.replicas }}
|
replicas: {{ .Values.app.replicas.goworker }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ .Chart.Name }}-goworker
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "goworker") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ .Chart.Name }}-goworker
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "goworker") | nindent 8 }}
|
||||||
|
app: goworker
|
||||||
|
annotations:
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
- name: regsecret
|
|
||||||
containers:
|
containers:
|
||||||
- name: goworker
|
- name: goworker
|
||||||
|
image: {{ .Values.image.goworker | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
stdin: true
|
stdin: true
|
||||||
tty: true
|
tty: true
|
||||||
image: {{ .Values.app.goworker.image }}
|
ports:
|
||||||
imagePullPolicy: Always
|
- name: http
|
||||||
|
containerPort: 8686
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "goworker_resources" . | indent 10 }}
|
{{- include "hrbox.resources" (dict "ctx" . "component" "goworker") | indent 10 }}
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-goworker
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "goworker") | nindent 4 }}
|
||||||
annotations:
|
annotations:
|
||||||
prometheus.io/scrape: "true"
|
prometheus.io/scrape: "true"
|
||||||
prometheus.io/port: "8686"
|
prometheus.io/port: "8686"
|
||||||
prometheus.io/path: "/metrics"
|
prometheus.io/path: "/metrics"
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
service: {{ .Chart.Name }}-goworker
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "goworker") | nindent 4 }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: 8686
|
port: 8686
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
{{- $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
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-kedoca
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: kedoca
|
{{- include "hrbox.labels" (dict "ctx" . "name" "kedoca") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
minReadySeconds: 5
|
minReadySeconds: 5
|
||||||
strategy:
|
strategy:
|
||||||
@@ -12,27 +21,42 @@ spec:
|
|||||||
rollingUpdate:
|
rollingUpdate:
|
||||||
maxSurge: 1
|
maxSurge: 1
|
||||||
maxUnavailable: 1
|
maxUnavailable: 1
|
||||||
replicas: {{ .Values.app.kedoca.replicas }}
|
replicas: {{ .Values.app.replicas.kedoca }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
service: {{ .Chart.Name }}-kedoca
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "kedoca") | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
service: {{ .Chart.Name }}-kedoca
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "kedoca") | nindent 8 }}
|
||||||
|
app: kedoca
|
||||||
|
annotations:
|
||||||
|
checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
affinity:
|
||||||
- name: regsecret
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- kedoca
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
containers:
|
containers:
|
||||||
- name: kedoca
|
- name: kedoca
|
||||||
image: {{ .Values.app.kedoca.image }}
|
image: {{ .Values.image.kedoca | quote }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8558
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "envs_kedoca" . | indent 12 }}
|
{{- include "envs_kedoca" . | indent 12 }}
|
||||||
ports:
|
{{- include "hrbox.resources" (dict "ctx" . "component" "kedoca") | indent 10 }}
|
||||||
- containerPort: 8558
|
|
||||||
{{- include "kedoca_resources" . | indent 10 }}
|
|
||||||
{{- if .Values.app.kedoca.secretName }}
|
{{- if .Values.app.kedoca.secretName }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: ca-cert
|
- name: ca-cert
|
||||||
@@ -42,22 +66,22 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: ca-cert
|
- name: ca-cert
|
||||||
secret:
|
secret:
|
||||||
secretName: {{ .Values.app.kedoca.secretName }}
|
secretName: {{ .Values.app.kedoca.secretName | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-kedoca
|
name: {{ $name }}
|
||||||
labels:
|
labels:
|
||||||
app: kedoca
|
{{- include "hrbox.labels" (dict "ctx" . "name" "kedoca") | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
selector:
|
selector:
|
||||||
service: {{ .Chart.Name }}-kedoca
|
{{- include "hrbox.selectorLabels" (dict "ctx" . "name" "kedoca") | nindent 4 }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: 8558
|
port: 8558
|
||||||
targetPort: 8558
|
targetPort: 8558
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
type: ClusterIP
|
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
{{- $cfg := .Values.app.nanabushPlayer.deployment }}
|
||||||
|
{{- if $cfg.enabled }}
|
||||||
|
{{- $name := include "nanabushPlayer.serviceName" . -}}
|
||||||
|
{{- $secretName := include "nanabushPlayer.secretName" . -}}
|
||||||
|
{{/*
|
||||||
|
Nanabush Player — проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5,
|
||||||
|
xAPI, нативные курсы, тесты и эссе.
|
||||||
|
|
||||||
|
Как это работает:
|
||||||
|
* HRBox выпускает launch-тикет, подписанный общим ключом ticketHs256,
|
||||||
|
и отправляет браузер на https://<домен>{{ include "nanabushPlayer.basePath" . }}/start;
|
||||||
|
* плеер проверяет подпись, audience и issuer, заводит свою сессию;
|
||||||
|
* за пакетом курса и для отправки отчётов плеер ходит обратно в HRBox по
|
||||||
|
внутрикластерному адресу — только он и разрешён в allowlist;
|
||||||
|
* учебные события копятся в Redis Streams и досылаются форвардером,
|
||||||
|
поэтому короткая недоступность HRBox не теряет прогресс.
|
||||||
|
|
||||||
|
Ограничения on-premise описаны в values.yaml рядом с app.nanabushPlayer.
|
||||||
|
Полный контракт — docs/nanabush-player-deployment.md в репозитории HRBox.
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nanabush-player") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ $cfg.replicas }}
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
minReadySeconds: 5
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
service: {{ $name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
service: {{ $name }}
|
||||||
|
app: nanabush-player
|
||||||
|
{{- if $cfg.metrics.enabled }}
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: {{ $cfg.metrics.port | quote }}
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
# Плееру не нужен доступ к API Kubernetes, а исполняет он содержимое,
|
||||||
|
# загруженное пользователями, — поэтому токен не монтируется, процесс
|
||||||
|
# не root, корневая файловая система только на чтение.
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
terminationGracePeriodSeconds: {{ $cfg.terminationGracePeriodSeconds }}
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65532
|
||||||
|
runAsGroup: 65532
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: player
|
||||||
|
image: {{ .Values.image.nanabushPlayer | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.app.nanabushPlayer.service.port }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- if $cfg.metrics.enabled }}
|
||||||
|
- name: metrics
|
||||||
|
containerPort: {{ $cfg.metrics.port }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
|
env:
|
||||||
|
- name: PLAYER_HTTP_ADDR
|
||||||
|
value: {{ printf ":%d" (.Values.app.nanabushPlayer.service.port | int) | quote }}
|
||||||
|
- name: PLAYER_METRICS_ADDR
|
||||||
|
{{- if $cfg.metrics.enabled }}
|
||||||
|
value: {{ printf ":%d" ($cfg.metrics.port | int) | quote }}
|
||||||
|
{{- else }}
|
||||||
|
value: "off"
|
||||||
|
{{- end }}
|
||||||
|
# Путь монтирования не входит в audience: audience — это origin
|
||||||
|
# браузера, иначе сломались бы проверки на стороне клиента.
|
||||||
|
- name: PLAYER_BASE_PATH
|
||||||
|
value: {{ include "nanabushPlayer.basePath" . | quote }}
|
||||||
|
- name: PLAYER_AUDIENCE
|
||||||
|
value: {{ include "nanabushPlayer.publicOrigin" . | quote }}
|
||||||
|
- name: PLAYER_ALLOWED_ISSUERS
|
||||||
|
value: {{ include "nanabushPlayer.issuer" . | quote }}
|
||||||
|
- name: PLAYER_HS256_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ $secretName | quote }}
|
||||||
|
key: {{ required "Нужно имя ключа ticketHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.ticketHs256 | quote }}
|
||||||
|
{{- with $cfg.existingSecret.keys.ticketKeys }}
|
||||||
|
# Кольцо ключей: подпись проверяется ключом, выбранным по kid из
|
||||||
|
# тикета, поэтому во время ротации работают обе версии HRBox.
|
||||||
|
- name: PLAYER_TICKET_KEYS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ $secretName | quote }}
|
||||||
|
key: {{ . | quote }}
|
||||||
|
{{- with $cfg.ticket.keylessAcceptedUntil }}
|
||||||
|
# Только на первую раскатку: принимать тикеты без kid от версии
|
||||||
|
# HRBox, которая ещё не знает про кольцо ключей.
|
||||||
|
- name: PLAYER_TICKET_KEYLESS_UNTIL
|
||||||
|
value: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
- name: PLAYER_SESSION_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ $secretName | quote }}
|
||||||
|
key: {{ required "Нужно имя ключа sessionHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.sessionHs256 | quote }}
|
||||||
|
- name: PLAYER_SESSION_ISSUER
|
||||||
|
value: {{ include "nanabushPlayer.publicOrigin" . | quote }}
|
||||||
|
- name: PLAYER_SESSION_TTL
|
||||||
|
value: {{ $cfg.ticket.sessionTtl | quote }}
|
||||||
|
- name: PLAYER_MAX_TICKET_LIFETIME
|
||||||
|
value: {{ $cfg.ticket.maxLifetime | quote }}
|
||||||
|
- name: REDIS_URL
|
||||||
|
{{- if $cfg.redis.urlFromExistingSecret }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ $secretName | quote }}
|
||||||
|
key: {{ required "Нужно имя ключа redisUrl, когда redis.urlFromExistingSecret: true" $cfg.existingSecret.keys.redisUrl | quote }}
|
||||||
|
{{- else }}
|
||||||
|
value: {{ include "nanabushPlayer.redisUrl" . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
- name: PLAYER_REDIS_KEY_PREFIX
|
||||||
|
value: {{ include "nanabushPlayer.redisKeyPrefix" . | quote }}
|
||||||
|
# Обратный канал: единственный разрешённый адрес — внутрикластерный
|
||||||
|
# HRBox. Так пакет курса не сможет заставить плеер сходить наружу.
|
||||||
|
- name: PLAYER_PACKAGE_FETCH_SCHEMES
|
||||||
|
value: {{ $cfg.backchannel.schemes | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_FETCH_ALLOWLIST
|
||||||
|
value: {{ include "nanabushPlayer.backchannelHost" . | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_FETCH_ALLOW_PRIVATE_IPS
|
||||||
|
value: {{ $cfg.backchannel.allowPrivateIps | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_FETCH_MAX_BYTES
|
||||||
|
value: {{ $cfg.backchannel.packageFetchMaxBytes | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_CONTENT_MAX_BYTES
|
||||||
|
value: {{ $cfg.backchannel.packageContentMaxBytes | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_FETCH_TIMEOUT
|
||||||
|
value: {{ $cfg.backchannel.packageFetchTimeout | quote }}
|
||||||
|
- name: PLAYER_PACKAGE_STREAM_TIMEOUT
|
||||||
|
value: {{ $cfg.backchannel.packageStreamTimeout | quote }}
|
||||||
|
- name: PLAYER_REPORT_ENDPOINT_SCHEMES
|
||||||
|
value: {{ $cfg.backchannel.schemes | quote }}
|
||||||
|
- name: PLAYER_REPORT_ENDPOINT_ALLOWLIST
|
||||||
|
value: {{ include "nanabushPlayer.backchannelHost" . | quote }}
|
||||||
|
- name: PLAYER_REPORT_ENDPOINT_ALLOW_PRIVATE_IPS
|
||||||
|
value: {{ $cfg.backchannel.allowPrivateIps | quote }}
|
||||||
|
- name: PLAYER_REPORT_ENDPOINT_TIMEOUT
|
||||||
|
value: {{ $cfg.backchannel.reportTimeout | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_QUEUE_SIZE
|
||||||
|
value: {{ $cfg.forwarder.queueSize | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_WORKERS
|
||||||
|
value: {{ $cfg.forwarder.workers | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_MAX_ATTEMPTS
|
||||||
|
value: {{ $cfg.forwarder.maxAttempts | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_INITIAL_BACKOFF
|
||||||
|
value: {{ $cfg.forwarder.initialBackoff | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_MAX_BACKOFF
|
||||||
|
value: {{ $cfg.forwarder.maxBackoff | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_PER_ATTEMPT_TIMEOUT
|
||||||
|
value: {{ $cfg.forwarder.perAttemptTimeout | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_DEAD_LETTER_SIZE
|
||||||
|
value: {{ $cfg.forwarder.deadLetterSize | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_CLAIM_IDLE
|
||||||
|
value: {{ $cfg.forwarder.claimIdle | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_POLL_INTERVAL
|
||||||
|
value: {{ $cfg.forwarder.pollInterval | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_RATE_LIMIT_EVENTS_PER_MINUTE
|
||||||
|
value: {{ $cfg.forwarder.rateLimitEventsPerMinute | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_RATE_LIMIT_BATCHES_PER_MINUTE
|
||||||
|
value: {{ $cfg.forwarder.rateLimitBatchesPerMinute | quote }}
|
||||||
|
- name: PLAYER_FORWARDER_RATE_LIMIT_PAYLOAD_BYTES_PER_MINUTE
|
||||||
|
value: {{ $cfg.forwarder.rateLimitPayloadBytesPerMinute | quote }}
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: {{ $cfg.log.level | quote }}
|
||||||
|
- name: LOG_FORMAT
|
||||||
|
value: {{ $cfg.log.format | quote }}
|
||||||
|
- name: PLAYER_DEV_HMR
|
||||||
|
value: "false"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ $cfg.probes.readiness.path }}
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: {{ $cfg.probes.readiness.initialDelaySeconds }}
|
||||||
|
periodSeconds: {{ $cfg.probes.readiness.periodSeconds }}
|
||||||
|
timeoutSeconds: {{ $cfg.probes.readiness.timeoutSeconds }}
|
||||||
|
failureThreshold: {{ $cfg.probes.readiness.failureThreshold }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ $cfg.probes.liveness.path }}
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: {{ $cfg.probes.liveness.initialDelaySeconds }}
|
||||||
|
periodSeconds: {{ $cfg.probes.liveness.periodSeconds }}
|
||||||
|
timeoutSeconds: {{ $cfg.probes.liveness.timeoutSeconds }}
|
||||||
|
failureThreshold: {{ $cfg.probes.liveness.failureThreshold }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "nanabushPlayer") | indent 10 }}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ $name }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "nanabush-player") | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
service: {{ $name }}
|
||||||
|
# Порт метрик наружу намеренно не публикуется: содержимое курсов приходит
|
||||||
|
# с того же origin и не должно читать эксплуатационные метрики.
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ .Values.app.nanabushPlayer.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
@@ -1,35 +1,70 @@
|
|||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- $player := .Values.app.nanabushPlayer -}}
|
||||||
|
{{/*
|
||||||
|
Единая точка входа.
|
||||||
|
|
||||||
|
Все три маршрута живут на одном домене, потому что в on-premise выделенных
|
||||||
|
поддоменов под сервисы нет:
|
||||||
|
|
||||||
|
{{ include "nanabushPlayer.basePath" . }}/ -> плеер учебных материалов
|
||||||
|
/chatbox/ -> вебсокеты чата
|
||||||
|
/ -> само приложение
|
||||||
|
|
||||||
|
Длинные таймауты нужны вебсокетам и плееру: и то и другое держит соединение
|
||||||
|
открытым, пока пользователь работает.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-ingress
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "ingress") }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: {{ .Values.ingress.className }}
|
# Аннотация класса устарела в пользу spec.ingressClassName ниже, но
|
||||||
nginx.org/websocket-services: {{ .Chart.Name }}-chatbox
|
# оставлена: контроллеры старше ingress-nginx 1.0 понимают только её.
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
# Когда заданы обе, современный контроллер использует ingressClassName.
|
||||||
|
kubernetes.io/ingress.class: {{ .Values.ingress.className | quote }}
|
||||||
|
# Аннотация NGINX Inc.; ingress-nginx её игнорирует, вебсокеты у него
|
||||||
|
# работают за счёт proxy-read-timeout выше.
|
||||||
|
nginx.org/websocket-services: {{ include "hrbox.component" (dict "ctx" . "name" "chatbox") | quote }}
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.maxBodySize | quote }}
|
||||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
proxy_set_header X-Forwarded-For "$remote_addr, $server_addr";
|
proxy_set_header X-Forwarded-For "$remote_addr, $server_addr";
|
||||||
proxy_set_header X-Forwarded-Port $pass_port;
|
proxy_set_header X-Forwarded-Port $pass_port;
|
||||||
proxy_set_header X-Forwarded-Proto $pass_access_scheme;
|
proxy_set_header X-Forwarded-Proto $pass_access_scheme;
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: {{ .Values.ingress.className | quote }}
|
||||||
rules:
|
rules:
|
||||||
- host: {{ .Values.ingress.host | quote }} # something.example.com
|
- host: {{ .Values.ingress.host | quote }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
|
{{- if and $player.ingress.enabled $player.deployment.enabled }}
|
||||||
|
- path: {{ include "nanabushPlayer.basePath" . | quote }}
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "nanabushPlayer.serviceName" . }}
|
||||||
|
port:
|
||||||
|
number: {{ $player.service.port }}
|
||||||
|
{{- end }}
|
||||||
- path: /chatbox/
|
- path: /chatbox/
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ .Chart.Name }}-chatbox
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "chatbox") }}
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ .Chart.Name }}-web
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "web") }}
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
{{- if .Values.ingress.tls.enabled }}
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
@@ -38,3 +73,4 @@ spec:
|
|||||||
- {{ .Values.ingress.host | quote }}
|
- {{ .Values.ingress.host | quote }}
|
||||||
secretName: {{ .Values.ingress.tls.secretName | quote }}
|
secretName: {{ .Values.ingress.tls.secretName | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
|
{{/*
|
||||||
|
Миграции базы данных.
|
||||||
|
|
||||||
|
Хук post-install/post-upgrade с весом 10: выполняется после того, как Helm
|
||||||
|
создал ConfigMap и Secret, но до джобы обновления кластера (вес 20).
|
||||||
|
Имя джобы содержит номер ревизии релиза, поэтому в истории видно, какой
|
||||||
|
выкат её запускал, а повторный upgrade не спотыкается об уже существующий
|
||||||
|
объект.
|
||||||
|
*/}}
|
||||||
---
|
---
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-migrate
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "migrate") }}-{{ .Release.Revision }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 4 }}
|
||||||
annotations:
|
annotations:
|
||||||
"helm.sh/hook": post-install,post-upgrade
|
"helm.sh/hook": post-install,post-upgrade
|
||||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
|
"helm.sh/hook-delete-policy": before-hook-creation
|
||||||
"helm.sh/hook-weight": "10"
|
"helm.sh/hook-weight": "10"
|
||||||
spec:
|
spec:
|
||||||
ttlSecondsAfterFinished: 100
|
ttlSecondsAfterFinished: 3600
|
||||||
|
backoffLimit: 6
|
||||||
template:
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 8 }}
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
restartPolicy: OnFailure
|
||||||
- name: regsecret
|
terminationGracePeriodSeconds: 5
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: wait-postgres
|
- name: wait-postgres
|
||||||
image: postgres:14-alpine
|
image: {{ .Values.image.postgresClient | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command:
|
command:
|
||||||
- bash
|
- bash
|
||||||
- /wait.sh
|
- /wait.sh
|
||||||
@@ -27,16 +44,14 @@ spec:
|
|||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
containers:
|
containers:
|
||||||
- name: run-migrations
|
- name: run-migrations
|
||||||
image: {{ .Values.app.image }}
|
image: {{ .Values.image.hrbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
command: ["php", "yii", "migrate", "--interactive=0"]
|
command: ["php", "yii", "migrate", "--interactive=0"]
|
||||||
env:
|
env:
|
||||||
{{- include "envs_all" . | indent 12 }}
|
{{- include "envs_all" . | indent 12 }}
|
||||||
{{- include "jobs_resources" . | indent 10 }}
|
{{- include "hrbox.resources" (dict "ctx" . "component" "jobs") | indent 10 }}
|
||||||
volumes:
|
volumes:
|
||||||
- name: configs
|
- name: configs
|
||||||
configMap:
|
configMap:
|
||||||
name: job-scripts
|
name: job-scripts
|
||||||
defaultMode: 420
|
defaultMode: 420
|
||||||
restartPolicy: OnFailure
|
|
||||||
terminationGracePeriodSeconds: 5
|
|
||||||
backoffLimit: 6
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: batch/v1
|
|
||||||
kind: Job
|
|
||||||
metadata:
|
|
||||||
name: {{ .Chart.Name }}-update-cluster
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": post-install,post-upgrade
|
|
||||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
|
|
||||||
"helm.sh/hook-weight": "20"
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: regsecret
|
|
||||||
containers:
|
|
||||||
- name: run-update
|
|
||||||
image: {{ .Values.app.image }}
|
|
||||||
command: [ "php", "yii", "util/update-cluster" ]
|
|
||||||
env:
|
|
||||||
{{- include "envs_all" . | indent 12 }}
|
|
||||||
{{- include "jobs_resources" . | indent 10 }}
|
|
||||||
restartPolicy: OnFailure
|
|
||||||
terminationGracePeriodSeconds: 5
|
|
||||||
backoffLimit: 6
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{{/*
|
||||||
|
Обновление данных кластера после миграций: пересчёт справочников,
|
||||||
|
перерегистрация фоновых задач, обновление настроек арендаторов.
|
||||||
|
|
||||||
|
Вес 20 — строго после джобы миграций (вес 10).
|
||||||
|
*/}}
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: {{ include "hrbox.component" (dict "ctx" . "name" "update-cluster") }}-{{ .Release.Revision }}
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": post-install,post-upgrade
|
||||||
|
"helm.sh/hook-delete-policy": before-hook-creation
|
||||||
|
"helm.sh/hook-weight": "20"
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
|
backoffLimit: 6
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
terminationGracePeriodSeconds: 5
|
||||||
|
{{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: run-update
|
||||||
|
image: {{ .Values.image.hrbox | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
command: ["php", "yii", "util/update-cluster"]
|
||||||
|
env:
|
||||||
|
{{- include "envs_all" . | indent 12 }}
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "jobs") | indent 10 }}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
HRBox {{ .Chart.AppVersion }} — релиз «{{ .Release.Name }}» в namespace «{{ .Release.Namespace }}».
|
||||||
|
|
||||||
|
Адрес: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.ingress.host }}
|
||||||
|
|
||||||
|
Что происходит прямо сейчас
|
||||||
|
---------------------------
|
||||||
|
1. Поднимаются поды приложения и сервисов.
|
||||||
|
2. Джоба {{ include "hrbox.component" (dict "ctx" . "name" "migrate") }}-{{ .Release.Revision }} накатывает миграции базы.
|
||||||
|
3. Джоба {{ include "hrbox.component" (dict "ctx" . "name" "update-cluster") }}-{{ .Release.Revision }} обновляет данные кластера.
|
||||||
|
|
||||||
|
Дождитесь завершения миграций:
|
||||||
|
|
||||||
|
kubectl -n {{ .Release.Namespace }} wait --for=condition=complete --timeout=30m \
|
||||||
|
job/{{ include "hrbox.component" (dict "ctx" . "name" "migrate") }}-{{ .Release.Revision }}
|
||||||
|
|
||||||
|
Проверка состояния
|
||||||
|
------------------
|
||||||
|
kubectl -n {{ .Release.Namespace }} get pods
|
||||||
|
kubectl -n {{ .Release.Namespace }} logs -l app=web -c fpm --tail=100
|
||||||
|
|
||||||
|
{{ if .Release.IsInstall -}}
|
||||||
|
Первый запуск: создайте администратора
|
||||||
|
--------------------------------------
|
||||||
|
kubectl -n {{ .Release.Namespace }} exec -it \
|
||||||
|
deploy/{{ include "hrbox.component" (dict "ctx" . "name" "worker") }} -- \
|
||||||
|
php yii user/create-admin admin@example.com --tenant-id=1
|
||||||
|
|
||||||
|
{{ end -}}
|
||||||
|
{{- if .Values.app.nanabushPlayer.deployment.enabled }}
|
||||||
|
Nanabush Player
|
||||||
|
---------------
|
||||||
|
Смонтирован на {{ include "nanabushPlayer.publicOrigin" . }}{{ include "nanabushPlayer.basePath" . }}
|
||||||
|
Версия: {{ include "nanabushPlayer.version" . }}
|
||||||
|
|
||||||
|
Проверить, что плеер отвечает сам, а не HRBox через общий маршрут
|
||||||
|
(настоящий плеер возвращает JSON, HTML означает, что запрос ушёл в HRBox):
|
||||||
|
|
||||||
|
kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "hrbox.component" (dict "ctx" . "name" "worker") }} -- \
|
||||||
|
curl -s http://{{ include "nanabushPlayer.serviceName" . }}:{{ .Values.app.nanabushPlayer.service.port }}{{ .Values.app.nanabushPlayer.deployment.probes.readiness.path }}
|
||||||
|
|
||||||
|
Служебные пути (/healthz, /readyz, метрики) живут в корне и не зависят от
|
||||||
|
basePath; под basePath публикуются /start, статика и API плеера.
|
||||||
|
|
||||||
|
{{- if not .Values.app.nanabushPlayer.enabled }}
|
||||||
|
|
||||||
|
ВНИМАНИЕ: плеер развёрнут, но выключен для пользователей
|
||||||
|
(app.nanabushPlayer.enabled: false). Учебные материалы открываются старым
|
||||||
|
проигрывателем.
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
Nanabush Player выключен (app.nanabushPlayer.deployment.enabled: false).
|
||||||
|
Учебные материалы открываются старым проигрывателем.
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ if not .Values.dragonfly.enabled -}}
|
||||||
|
Dragonfly выключен — проверьте, что app.redis указывает на внешний Redis:
|
||||||
|
{{ include "hrbox.redisHost" . }}:{{ .Values.app.redis.port }}
|
||||||
|
{{ end -}}
|
||||||
|
{{- if not .Values.nats.enabled }}
|
||||||
|
NATS выключен — проверьте, что app.nats.url указывает на внешний брокер:
|
||||||
|
{{ include "hrbox.natsUrl" . }}
|
||||||
|
{{ end -}}
|
||||||
|
{{- if eq .Values.app.debug "1" }}
|
||||||
|
|
||||||
|
ВНИМАНИЕ: включён режим отладки (app.debug: "1"). Для production верните "0".
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.ingress.tls.enabled }}
|
||||||
|
|
||||||
|
ВНИМАНИЕ: TLS выключен (ingress.tls.enabled: false). Приложение отдаёт
|
||||||
|
cookie сессий по открытому HTTP.
|
||||||
|
{{- end }}
|
||||||
+254
-165
@@ -1,64 +1,66 @@
|
|||||||
|
{{/*
|
||||||
|
============================================================================
|
||||||
|
Переменные окружения 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
|
||||||
value: {{ .Values.app.defaultTenantHostname | quote }}
|
value: {{ .Values.app.defaultTenantHostname | quote }}
|
||||||
- name: APP_ENV
|
- name: APP_ENV
|
||||||
value: "prod"
|
value: "prod"
|
||||||
|
- name: APP_PORT
|
||||||
|
value: "80"
|
||||||
- name: YII_DEBUG
|
- name: YII_DEBUG
|
||||||
value: {{ .Values.app.debug | quote }}
|
value: {{ .Values.app.debug | quote }}
|
||||||
- name: YII_TRACE_LEVEL
|
- name: YII_TRACE_LEVEL
|
||||||
value: "0"
|
value: "0"
|
||||||
- name: APP_COOKIE_VALIDATION_KEY
|
|
||||||
value: {{ .Values.secrets.cookieValidationKey | quote }}
|
|
||||||
- name: APP_ENCRYPTION_KEY
|
|
||||||
value: {{ .Values.secrets.encryptionKey | quote }}
|
|
||||||
- name: APP_PORT
|
|
||||||
value: "80"
|
|
||||||
- name: AUTH_HOST
|
- name: AUTH_HOST
|
||||||
value: {{ .Values.app.authHost | quote }}
|
value: {{ .Values.app.authHost | quote }}
|
||||||
|
- name: SINGLE_TENANT
|
||||||
|
value: {{ .Values.app.singleTenant | quote }}
|
||||||
|
- name: FORCE_HTTPS
|
||||||
|
value: {{ .Values.app.forceHttps | quote }}
|
||||||
|
- name: ENABLE_FRONTEND
|
||||||
|
value: {{ .Values.app.front2 | quote }}
|
||||||
- name: LAUNCHPAD_TOPICS
|
- name: LAUNCHPAD_TOPICS
|
||||||
value: {{ .Values.app.launchpad.topics | quote }}
|
value: {{ .Values.app.launchpad.topics | quote }}
|
||||||
- name: SMS_SENDER_NAME
|
- name: SMS_SENDER_NAME
|
||||||
value: "HRBOX"
|
value: {{ .Values.app.sms.senderName | quote }}
|
||||||
- name: ENABLE_FRONTEND
|
- name: IFRAME_FULL_ACCESS
|
||||||
value: {{ .Values.app.front2 | quote }}
|
value: {{ .Values.app.security.iframeFullAccess | quote }}
|
||||||
{{- if .Values.app.custom_policy }}
|
- name: LDAPTLS_REQCERT
|
||||||
|
value: "never"
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "APP_COOKIE_VALIDATION_KEY" "key" "cookie-validation-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 }}
|
||||||
|
|
||||||
|
# --- Своя политика конфиденциальности -------------------------------------
|
||||||
- name: ALLOW_CUSTOM_POLICY_URLS
|
- name: ALLOW_CUSTOM_POLICY_URLS
|
||||||
value: {{ .Values.app.custom_policy.enabled | default false | quote }}
|
value: {{ .Values.app.customPolicy.enabled | quote }}
|
||||||
{{- if .Values.app.custom_policy.enabled }}
|
{{- if .Values.app.customPolicy.enabled }}
|
||||||
- name: CUSTOM_PRIVACY_POLICY_URL
|
- name: CUSTOM_PRIVACY_POLICY_URL
|
||||||
value: {{ .Values.app.custom_policy.privacy_url | default "https://hrbox.io/policy" | quote }}
|
value: {{ .Values.app.customPolicy.privacyUrl | default "https://hrbox.io/policy" | quote }}
|
||||||
- name: CUSTOM_TERMS_URL
|
- name: CUSTOM_TERMS_URL
|
||||||
value: {{ .Values.app.custom_policy.terms_url | default "https://hrbox.io/terms" | quote }}
|
value: {{ .Values.app.customPolicy.termsUrl | default "https://hrbox.io/terms" | quote }}
|
||||||
{{- end }}
|
|
||||||
{{- else }}
|
{{- else }}
|
||||||
- name: ALLOW_CUSTOM_POLICY_URLS
|
- name: CUSTOM_PRIVACY_POLICY_URL
|
||||||
value: "false"
|
value: ""
|
||||||
|
- name: CUSTOM_TERMS_URL
|
||||||
|
value: ""
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
# NATS
|
# --- PostgreSQL -----------------------------------------------------------
|
||||||
- name: BROKER
|
|
||||||
value: "nats"
|
|
||||||
- name: NATS_URL
|
|
||||||
value: {{ .Values.app.nats.url | quote }}
|
|
||||||
|
|
||||||
# Sentry (опционально)
|
|
||||||
{{- if .Values.app.sentry.dsn }}
|
|
||||||
- name: SENTRY_DSN
|
|
||||||
value: {{ .Values.app.sentry.dsn | quote }}
|
|
||||||
- name: SENTRY_DSN_LAUNCHPAD
|
|
||||||
value: {{ .Values.app.sentry.dsn | quote }}
|
|
||||||
- name: SENTRY_DSN_CONDUCTOR
|
|
||||||
value: {{ .Values.app.sentry.dsn | quote }}
|
|
||||||
- name: SENTRY_ENVIRONMENT
|
|
||||||
value: {{ .Values.app.sentry.environment | quote }}
|
|
||||||
- name: SENTRY_RELEASE
|
|
||||||
value: {{ .Values.app.image | quote }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# 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
|
||||||
@@ -67,74 +69,37 @@
|
|||||||
value: {{ .Values.app.postgres.database | quote }}
|
value: {{ .Values.app.postgres.database | quote }}
|
||||||
- name: POSTGRES_USER
|
- name: POSTGRES_USER
|
||||||
value: {{ .Values.app.postgres.user | quote }}
|
value: {{ .Values.app.postgres.user | quote }}
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
value: {{ .Values.app.postgres.password | quote }}
|
|
||||||
- name: POSTGRES_CITUS
|
- name: POSTGRES_CITUS
|
||||||
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 }}
|
||||||
|
|
||||||
# Redis
|
# --- Redis / Dragonfly ----------------------------------------------------
|
||||||
- name: REDIS_HOST
|
- name: REDIS_HOST
|
||||||
value: {{ .Values.app.redis.host | quote }}
|
value: {{ include "hrbox.redisHost" . | quote }}
|
||||||
- name: REDIS_PORT
|
- name: REDIS_PORT
|
||||||
value: {{ .Values.app.redis.port | quote }}
|
value: {{ .Values.app.redis.port | quote }}
|
||||||
- name: REDIS_CONNECTION
|
- name: REDIS_CONNECTION
|
||||||
value: {{ printf "tcp://%s:%s" .Values.app.redis.host .Values.app.redis.port | quote }}
|
value: {{ printf "tcp://%s:%s" (include "hrbox.redisHost" .) (.Values.app.redis.port | toString) | quote }}
|
||||||
|
|
||||||
# Yandex API (опционально)
|
# --- NATS -----------------------------------------------------------------
|
||||||
{{- if .Values.app.yandex.clientId }}
|
- name: BROKER
|
||||||
- name: YANDEX_CLIENT_ID
|
value: "nats"
|
||||||
value: {{ .Values.app.yandex.clientId | quote }}
|
- name: NATS_URL
|
||||||
- name: YANDEX_CLIENT_PASSWORD
|
value: {{ include "hrbox.natsUrl" . | quote }}
|
||||||
value: {{ .Values.app.yandex.clientPassword | quote }}
|
# Число реплик JetStream-стримов должно совпадать с размером кластера NATS,
|
||||||
|
# иначе стрим создаётся в одном экземпляре и теряется вместе со своей нодой.
|
||||||
|
- name: NATS_STREAM_REPLICAS
|
||||||
|
{{- if and .Values.nats.enabled .Values.nats.cluster.enabled }}
|
||||||
|
value: {{ .Values.nats.cluster.replicas | quote }}
|
||||||
|
{{- else }}
|
||||||
|
value: "1"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
# Slack (опционально)
|
# --- S3 -------------------------------------------------------------------
|
||||||
{{- if .Values.app.slack.clientId }}
|
|
||||||
- name: SLACK_APP_CLIENT_ID
|
|
||||||
value: {{ .Values.app.slack.clientId | quote }}
|
|
||||||
- name: SLACK_APP_CLIENT_SECRET
|
|
||||||
value: {{ .Values.app.slack.clientSecret | quote }}
|
|
||||||
- name: SLACK_APP_VERIFICATION_TOKEN
|
|
||||||
value: {{ .Values.app.slack.verificationToken | quote }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# Email
|
|
||||||
- name: SENDER_ENV
|
|
||||||
value: "PROD"
|
|
||||||
- name: SENDER_DEV_EMAIL
|
|
||||||
value: ""
|
|
||||||
- name: SENDGRID_API_KEY
|
|
||||||
value: ""
|
|
||||||
|
|
||||||
# SMTP
|
|
||||||
- name: SMTP_USERNAME
|
|
||||||
value: {{ .Values.app.smtp.user | quote }}
|
|
||||||
- name: SMTP_PASSWORD
|
|
||||||
value: {{ .Values.app.smtp.password | quote }}
|
|
||||||
- name: SMTP_HOST
|
|
||||||
value: {{ .Values.app.smtp.host | quote }}
|
|
||||||
- name: SMTP_PORT
|
|
||||||
value: {{ .Values.app.smtp.port | quote }}
|
|
||||||
- name: SMTP_FROM
|
|
||||||
value: {{ .Values.app.smtp.from | quote }}
|
|
||||||
- name: SMTP_FROM_HOST
|
|
||||||
value: {{ .Values.app.smtp.fromHost | quote }}
|
|
||||||
- name: SMTP_SECURITY_TYPE
|
|
||||||
value: {{ .Values.app.smtp.securityType | quote }}
|
|
||||||
- name: SMTP_SSL_ALLOW_SELF_SIGNED
|
|
||||||
value: {{ .Values.app.smtp.sslAllowSelfSigned | quote }}
|
|
||||||
- name: SMTP_SSL_VERIFY_PEER
|
|
||||||
value: {{ .Values.app.smtp.sslVerifyPeer | quote }}
|
|
||||||
|
|
||||||
# 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
|
||||||
value: {{ .Values.app.s3.bucket | quote }}
|
value: {{ .Values.app.s3.bucket | quote }}
|
||||||
- name: S3_KEY
|
|
||||||
value: {{ .Values.app.s3.key | quote }}
|
|
||||||
- name: S3_SECRET
|
|
||||||
value: {{ .Values.app.s3.secret | quote }}
|
|
||||||
- name: S3_PREFIX
|
- name: S3_PREFIX
|
||||||
value: {{ .Values.app.s3.prefix | quote }}
|
value: {{ .Values.app.s3.prefix | quote }}
|
||||||
- name: S3_REGION
|
- name: S3_REGION
|
||||||
@@ -145,40 +110,120 @@
|
|||||||
value: {{ .Values.app.s3.pathStyle | quote }}
|
value: {{ .Values.app.s3.pathStyle | quote }}
|
||||||
- name: S3_MULTIPART
|
- name: S3_MULTIPART
|
||||||
value: {{ .Values.app.s3.multipart | quote }}
|
value: {{ .Values.app.s3.multipart | quote }}
|
||||||
|
{{- 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 }}
|
||||||
|
|
||||||
# Hub
|
# --- Почта ----------------------------------------------------------------
|
||||||
- name: HUB_URL
|
- name: SENDER_ENV
|
||||||
value: {{ .Values.app.hub.url | quote }}
|
value: "PROD"
|
||||||
- name: HUB_TOKEN
|
- name: SENDER_DEV_EMAIL
|
||||||
value: {{ .Values.app.hub.token | quote }}
|
value: ""
|
||||||
|
- name: SMTP_HOST
|
||||||
|
value: {{ .Values.app.smtp.host | quote }}
|
||||||
|
- name: SMTP_PORT
|
||||||
|
value: {{ .Values.app.smtp.port | quote }}
|
||||||
|
- name: SMTP_USERNAME
|
||||||
|
value: {{ .Values.app.smtp.user | quote }}
|
||||||
|
- name: SMTP_FROM
|
||||||
|
value: {{ .Values.app.smtp.from | quote }}
|
||||||
|
- name: SMTP_FROM_HOST
|
||||||
|
value: {{ .Values.app.smtp.fromHost | quote }}
|
||||||
|
- name: SMTP_SECURITY_TYPE
|
||||||
|
value: {{ .Values.app.smtp.securityType | quote }}
|
||||||
|
- name: SMTP_SSL_ALLOW_SELF_SIGNED
|
||||||
|
value: {{ .Values.app.smtp.sslAllowSelfSigned | quote }}
|
||||||
|
- name: SMTP_SSL_VERIFY_PEER
|
||||||
|
value: {{ .Values.app.smtp.sslVerifyPeer | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "SMTP_PASSWORD" "key" "smtp-password") | nindent 0 }}
|
||||||
|
|
||||||
# Geonames
|
# --- Внутренние сервисы ---------------------------------------------------
|
||||||
- name: GEONAMES_URL
|
- name: GEONAMES_URL
|
||||||
value: {{ .Values.app.geonames.url | quote }}
|
value: {{ include "hrbox.geonamesUrl" . | quote }}
|
||||||
|
- name: GOWORKER_URL
|
||||||
# Статические переменные
|
value: {{ include "hrbox.goworkerUrl" . | quote }}
|
||||||
|
- name: GOWORKER_PUSH_MIN_INTERVAL
|
||||||
|
value: {{ .Values.app.goworker.pushMinInterval | quote }}
|
||||||
|
- name: GOWORKER_SENTRY_DSN
|
||||||
|
value: {{ .Values.app.sentry.goworkerDsn | quote }}
|
||||||
|
- name: KEDOCA_URL
|
||||||
|
value: {{ include "hrbox.kedocaUrl" . | quote }}
|
||||||
|
- name: TSP_URL
|
||||||
|
value: {{ .Values.app.kedoca.tspUrl | quote }}
|
||||||
|
- name: APP_INTERNAL_URL
|
||||||
|
value: {{ .Values.app.videoConverter.internalUrl | default (printf "http://%s" (include "hrbox.component" (dict "ctx" . "name" "web"))) | quote }}
|
||||||
- name: CONDUCTOR_TOPIC_CONDUCTOR
|
- name: CONDUCTOR_TOPIC_CONDUCTOR
|
||||||
value: "conductor"
|
value: "conductor"
|
||||||
- name: CONDUCTOR_TOPIC_LAUNCHER
|
- name: CONDUCTOR_TOPIC_LAUNCHER
|
||||||
value: "launcher"
|
value: "launcher"
|
||||||
- name: GOOGLE_CLIENT_ID
|
- name: PYTHONUNBUFFERED
|
||||||
value: "692956564858-ccapt35khk7tmadl17b3879ht2cropp6.apps.googleusercontent.com"
|
value: "1"
|
||||||
- name: GOOGLE_CLIENT_SECRET
|
- name: DOCCONV_MAX_PARALLEL_TASKS
|
||||||
value: "nW4pl4YsVWAR371gMRStW9qg"
|
value: {{ .Values.app.fileProcessor.docconvMaxParallelTasks | quote }}
|
||||||
- name: SINGLE_TENANT
|
|
||||||
value: "0"
|
|
||||||
- name: FORCE_HTTPS
|
|
||||||
value: "true"
|
|
||||||
- name: GLAGOL_PARTNER_ID
|
|
||||||
value: "ab230ee1-6946-43b9-8kfa-447beb8348b0"
|
|
||||||
- name: GLAGOL_API_URL
|
|
||||||
value: "https://api.glagol.online/glagol-api"
|
|
||||||
- name: LDAPTLS_REQCERT
|
|
||||||
value: "never"
|
|
||||||
- name: IFRAME_FULL_ACCESS
|
|
||||||
value: {{ .Values.app.security.iframeFullAccess | default "false" | quote }}
|
|
||||||
|
|
||||||
# Метаданные пода
|
# --- HRBox Hub ------------------------------------------------------------
|
||||||
|
- name: HUB_URL
|
||||||
|
value: {{ .Values.app.hub.url | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "HUB_TOKEN" "key" "hub-token") | nindent 0 }}
|
||||||
|
|
||||||
|
# --- Nanabush Player ------------------------------------------------------
|
||||||
|
{{- include "envs_nanabush_player" . }}
|
||||||
|
|
||||||
|
# --- Чат ------------------------------------------------------------------
|
||||||
|
- name: CHATBOX_SENTRY_DSN
|
||||||
|
value: {{ .Values.app.sentry.chatboxDsn | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "CHATBOX_WRAPPING_KEY" "key" "chatbox-wrapping-key") | nindent 0 }}
|
||||||
|
|
||||||
|
# --- Видеозвонки (LiveKit) ------------------------------------------------
|
||||||
|
- name: LIVEKIT_URL
|
||||||
|
value: {{ .Values.app.livekit.url | quote }}
|
||||||
|
- name: LIVEKIT_API_KEY
|
||||||
|
value: {{ .Values.app.livekit.apiKey | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "LIVEKIT_API_SECRET" "key" "livekit-api-secret") | nindent 0 }}
|
||||||
|
|
||||||
|
# --- AI -------------------------------------------------------------------
|
||||||
|
- name: AI_DOCS_INDEX_ENABLED
|
||||||
|
value: {{ .Values.app.ai.docsIndexEnabled | quote }}
|
||||||
|
- name: AI_SEMANTIC_INDEX_ENABLED
|
||||||
|
value: {{ .Values.app.ai.semanticIndexEnabled | quote }}
|
||||||
|
- name: AI_SEMANTIC_INDEX_USE_BATCH
|
||||||
|
value: {{ .Values.app.ai.semanticIndexUseBatch | quote }}
|
||||||
|
- name: YANDEX_CLOUD_AI_FOLDER_ID
|
||||||
|
value: {{ .Values.app.ai.yandex.folderId | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "YANDEX_CLOUD_AI_API_KEY" "key" "yandex-ai-api-key") | nindent 0 }}
|
||||||
|
|
||||||
|
# --- Внешние интеграции ---------------------------------------------------
|
||||||
|
- name: TELEGRAM_API_URL
|
||||||
|
value: {{ .Values.app.integrations.telegram.apiUrl | quote }}
|
||||||
|
- name: AUTH_GOOGLE_ENABLED
|
||||||
|
value: {{ .Values.app.integrations.google.enabled | quote }}
|
||||||
|
- name: GOOGLE_CLIENT_ID
|
||||||
|
value: {{ .Values.app.integrations.google.clientId | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "GOOGLE_CLIENT_SECRET" "key" "google-client-secret") | nindent 0 }}
|
||||||
|
- name: YANDEX_CLIENT_ID
|
||||||
|
value: {{ .Values.app.yandex.clientId | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "YANDEX_CLIENT_PASSWORD" "key" "yandex-client-password") | nindent 0 }}
|
||||||
|
- name: SLACK_APP_CLIENT_ID
|
||||||
|
value: {{ .Values.app.slack.clientId | quote }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "SLACK_APP_CLIENT_SECRET" "key" "slack-client-secret") | nindent 0 }}
|
||||||
|
{{- include "hrbox.secretEnv" (dict "ctx" . "env" "SLACK_APP_VERIFICATION_TOKEN" "key" "slack-verification-token") | nindent 0 }}
|
||||||
|
- name: GLAGOL_PARTNER_ID
|
||||||
|
value: {{ .Values.app.integrations.glagol.partnerId | quote }}
|
||||||
|
- name: GLAGOL_API_URL
|
||||||
|
value: {{ .Values.app.integrations.glagol.apiUrl | quote }}
|
||||||
|
|
||||||
|
# --- Sentry ---------------------------------------------------------------
|
||||||
|
- name: SENTRY_DSN
|
||||||
|
value: {{ .Values.app.sentry.dsn | quote }}
|
||||||
|
- name: SENTRY_DSN_LAUNCHPAD
|
||||||
|
value: {{ .Values.app.sentry.launchpadDsn | default .Values.app.sentry.dsn | quote }}
|
||||||
|
- name: SENTRY_DSN_CONDUCTOR
|
||||||
|
value: {{ .Values.app.sentry.conductorDsn | default .Values.app.sentry.dsn | quote }}
|
||||||
|
- name: SENTRY_ENVIRONMENT
|
||||||
|
value: {{ .Values.app.sentry.environment | quote }}
|
||||||
|
- name: SENTRY_RELEASE
|
||||||
|
value: {{ .Values.image.hrbox | quote }}
|
||||||
|
|
||||||
|
# --- Метаданные пода ------------------------------------------------------
|
||||||
- name: POD_NAME
|
- name: POD_NAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
@@ -195,57 +240,83 @@
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: spec.nodeName
|
fieldPath: spec.nodeName
|
||||||
|
|
||||||
# AI (опционально)
|
|
||||||
{{- if .Values.app.ai.yandex.folderId }}
|
|
||||||
- name: YANDEX_CLOUD_AI_FOLDER_ID
|
|
||||||
value: {{ .Values.app.ai.yandex.folderId | quote }}
|
|
||||||
- name: YANDEX_CLOUD_AI_API_KEY
|
|
||||||
value: {{ .Values.app.ai.yandex.apiKey | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
# Integration Services
|
{{/*
|
||||||
- name: GOWORKER_URL
|
Настройки Nanabush Player на стороне HRBox.
|
||||||
value: {{ .Values.app.goworker.url | quote }}
|
|
||||||
{{- if .Values.app.goworker.sentryDsn }}
|
NANABUSH_PLAYER_AUDIENCE и NANABUSH_PLAYER_PUBLIC_ORIGIN всегда равны:
|
||||||
- name: GOWORKER_SENTRY_DSN
|
audience тикета — это origin браузера без пути, а путь монтирования едет
|
||||||
value: {{ .Values.app.goworker.sentryDsn | quote }}
|
отдельной переменной NANABUSH_PLAYER_BASE_PATH.
|
||||||
|
*/}}
|
||||||
|
{{- define "envs_nanabush_player" -}}
|
||||||
|
{{- $player := .Values.app.nanabushPlayer }}
|
||||||
|
- name: NANABUSH_PLAYER_ENABLED
|
||||||
|
value: {{ $player.enabled | quote }}
|
||||||
|
{{- if $player.deployment.enabled }}
|
||||||
|
- name: NANABUSH_PLAYER_PUBLIC_ORIGIN
|
||||||
|
value: {{ include "nanabushPlayer.publicOrigin" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_AUDIENCE
|
||||||
|
value: {{ include "nanabushPlayer.publicOrigin" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_BASE_PATH
|
||||||
|
value: {{ include "nanabushPlayer.basePath" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_INTERNAL_ORIGIN
|
||||||
|
value: {{ include "nanabushPlayer.internalOrigin" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_ISSUER
|
||||||
|
value: {{ include "nanabushPlayer.issuer" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_CONTRACT_VERSION
|
||||||
|
value: {{ $player.contractVersion | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_PLAYER_VERSION
|
||||||
|
value: {{ include "nanabushPlayer.version" . | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_TICKET_TTL_SECONDS
|
||||||
|
value: {{ $player.deployment.ticket.ttlSeconds | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_SESSION_TTL_SECONDS
|
||||||
|
value: {{ $player.deployment.ticket.credentialTtlSeconds | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_PACKAGE_CONTENT_MAX_BYTES
|
||||||
|
value: {{ $player.deployment.backchannel.packageContentMaxBytes | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_SCORM_PACKAGE_MAX_BYTES
|
||||||
|
value: {{ $player.deployment.backchannel.scormPackageMaxBytes | quote }}
|
||||||
|
{{- with $player.deployment.backchannel.scormPackageMaxFiles }}
|
||||||
|
- name: NANABUSH_PLAYER_SCORM_PACKAGE_MAX_FILES
|
||||||
|
value: {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
- name: GOWORKER_PUSH_MIN_INTERVAL
|
- name: NANABUSH_PLAYER_LEGACY_FALLBACK
|
||||||
value: {{ .Values.app.goworker.pushMinInterval | quote }}
|
value: {{ $player.rollout.legacyFallback | quote }}
|
||||||
|
- name: NANABUSH_PLAYER_ALLOWED_CONTENT_TYPES
|
||||||
- name: KEDOCA_URL
|
value: {{ join "," $player.rollout.allowedContentTypes | quote }}
|
||||||
value: {{ .Values.app.kedoca.url | quote }}
|
- name: NANABUSH_PLAYER_ALLOWED_TENANTS
|
||||||
- name: TSP_URL
|
value: {{ join "," $player.rollout.allowedTenants | quote }}
|
||||||
value: "http://qs.cryptopro.ru/tsp/tsp.srf"
|
- name: NANABUSH_PLAYER_ROLLOUT_PERCENT
|
||||||
|
value: {{ $player.rollout.percent | quote }}
|
||||||
# Internal Services
|
|
||||||
- name: SERVICES_INTERNAL_SECRET
|
|
||||||
value: {{ .Values.secrets.servicesInternalSecret | default "QrrH84K2dG5JfoYxi4ZNPyg5p2LEj6rN69z5tIQYkfY6SJZ8GDXOcRjTNnefbOx5" | quote }}
|
|
||||||
|
|
||||||
# Document Converter
|
|
||||||
- name: PYTHONUNBUFFERED
|
|
||||||
value: "1"
|
|
||||||
- name: DOCCONV_MAX_PARALLEL_TASKS
|
|
||||||
value: "1"
|
|
||||||
|
|
||||||
# AI Features
|
|
||||||
- name: AI_DOCS_INDEX_ENABLED
|
|
||||||
value: "true"
|
|
||||||
- name: AI_SEMANTIC_INDEX_ENABLED
|
|
||||||
value: "true"
|
|
||||||
- name: AI_SEMANTIC_INDEX_USE_BATCH
|
|
||||||
value: "true"
|
|
||||||
|
|
||||||
# Chatbox
|
|
||||||
{{- if .Values.app.chatbox.sentryDsn }}
|
|
||||||
- name: CHATBOX_SENTRY_DSN
|
|
||||||
value: {{ .Values.app.chatbox.sentryDsn | quote }}
|
|
||||||
{{- end }}
|
|
||||||
- name: CHATBOX_WRAPPING_KEY
|
|
||||||
value: {{ .Values.app.chatbox.wrappingKey | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Ключ подписи launch-тикета. Живёт только в PHP-контейнерах, которые выпускают
|
||||||
|
тикеты: nginx и вспомогательным контейнерам он не нужен.
|
||||||
|
*/}}
|
||||||
|
{{- define "envs_nanabush_player_signing" -}}
|
||||||
|
{{- $cfg := .Values.app.nanabushPlayer.deployment }}
|
||||||
|
{{- if $cfg.enabled }}
|
||||||
|
- name: NANABUSH_PLAYER_HS256_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "nanabushPlayer.secretName" . | quote }}
|
||||||
|
key: {{ required "Нужно имя ключа ticketHs256 в секрете Nanabush Player" $cfg.existingSecret.keys.ticketHs256 | quote }}
|
||||||
|
{{- with $cfg.existingSecret.keys.ticketKeys }}
|
||||||
|
# Кольцо ключей launch-тикета: HRBox подписывает первым ключом, Player
|
||||||
|
# принимает все. Тот же ключ секрета читает и сам Player, поэтому ротация —
|
||||||
|
# это одно значение на обе стороны.
|
||||||
|
- name: NANABUSH_PLAYER_TICKET_KEYS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "nanabushPlayer.secretName" $ | quote }}
|
||||||
|
key: {{ . | quote }}
|
||||||
|
{{- 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 }}
|
||||||
@@ -256,3 +327,21 @@
|
|||||||
- name: KEDO_PKEYOPT
|
- name: KEDO_PKEYOPT
|
||||||
value: {{ .Values.app.kedoca.pkeyopt | quote }}
|
value: {{ .Values.app.kedoca.pkeyopt | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* OpenTelemetry — только когда app.tracing.enabled. */}}
|
||||||
|
{{- define "envs_tracing" }}
|
||||||
|
- name: OTEL_PHP_AUTOLOAD_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||||
|
value: {{ .Values.app.tracing.exporterUrl | quote }}
|
||||||
|
- name: OTEL_SERVICE_NAME
|
||||||
|
value: "hrbox-app"
|
||||||
|
- name: OTEL_TRACES_EXPORTER
|
||||||
|
value: "otlp"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_PROTOCOL
|
||||||
|
value: "http/protobuf"
|
||||||
|
- name: OTEL_PROPAGATORS
|
||||||
|
value: "baggage,tracecontext"
|
||||||
|
- name: OTEL_LOG_LEVEL
|
||||||
|
value: "info"
|
||||||
|
{{- end }}
|
||||||
|
|||||||
+211
-90
@@ -1,110 +1,231 @@
|
|||||||
{{/*
|
{{/*
|
||||||
Expand the name of the chart.
|
============================================================================
|
||||||
|
Общие хелперы чарта HRBox
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Правило именования: все объекты называются "<имя чарта>-<компонент>"
|
||||||
|
(hrbox-web, hrbox-worker, ...). Имя чарта можно переопределить через
|
||||||
|
nameOverride, но по умолчанию оно равно "hrbox" — так же, как в чарте 2.x,
|
||||||
|
поэтому обновление 2.x -> 3.x не пересоздаёт объекты.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "nats.name" -}}
|
|
||||||
{{- default .Chart.Name .Values.nats.nameOverride | trunc 63 | trimSuffix "-" -}}
|
{{/* Базовое имя релиза: "hrbox" или nameOverride. */}}
|
||||||
|
{{- define "hrbox.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* Имя чарта с версией — для лейбла helm.sh/chart. */}}
|
||||||
|
{{- define "hrbox.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "nats.fullname" -}}
|
{{/*
|
||||||
{{- if .Values.nats.fullnameOverride -}}
|
Полное имя объекта компонента.
|
||||||
{{- .Values.nats.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
|
||||||
|
Usage: {{ include "hrbox.component" (dict "ctx" . "name" "web") }} -> hrbox-web
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.component" -}}
|
||||||
|
{{- printf "%s-%s" (include "hrbox.name" .ctx) .name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Селектор пода. ВАЖНО: spec.selector в Deployment неизменяем, поэтому набор
|
||||||
|
лейблов здесь совпадает с чартом 2.x (один лейбл "service") и меняться не должен.
|
||||||
|
|
||||||
|
Usage: {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 6 }}
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.selectorLabels" -}}
|
||||||
|
service: {{ include "hrbox.component" (dict "ctx" .ctx "name" .name) }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Лейблы объекта. Лейбл "app" сохранён из чарта 2.x — на него опираются
|
||||||
|
привычные команды вида "kubectl get pods -l app=worker".
|
||||||
|
|
||||||
|
Usage: {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }}
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.labels" -}}
|
||||||
|
app: {{ .name }}
|
||||||
|
app.kubernetes.io/name: {{ include "hrbox.name" .ctx }}
|
||||||
|
app.kubernetes.io/component: {{ .name }}
|
||||||
|
app.kubernetes.io/instance: {{ .ctx.Release.Name }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .ctx.Release.Service }}
|
||||||
|
app.kubernetes.io/version: {{ .ctx.Chart.AppVersion | quote }}
|
||||||
|
helm.sh/chart: {{ include "hrbox.chart" .ctx }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Секреты для доступа к приватному Docker-реестру.
|
||||||
|
|
||||||
|
Usage: {{- include "hrbox.imagePullSecrets" . | nindent 6 }}
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.imagePullSecrets" -}}
|
||||||
|
{{- if .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.imagePullSecrets }}
|
||||||
|
- name: {{ .name | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Тег из ссылки на образ: "registry/repo:1.2.3" -> "1.2.3".
|
||||||
|
Двоеточие ищется только в последнем сегменте пути, чтобы не спутать тег
|
||||||
|
с портом реестра ("registry:5000/repo").
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.imageTag" -}}
|
||||||
|
{{- $lastSegment := splitList "/" . | last -}}
|
||||||
|
{{- if contains ":" $lastSegment -}}
|
||||||
|
{{- splitList ":" $lastSegment | last -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
============================================================================
|
||||||
|
Секрет приложения
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Чувствительные значения (пароли БД и SMTP, ключи шифрования, токены) не
|
||||||
|
попадают в env-блоки Deployment открытым текстом: чарт складывает их в один
|
||||||
|
Secret, а контейнеры читают его через secretKeyRef. Полный список ключей —
|
||||||
|
в README, раздел "Секреты".
|
||||||
|
|
||||||
|
secrets.existingSecret позволяет подставить Secret, созданный вне чарта
|
||||||
|
(например, из внешнего vault-оператора) — тогда чарт свой Secret не создаёт.
|
||||||
|
*/}}
|
||||||
|
{{/*
|
||||||
|
Адреса внутренних сервисов.
|
||||||
|
|
||||||
|
Сервисы релиза адресуются по именам, которые создаёт этот же чарт, поэтому
|
||||||
|
пустое значение в values резолвится в имя из hrbox.component: адреса не
|
||||||
|
разъезжаются при nameOverride и их не нужно править вручную. Непустое
|
||||||
|
значение используется как есть — для сервиса за пределами релиза.
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.geonamesUrl" -}}
|
||||||
|
{{- .Values.app.geonames.url | default (printf "http://%s:8181" (include "hrbox.component" (dict "ctx" . "name" "geonames"))) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "hrbox.goworkerUrl" -}}
|
||||||
|
{{- .Values.app.goworker.url | default (printf "http://%s:8686" (include "hrbox.component" (dict "ctx" . "name" "goworker"))) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "hrbox.kedocaUrl" -}}
|
||||||
|
{{- .Values.app.kedoca.url | default (printf "http://%s:8558" (include "hrbox.component" (dict "ctx" . "name" "kedoca"))) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "hrbox.redisHost" -}}
|
||||||
|
{{- .Values.app.redis.host | default (include "hrbox.component" (dict "ctx" . "name" "dragonfly")) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "hrbox.natsUrl" -}}
|
||||||
|
{{- .Values.app.nats.url | default (printf "nats://%s:4222" (include "hrbox.component" (dict "ctx" . "name" "nats"))) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "hrbox.secretName" -}}
|
||||||
|
{{- if .Values.secrets.existingSecret -}}
|
||||||
|
{{- .Values.secrets.existingSecret -}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
{{- $name := default .Chart.Name .Values.nats.nameOverride -}}
|
{{- include "hrbox.component" (dict "ctx" . "name" "secrets") -}}
|
||||||
{{- if contains $name .Release.Name -}}
|
{{- end -}}
|
||||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Одна переменная окружения из секрета приложения.
|
||||||
|
|
||||||
|
Usage: {{ include "hrbox.secretEnv" (dict "ctx" . "env" "POSTGRES_PASSWORD" "key" "postgres-password") }}
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.secretEnv" -}}
|
||||||
|
- name: {{ .env }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "hrbox.secretName" .ctx | quote }}
|
||||||
|
key: {{ .key | quote }}
|
||||||
|
{{- 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" -}}
|
||||||
|
{{- required "Для Nanabush Player нужен непустой ingress.host" .Values.ingress.host -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "nanabushPlayer.publicOrigin" -}}
|
||||||
|
{{- printf "https://%s" (include "nanabushPlayer.host" .) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Путь монтирования Player'а на основном домене. Пустое значение и "/" запрещены:
|
||||||
|
это отдало бы Player'у корень, занятый самим HRBox.
|
||||||
|
*/}}
|
||||||
|
{{- define "nanabushPlayer.basePath" -}}
|
||||||
|
{{- $path := .Values.app.nanabushPlayer.basePath | default "/nanabush-player" -}}
|
||||||
|
{{- $path | trimSuffix "/" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "nanabushPlayer.issuer" -}}
|
||||||
|
{{- if .Values.app.nanabushPlayer.issuer -}}
|
||||||
|
{{- .Values.app.nanabushPlayer.issuer -}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
{{- printf "https://%s/lms/player" (include "nanabushPlayer.host" .) -}}
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- define "nats.chart" -}}
|
{{- define "nanabushPlayer.serviceName" -}}
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
{{- .Values.app.nanabushPlayer.service.name | default (include "hrbox.component" (dict "ctx" . "name" "nanabush-player")) -}}
|
||||||
{{- end }}
|
{{- end -}}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Common labels
|
Хост обратного канала Player -> HRBox. Только внутрикластерное имя: по нему
|
||||||
|
Player забирает пакеты курсов и отправляет отчёты, и оно же попадает в
|
||||||
|
allowlist самого Player'а.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "nats.labels" -}}
|
{{- define "nanabushPlayer.backchannelHost" -}}
|
||||||
app.kubernetes.io/name: nats
|
{{- .Values.app.nanabushPlayer.deployment.backchannel.host | default (include "hrbox.component" (dict "ctx" . "name" "web")) -}}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
helm.sh/chart: {{ include "nats.chart" . }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Selector labels
|
|
||||||
*/}}
|
|
||||||
{{- define "nats.selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: nats
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Return the proper NATS image name
|
|
||||||
*/}}
|
|
||||||
{{- define "nats.clusterAdvertise" -}}
|
|
||||||
{{- printf "$(POD_NAME).%s.$(POD_NAMESPACE).svc.%s" (include "nats.fullname" . ) $.Values.nats.k8sClusterDomain }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Return the NATS cluster routes.
|
|
||||||
*/}}
|
|
||||||
{{- define "nats.clusterRoutes" -}}
|
|
||||||
{{- $name := (include "nats.fullname" . ) -}}
|
|
||||||
{{- range $i, $e := until (.Values.nats.cluster.replicas | int) -}}
|
|
||||||
{{- printf "nats://%s-%d.%s.%s.svc.%s:6222," $name $i $name $.Release.Namespace $.Values.nats.k8sClusterDomain -}}
|
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "nats.extraRoutes" -}}
|
{{- define "nanabushPlayer.internalOrigin" -}}
|
||||||
{{- range $i, $url := .Values.nats.cluster.extraRoutes -}}
|
{{- printf "http://%s" (include "nanabushPlayer.backchannelHost" .) -}}
|
||||||
{{- printf "%s," $url -}}
|
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "nats.tlsConfig" -}}
|
|
||||||
tls {
|
|
||||||
{{- if .cert }}
|
|
||||||
cert_file: {{ .secretPath }}/{{ .secret.name }}/{{ .cert }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .key }}
|
|
||||||
key_file: {{ .secretPath }}/{{ .secret.name }}/{{ .key }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .ca }}
|
|
||||||
ca_file: {{ .secretPath }}/{{ .secret.name }}/{{ .ca }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .insecure }}
|
|
||||||
insecure: {{ .insecure }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .verify }}
|
|
||||||
verify: {{ .verify }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .verifyAndMap }}
|
|
||||||
verify_and_map: {{ .verifyAndMap }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .curvePreferences }}
|
|
||||||
curve_preferences: {{ .curvePreferences }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .timeout }}
|
|
||||||
timeout: {{ .timeout }}
|
|
||||||
{{- end }}
|
|
||||||
}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Renders a value that contains template.
|
Версия Player'а берётся из тега образа, а не из отдельной настройки: тикет
|
||||||
Usage:
|
не должен объявлять версию, отличную от той, что реально запущена в кластере.
|
||||||
{{ include "tplvalues.render" ( dict "value" .Values.nats.path.to.the.Value "context" $) }}
|
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "tplvalues.render" -}}
|
{{- define "nanabushPlayer.version" -}}
|
||||||
{{- if typeIs "string" .value }}
|
{{- $tag := include "hrbox.imageTag" .Values.image.nanabushPlayer -}}
|
||||||
{{- tpl .value .context }}
|
{{- if not $tag -}}
|
||||||
{{- else }}
|
{{- fail "image.nanabushPlayer должен содержать явный тег версии, например nanabush-player:1.0.11" -}}
|
||||||
{{- tpl (.value | toYaml) .context }}
|
{{- end -}}
|
||||||
{{- end }}
|
{{- $tag -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "nanabushPlayer.secretName" -}}
|
||||||
|
{{- required "app.nanabushPlayer.deployment.existingSecret.name обязателен, когда Player включён" .Values.app.nanabushPlayer.deployment.existingSecret.name -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/* URL Redis, который чарт собирает из штатных настроек app.redis. */}}
|
||||||
|
{{- define "nanabushPlayer.redisUrl" -}}
|
||||||
|
{{- printf "redis://%s:%s/%d" (include "hrbox.redisHost" .) (.Values.app.redis.port | toString) (.Values.app.nanabushPlayer.deployment.redis.database | int) -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Префикс ключей Redis. Namespace в префиксе разводит несколько релизов HRBox,
|
||||||
|
живущих на одном Dragonfly.
|
||||||
|
*/}}
|
||||||
|
{{- define "nanabushPlayer.redisKeyPrefix" -}}
|
||||||
|
{{- printf "hrbox:%s:%s" .Release.Namespace .Values.app.nanabushPlayer.deployment.redis.keyPrefixSuffix -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
+30
-106
@@ -1,119 +1,43 @@
|
|||||||
{{- define "app_resources" }}
|
{{/*
|
||||||
resources:
|
============================================================================
|
||||||
requests:
|
resources: для контейнера
|
||||||
memory: {{ .Values.resources.app.requests.memory | quote }}
|
============================================================================
|
||||||
cpu: {{ .Values.resources.app.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.app.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.app.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.app.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.app.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "nginx_resources" }}
|
В чарте 2.x на каждый компонент был отдельный define из восьми одинаковых
|
||||||
resources:
|
строк. Здесь один хелпер, который читает resources.<компонент> из values.yaml.
|
||||||
requests:
|
Незаданные поля просто не рендерятся, поэтому компоненту можно указать,
|
||||||
memory: {{ .Values.resources.nginx.requests.memory | quote }}
|
например, только requests.
|
||||||
cpu: {{ .Values.resources.nginx.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.nginx.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.nginx.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.nginx.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.nginx.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "worker_resources" }}
|
Usage:
|
||||||
|
{{- include "hrbox.resources" (dict "ctx" . "component" "app") | indent 10 }}
|
||||||
|
*/}}
|
||||||
|
{{- define "hrbox.resources" -}}
|
||||||
|
{{- $r := index .ctx.Values.resources .component -}}
|
||||||
|
{{- if $r }}
|
||||||
resources:
|
resources:
|
||||||
|
{{- with $r.requests }}
|
||||||
requests:
|
requests:
|
||||||
memory: {{ .Values.resources.worker.requests.memory | quote }}
|
{{- with .cpu }}
|
||||||
cpu: {{ .Values.resources.worker.requests.cpu | quote }}
|
cpu: {{ . | quote }}
|
||||||
ephemeral-storage: {{ .Values.resources.worker.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.worker.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.worker.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.worker.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .memory }}
|
||||||
{{- define "websocket_resources" }}
|
memory: {{ . | quote }}
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.websocket.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.websocket.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.websocket.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.websocket.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.websocket.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.websocket.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .ephemeralStorage }}
|
||||||
{{- define "geonames_resources" }}
|
ephemeral-storage: {{ . | quote }}
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.geonames.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.geonames.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.geonames.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.geonames.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.geonames.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.geonames.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "conductor_resources" }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.conductor.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.conductor.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.conductor.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.conductor.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.conductor.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.conductor.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with $r.limits }}
|
||||||
{{- define "jobs_resources" }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.jobs.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.jobs.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.jobs.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
limits:
|
||||||
memory: {{ .Values.resources.jobs.limits.memory | quote }}
|
{{- with .cpu }}
|
||||||
cpu: {{ .Values.resources.jobs.limits.cpu | quote }}
|
cpu: {{ . | quote }}
|
||||||
ephemeral-storage: {{ .Values.resources.jobs.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .memory }}
|
||||||
{{- define "goworker_resources" }}
|
memory: {{ . | quote }}
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.goworker.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.goworker.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.goworker.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.goworker.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.goworker.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.goworker.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .ephemeralStorage }}
|
||||||
{{- define "kedoca_resources" }}
|
ephemeral-storage: {{ . | quote }}
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.kedoca.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.kedoca.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.kedoca.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.kedoca.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.kedoca.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.kedoca.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "chatbox_resources" }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: {{ .Values.resources.chatbox.requests.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.chatbox.requests.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.chatbox.requests.ephemeralStorage | quote }}
|
|
||||||
limits:
|
|
||||||
memory: {{ .Values.resources.chatbox.limits.memory | quote }}
|
|
||||||
cpu: {{ .Values.resources.chatbox.limits.cpu | quote }}
|
|
||||||
ephemeral-storage: {{ .Values.resources.chatbox.limits.ephemeralStorage | quote }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
|||||||
+554
-280
File diff suppressed because it is too large
Load Diff
+263
@@ -5,6 +5,268 @@ All notable changes to the HRBox Helm chart will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.0.0] - 2026-09-02
|
||||||
|
|
||||||
|
Синхронизация с продовым окружением HRBox и приведение чарта в порядок:
|
||||||
|
добавлен плеер учебных материалов, секреты перестали лежать открытым текстом
|
||||||
|
в спеках подов, вместо девяти одинаковых блоков ресурсов — один хелпер,
|
||||||
|
а неполная конфигурация теперь останавливает установку с внятным текстом.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### Nanabush Player
|
||||||
|
|
||||||
|
Проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные
|
||||||
|
курсы, тесты и эссе.
|
||||||
|
|
||||||
|
- `09-nanabush-player.yaml` — Deployment и Service, образ `nanabush-player:1.0.11`
|
||||||
|
- `00-nanabush-player-secret.yaml` — ключи launch-тикета и сессии
|
||||||
|
- маршрут `/nanabush-player` на основном Ingress
|
||||||
|
- настройки: `app.nanabushPlayer` в values.yaml, ресурсы — `resources.nanabushPlayer`
|
||||||
|
- переменные `NANABUSH_PLAYER_*` в HRBox и `PLAYER_*` в самом плеере
|
||||||
|
|
||||||
|
On-premise-топология отличается от облачной: выделенного поддомена нет,
|
||||||
|
плеер монтируется на основной домен под путём. Отсюда два ограничения,
|
||||||
|
описанные в README: нет изоляции cookie между HRBox и содержимым курсов,
|
||||||
|
и нужны синхронизированные часы (тикет живёт 5 минут).
|
||||||
|
|
||||||
|
Три независимых переключателя (`enabled`, `ingress.enabled`,
|
||||||
|
`deployment.enabled`) сохранены для аварийного отката; чарт не даёт оставить
|
||||||
|
маршрут без пода или раскатку без маршрута.
|
||||||
|
|
||||||
|
#### Проверка конфигурации
|
||||||
|
|
||||||
|
- `00-validate.yaml` — объектов не создаёт, но останавливает рендер, если
|
||||||
|
не заполнены обязательные значения, использованы ключи из версии 2.x или
|
||||||
|
нарушены инварианты плеера. Сообщения на русском, с указанием, что править.
|
||||||
|
|
||||||
|
#### Секрет приложения
|
||||||
|
|
||||||
|
- `00-app-secret.yaml` — единый Secret с паролями и ключами. Контейнеры
|
||||||
|
читают его через `secretKeyRef`, поэтому `kubectl get deploy -o yaml`
|
||||||
|
больше не показывает пароль базы и ключ шифрования.
|
||||||
|
- `secrets.existingSecret` — подстановка Secret, созданного вне чарта.
|
||||||
|
|
||||||
|
#### Новые компоненты и возможности
|
||||||
|
|
||||||
|
- `09-file-processor-video.yaml` — отдельный пул конвертации видео с
|
||||||
|
собственным профилем ресурсов, `nodeSelector` и `tolerations`
|
||||||
|
(`app.videoConverter.enabled`, по умолчанию выключен)
|
||||||
|
- `02-web-pdb.yaml` — PodDisruptionBudget для `web`, создаётся начиная с
|
||||||
|
двух реплик
|
||||||
|
- `01-otel-configmap.yaml` и sidecar-коллектор в поде `web`
|
||||||
|
(`app.tracing.enabled`)
|
||||||
|
- `01-nginx-assetlinks.yaml` — привязка мобильных приложений к домену
|
||||||
|
(`app.mobileApps`)
|
||||||
|
- `templates/NOTES.txt` — что делать после установки, с готовыми командами
|
||||||
|
и предупреждениями о небезопасных настройках
|
||||||
|
|
||||||
|
#### Новые переменные окружения
|
||||||
|
|
||||||
|
- `NATS_STREAM_REPLICAS` — число реплик JetStream-стримов по размеру кластера
|
||||||
|
- `TELEGRAM_API_URL`, `AUTH_GOOGLE_ENABLED`
|
||||||
|
- `LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` — видеозвонки
|
||||||
|
- `APP_INTERNAL_URL` — внутренний адрес HRBox для конвертера
|
||||||
|
- `VIDEO_CONVERTER_ENABLED` — отдельно для каждого пула
|
||||||
|
- `SENDER_ENV`, `IMAGE_RESIZE_MAX_PARALLEL`, `OTEL_*`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
#### Структура values.yaml
|
||||||
|
|
||||||
|
- все образы собраны в блок `image` — обновление версии сервиса стало
|
||||||
|
правкой одной строки
|
||||||
|
- `imagePullSecrets` вынесен в настройки, `regsecret` больше не захардкожен
|
||||||
|
- ключи приведены к camelCase: `file_processor` → `fileProcessor`,
|
||||||
|
`custom_policy` → `customPolicy`
|
||||||
|
- `resources` дополнен компонентами `fileProcessor`, `fileProcessorVideo`,
|
||||||
|
`nanabushPlayer`, `dragonfly`, `nats`
|
||||||
|
- адреса внутренних сервисов (`app.geonames.url`, `app.goworker.url`,
|
||||||
|
`app.kedoca.url`, `app.redis.host`, `app.nats.url`) по умолчанию пустые и
|
||||||
|
выводятся из имён, которые создаёт сам чарт. Раньше это были захардкоженные
|
||||||
|
строки `hrbox-*`, которые молча ломались при `nameOverride`. Явно заданное
|
||||||
|
значение по-прежнему используется как есть, поэтому старые values работают
|
||||||
|
без правок. Выключение `dragonfly` или `nats` без указания внешнего адреса
|
||||||
|
теперь останавливает установку
|
||||||
|
|
||||||
|
#### Шаблоны
|
||||||
|
|
||||||
|
- `_resources.tpl`: девять почти одинаковых define заменены одним
|
||||||
|
`hrbox.resources`; незаданные поля не рендерятся
|
||||||
|
- `_helpers.tpl`: убрано около сотни строк мёртвого кода (хелперы `nats.*`,
|
||||||
|
которыми ничего не пользовалось и которые ссылались на несуществующий
|
||||||
|
`nats.k8sClusterDomain`); добавлены `hrbox.component`, `hrbox.labels`,
|
||||||
|
`hrbox.selectorLabels`, `hrbox.secretEnv` и хелперы плеера
|
||||||
|
- у всех объектов появились стандартные лейблы `app.kubernetes.io/*`;
|
||||||
|
лейбл `app` сохранён, привычные `kubectl get pods -l app=worker` работают
|
||||||
|
- селекторы Deployment не менялись — обновление 2.x → 3.0 не пересоздаёт
|
||||||
|
объекты
|
||||||
|
- аннотации `checksum/*` перезапускают поды при изменении конфигурации nginx
|
||||||
|
или секрета приложения
|
||||||
|
- образы, порты и таймауты вынесены из шаблонов в values.yaml
|
||||||
|
|
||||||
|
#### nginx
|
||||||
|
|
||||||
|
Набор `location` синхронизирован с продовым:
|
||||||
|
|
||||||
|
- `/healthz` и `/health-check` — под них настроены пробы пода `web`
|
||||||
|
(раньше проверялся только TCP-порт)
|
||||||
|
- `/api/v1/integration/commit` — потоковая загрузка оргструктуры из 1С без
|
||||||
|
буферизации тела, лимит 500 МБ
|
||||||
|
- `/ai/` — увеличенные таймауты
|
||||||
|
- `/.well-known/assetlinks.json` и `/.well-known/apple-app-site-association`
|
||||||
|
- `/frontend/sw.js` — корректные заголовки service worker
|
||||||
|
- кеширование шрифтов
|
||||||
|
- убран TLSv1 и TLSv1.1
|
||||||
|
|
||||||
|
#### Версии образов
|
||||||
|
|
||||||
|
| Компонент | Было | Стало |
|
||||||
|
|---|---|---|
|
||||||
|
| chatbox | 3.0 | 5.18 |
|
||||||
|
| goworker | 2.7 | 3.21.2 |
|
||||||
|
| kedoca | 0.8 | 1.0.6 |
|
||||||
|
| geonames | 3.1 | 3.3 |
|
||||||
|
| file-processor | 1.0 | 1.4.6 |
|
||||||
|
| nanabush-player | — | 1.0.11 |
|
||||||
|
|
||||||
|
#### Джобы
|
||||||
|
|
||||||
|
- имя содержит номер ревизии релиза: в `kubectl get jobs` видно, какой выкат
|
||||||
|
запускал миграции, а повторный `upgrade` не спотыкается об уже существующий
|
||||||
|
объект
|
||||||
|
- `ttlSecondsAfterFinished` — час у обеих джоб (было 100 секунд у миграций и
|
||||||
|
ничего у обновления кластера): логи упавшей джобы успевают дожить до того,
|
||||||
|
как их посмотрят
|
||||||
|
|
||||||
|
#### Переименования файлов
|
||||||
|
|
||||||
|
- `03-websocket.yaml` → `03-chatbox.yaml` (содержимое было chatbox с версии 2.0)
|
||||||
|
- `09-geonames.yml` → `09-geonames.yaml`
|
||||||
|
- `99-update-cluser-job.yaml` → `99-update-cluster-job.yaml`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Общий секрет в коде чарта.** `SERVICES_INTERNAL_SECRET` имел захардкоженное
|
||||||
|
значение по умолчанию, то есть все установки, где его не заполнили, работали
|
||||||
|
на одном и том же секрете межсервисной аутентификации. Теперь значение
|
||||||
|
обязательно.
|
||||||
|
- **Домен кластера игнорировался.** Маршруты кластера NATS были захардкожены
|
||||||
|
на `cluster.local`, хотя `cluster.domain` присутствовал в values и
|
||||||
|
использовался в соседней переменной. На кластере с нестандартным доменом
|
||||||
|
ноды не находили друг друга.
|
||||||
|
- **`CLUSTER_ADVERTISE` без сегмента `svc`.** Имя не резолвилось, и ноды NATS
|
||||||
|
объявляли соседям недостижимый адрес.
|
||||||
|
- **Рассинхрон реплик NATS.** `nats.replicas` и `nats.cluster.replicas`
|
||||||
|
задавались независимо; при расхождении часть нод не входила в кластер.
|
||||||
|
Теперь чарт требует их совпадения.
|
||||||
|
- **Пробы `web` не проверяли приложение.** Проверялся TCP-порт 9000, поэтому
|
||||||
|
под с неработающим PHP считался готовым. Теперь readiness ходит в
|
||||||
|
`/health-check` через FPM.
|
||||||
|
- Пароли и ключи больше не видны в спеках Deployment.
|
||||||
|
- `imagePullPolicy` у части сервисов был `Always` на закреплённых тегах —
|
||||||
|
лишние обращения к реестру при каждом рестарте пода.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `secrets` больше не имеет значений по умолчанию — пустые ключи
|
||||||
|
останавливают установку вместо тихого запуска на предсказуемых секретах
|
||||||
|
- убраны неиспользуемые хелперы `nats.name`, `nats.fullname`, `nats.chart`,
|
||||||
|
`nats.labels`, `nats.selectorLabels`, `nats.clusterAdvertise`,
|
||||||
|
`nats.clusterRoutes`, `nats.extraRoutes`, `nats.tlsConfig`, `tplvalues.render`
|
||||||
|
- `SENDGRID_API_KEY` — не используется в on-premise
|
||||||
|
|
||||||
|
### Migration Guide
|
||||||
|
|
||||||
|
#### Переход с 2.x на 3.0
|
||||||
|
|
||||||
|
Все переехавшие ключи чарт распознаёт и сообщает, куда именно они переехали,
|
||||||
|
поэтому надёжнее всего запустить `helm template` и править по сообщениям.
|
||||||
|
|
||||||
|
**Шаг 1. Перенесите образы в блок `image`**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# было
|
||||||
|
app:
|
||||||
|
image: "cr.yandex/.../hrbox:latest"
|
||||||
|
chatbox:
|
||||||
|
image: "cr.yandex/.../hrbox-chatbox:3.0"
|
||||||
|
goworker:
|
||||||
|
image: "cr.yandex/.../hrbox-goworker:2.7"
|
||||||
|
kedoca:
|
||||||
|
image: "cr.yandex/.../hrbox-kedoca:0.8"
|
||||||
|
file_processor:
|
||||||
|
image: "cr.yandex/.../hrbox-file-processor:1.0"
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# стало
|
||||||
|
image:
|
||||||
|
hrbox: "cr.yandex/.../hrbox:latest"
|
||||||
|
chatbox: "cr.yandex/.../hrbox-chatbox:5.18"
|
||||||
|
goworker: "cr.yandex/.../hrbox-goworker:3.21.2"
|
||||||
|
kedoca: "cr.yandex/.../hrbox-kedoca:1.0.6"
|
||||||
|
fileProcessor: "cr.yandex/.../hrbox-file-processor:1.4.6"
|
||||||
|
app:
|
||||||
|
replicas:
|
||||||
|
fileProcessor: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Шаг 2. Переименуйте ключи**
|
||||||
|
|
||||||
|
| Было | Стало |
|
||||||
|
|---|---|
|
||||||
|
| `app.file_processor` | `app.fileProcessor` |
|
||||||
|
| `app.custom_policy` | `app.customPolicy` |
|
||||||
|
| `app.custom_policy.privacy_url` | `app.customPolicy.privacyUrl` |
|
||||||
|
| `app.custom_policy.terms_url` | `app.customPolicy.termsUrl` |
|
||||||
|
| `app.goworker.replicas` | `app.replicas.goworker` |
|
||||||
|
| `app.kedoca.replicas` | `app.replicas.kedoca` |
|
||||||
|
| `app.goworker.sentryDsn` | `app.sentry.goworkerDsn` |
|
||||||
|
| `app.chatbox.sentryDsn` | `app.sentry.chatboxDsn` |
|
||||||
|
| `nats.image` | `image.nats` |
|
||||||
|
|
||||||
|
**Шаг 3. Заполните ключи плеера**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl rand -base64 48 # app.nanabushPlayer.keys.ticketHs256
|
||||||
|
openssl rand -base64 48 # app.nanabushPlayer.keys.sessionHs256
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключи должны отличаться друг от друга.
|
||||||
|
|
||||||
|
**Шаг 4. Проверьте `secrets.servicesInternalSecret`**
|
||||||
|
|
||||||
|
Если поле было пустым, установка работала на общем значении по умолчанию.
|
||||||
|
Сгенерируйте своё: `openssl rand -base64 48`. После обновления перезапустятся
|
||||||
|
все сервисы, обменивающиеся внутренними вызовами, — это ожидаемо.
|
||||||
|
|
||||||
|
**Шаг 5. Проверьте рендер и обновитесь**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm template hrbox .helm -f values.yaml > /dev/null
|
||||||
|
helm upgrade hrbox .helm -n hrbox -f values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Шаг 6. Включите плеер**
|
||||||
|
|
||||||
|
Порядок первого включения и проверки — в README, раздел «Nanabush Player».
|
||||||
|
|
||||||
|
#### Breaking Changes
|
||||||
|
|
||||||
|
1. **Ключи `values.yaml` переехали.** Установка останавливается с подсказкой,
|
||||||
|
молча ничего не игнорируется.
|
||||||
|
2. **`secrets.*` стали обязательными.** Установка без них не пройдёт.
|
||||||
|
3. **Появился Nanabush Player.** Это дополнительный под, Service, Secret и
|
||||||
|
маршрут; учтите ресурсы кластера. Плеер требует синхронизированного времени
|
||||||
|
на нодах.
|
||||||
|
4. **Пробы `web` стали строже.** Под, где PHP-FPM не отвечает по HTTP, теперь
|
||||||
|
не проходит readiness. Если приложение отдаёт `/health-check` иначе,
|
||||||
|
проверьте это до обновления.
|
||||||
|
5. **`app.replicas` расширился** полями `goworker`, `kedoca`, `fileProcessor`,
|
||||||
|
`fileProcessorVideo`; соответствующие `replicas` внутри сервисов удалены.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.0.0] - 2026-01-30
|
## [2.0.0] - 2026-01-30
|
||||||
|
|
||||||
Масштабная актуализация чарта на основе продового окружения hrbox. Синхронизированы версии компонентов, добавлены новые микросервисы, обновлены переменные окружения.
|
Масштабная актуализация чарта на основе продового окружения hrbox. Синхронизированы версии компонентов, добавлены новые микросервисы, обновлены переменные окружения.
|
||||||
@@ -227,5 +489,6 @@ kubectl get pods -n hrbox -l app=websocket # Должно быть пусто
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
[3.0.0]: #300---2026-09-02
|
||||||
[2.0.0]: #200---2026-01-30
|
[2.0.0]: #200---2026-01-30
|
||||||
[1.2.0]: #120---previous-version
|
[1.2.0]: #120---previous-version
|
||||||
|
|||||||
@@ -1,14 +1,48 @@
|
|||||||
# HRBox Helm Chart
|
# HRBox Helm Chart
|
||||||
|
|
||||||
Helm-чарт для развертывания приложения HRBox в Kubernetes.
|
Helm-чарт для развёртывания HRBox в собственном Kubernetes-кластере.
|
||||||
|
|
||||||
|
Чарт лежит в каталоге [`.helm`](.helm), настройки — в [`.helm/values.yaml`](.helm/values.yaml).
|
||||||
|
Каждый параметр там прокомментирован; ниже — только то, что нужно сделать руками.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Быстрый старт
|
## Что разворачивается
|
||||||
|
|
||||||
### 1. Создайте секрет для доступа к Docker-реестру
|
| Компонент | Что делает | Порт |
|
||||||
|
|---|---|---|
|
||||||
|
| `web` | PHP-FPM + nginx: веб-интерфейс и API | 80 |
|
||||||
|
| `worker` | фоновые задачи: файлы, почта, отчёты | — |
|
||||||
|
| `conductor` | планировщик задач по расписанию (строго 1 реплика) | 8080 |
|
||||||
|
| `chatbox` | вебсокеты чата и real-time уведомлений | 80 → 9797 |
|
||||||
|
| `nanabush-player` | проигрыватель курсов: SCORM, cmi5, xAPI, нативные материалы | 8094 |
|
||||||
|
| `goworker` | мессенджеры, push, синхронизация оргструктуры с 1С | 8686 |
|
||||||
|
| `kedoca` | электронная подпись: КЭДО, МЧД | 8558 |
|
||||||
|
| `geonames` | справочник городов, стран, часовых поясов | 8181 |
|
||||||
|
| `file-processor` | конвертация документов и изображений | — |
|
||||||
|
| `file-processor-video` | конвертация видео, выключена по умолчанию | — |
|
||||||
|
| `dragonfly` | Redis-совместимый кеш и сессии | 6379 |
|
||||||
|
| `nats` | брокер сообщений JetStream | 4222 |
|
||||||
|
|
||||||
Для скачивания приватного Docker-образа создайте Kubernetes Secret с помощью предоставленного JSON-ключа:
|
Все три публичных маршрута живут на одном домене: `/` — приложение,
|
||||||
|
`/chatbox/` — вебсокеты, `/nanabush-player` — плеер курсов.
|
||||||
|
|
||||||
|
## Что нужно от вас
|
||||||
|
|
||||||
|
- Kubernetes 1.21+ и ingress-контроллер (nginx или traefik);
|
||||||
|
- PostgreSQL 14+ (с расширением Citus, если планируется шардирование);
|
||||||
|
- S3-совместимое хранилище: AWS S3, MinIO, Yandex Object Storage, VK Cloud;
|
||||||
|
- SMTP-сервер;
|
||||||
|
- доступ к Docker-реестру HRBox и токен HRBox Hub — выдают сотрудники HRBox.
|
||||||
|
|
||||||
|
PostgreSQL и S3 чарт не разворачивает: это состояние, которое переживает
|
||||||
|
установку и требует собственного резервного копирования.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### 1. Доступ к реестру образов
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl create secret docker-registry regsecret \
|
kubectl create secret docker-registry regsecret \
|
||||||
@@ -18,57 +52,289 @@ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Сгенерируйте секретные ключи приложения
|
Или выпустите сертификат через cert-manager и укажите его Secret
|
||||||
|
в `ingress.tls.secretName`.
|
||||||
|
|
||||||
Для обеспечения безопасности необходимо сгенерировать и указать в `values.yaml` следующие ключи:
|
### 3. Ключи
|
||||||
|
|
||||||
- **cookieValidationKey** — 32 байта в hex (например: `openssl rand -hex 16`)
|
|
||||||
- **encryptionKey** — 64 байта в base64 (например: `openssl rand -base64 48`)
|
|
||||||
- **servicesInternalSecret** — 64 байта в base64 (например: `openssl rand -base64 48`)
|
|
||||||
- **chatbox.wrappingKey** — 32 символа в hex (например: `openssl rand -hex 16`)
|
|
||||||
|
|
||||||
Эти ключи используются для защиты cookie, шифрования данных, межсервисной коммуникации и шифрования чатов.
|
|
||||||
|
|
||||||
### 4. Заполните обязательные параметры в `values.yaml`
|
|
||||||
|
|
||||||
Обязательно укажите в конфигурации:
|
|
||||||
|
|
||||||
- Параметры подключения к базе данных PostgreSQL (`app.postgres.host`, `port`, `database`, `user`, `password`).
|
|
||||||
- Настройки S3-совместимого хранилища (`app.s3.endpoint`, `bucket`, `key`, `secret`).
|
|
||||||
- SMTP-настройки для отправки почты (`app.smtp.host`, `port`, `user`, `password`, `from`, `fromHost`).
|
|
||||||
- Домен для приложения (`app.defaultTenantHostname`) и Ingress (`ingress.host`).
|
|
||||||
- Имя TLS-секрета для HTTPS (`ingress.tls.secretName`), созданного на шаге 2.
|
|
||||||
- Ключ для hrbox hub (предоставляется сотрудниками hrbox)
|
|
||||||
|
|
||||||
Прочие настройки и пример `values.yml` с подробным описанием ключей вы можете найти [тут](.helm/values.yaml)
|
|
||||||
|
|
||||||
### 5. Установите или обновите Helm-релиз
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install hrbox . -f values.yaml
|
# secrets.cookieValidationKey и app.chatbox.wrappingKey
|
||||||
|
openssl rand -hex 16
|
||||||
|
|
||||||
|
# secrets.encryptionKey, secrets.servicesInternalSecret,
|
||||||
|
# app.nanabushPlayer.keys.ticketHs256, app.nanabushPlayer.keys.sessionHs256
|
||||||
|
openssl rand -base64 48
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Дождитесь завершения миграций
|
`secrets.encryptionKey` — единственный ключ, потеря которого необратима:
|
||||||
|
им зашифрованы пароли интеграций в базе. Сохраните его отдельно от кластера.
|
||||||
|
|
||||||
Job `hrbox-migrate-xxxxx` должна завершиться
|
Ключи плеера `ticketHs256` и `sessionHs256` должны отличаться друг от друга —
|
||||||
|
это ключи разных контуров, и чарт откажется ставить одинаковые.
|
||||||
|
|
||||||
### 7. При первом развертывании - создайте суперпользователя
|
### 4. Заполните `values.yaml`
|
||||||
|
|
||||||
|
Обязательный минимум:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ingress:
|
||||||
|
host: "hrbox.company.com"
|
||||||
|
tls:
|
||||||
|
secretName: "hrbox-tls"
|
||||||
|
|
||||||
|
app:
|
||||||
|
defaultTenantHostname: "hrbox.company.com"
|
||||||
|
postgres:
|
||||||
|
host: "postgresql"
|
||||||
|
database: "hrbox"
|
||||||
|
user: "hrbox"
|
||||||
|
password: "..."
|
||||||
|
s3:
|
||||||
|
endpoint: "https://storage.company.com"
|
||||||
|
bucket: "hrbox"
|
||||||
|
key: "..."
|
||||||
|
secret: "..."
|
||||||
|
smtp:
|
||||||
|
host: "smtp.company.com"
|
||||||
|
user: "noreply@company.com"
|
||||||
|
password: "..."
|
||||||
|
from: "noreply@company.com"
|
||||||
|
fromHost: "company.com"
|
||||||
|
hub:
|
||||||
|
token: "..."
|
||||||
|
chatbox:
|
||||||
|
wrappingKey: "..."
|
||||||
|
nanabushPlayer:
|
||||||
|
keys:
|
||||||
|
ticketHs256: "..."
|
||||||
|
sessionHs256: "..."
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
cookieValidationKey: "..."
|
||||||
|
encryptionKey: "..."
|
||||||
|
servicesInternalSecret: "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Всё остальное имеет рабочие значения по умолчанию.
|
||||||
|
|
||||||
|
Проверить конфигурацию, ничего не устанавливая:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl get pods
|
helm template hrbox .helm -f values.yaml > /dev/null
|
||||||
|
|
||||||
NAME READY STATUS RESTARTS AGE
|
|
||||||
...
|
|
||||||
hrbox-worker-7898b77b85-5ppwm 1/1 Running 0 53s
|
|
||||||
hrbox-worker-7898b77b85-9fnkc 1/1 Running 0 53s
|
|
||||||
|
|
||||||
kubectl exec -it hrbox-worker-7898b77b85-5ppwm bash
|
|
||||||
|
|
||||||
php yii user/create-admin youremail@hrbox.io --tenant-id=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Если чего-то не хватает, чарт скажет об этом текстом, а не оставит
|
||||||
|
неработающие поды в кластере.
|
||||||
|
|
||||||
|
### 5. Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade --install hrbox .helm -n hrbox --create-namespace -f values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Дождитесь миграций
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n hrbox wait --for=condition=complete --timeout=30m job -l app=jobs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Первый администратор
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n hrbox exec -it deploy/hrbox-worker -- \
|
||||||
|
php yii user/create-admin admin@company.com --tenant-id=1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Секреты
|
||||||
|
|
||||||
|
Чувствительные значения из `values.yaml` чарт складывает в один Secret
|
||||||
|
`hrbox-secrets` и монтирует контейнерам через `secretKeyRef` — в спеке
|
||||||
|
Deployment паролей нет.
|
||||||
|
|
||||||
|
Если секретами управляет внешний инструмент, создайте Secret сами и укажите
|
||||||
|
его имя в `secrets.existingSecret`. Ключи должны называться так:
|
||||||
|
|
||||||
|
| Ключ | Откуда берётся при `existingSecret: ""` |
|
||||||
|
|---|---|
|
||||||
|
| `cookie-validation-key` | `secrets.cookieValidationKey` |
|
||||||
|
| `encryption-key` | `secrets.encryptionKey` |
|
||||||
|
| `services-internal-secret` | `secrets.servicesInternalSecret` |
|
||||||
|
| `postgres-password` | `app.postgres.password` |
|
||||||
|
| `s3-key` | `app.s3.key` |
|
||||||
|
| `s3-secret` | `app.s3.secret` |
|
||||||
|
| `smtp-password` | `app.smtp.password` |
|
||||||
|
| `hub-token` | `app.hub.token` |
|
||||||
|
| `chatbox-wrapping-key` | `app.chatbox.wrappingKey` |
|
||||||
|
| `livekit-api-secret` | `app.livekit.apiSecret` |
|
||||||
|
| `yandex-client-password` | `app.yandex.clientPassword` |
|
||||||
|
| `yandex-ai-api-key` | `app.ai.yandex.apiKey` |
|
||||||
|
| `google-client-secret` | `app.integrations.google.clientSecret` |
|
||||||
|
| `slack-client-secret` | `app.slack.clientSecret` |
|
||||||
|
| `slack-verification-token` | `app.slack.verificationToken` |
|
||||||
|
|
||||||
|
Ключи плеера лежат в отдельном Secret `hrbox-nanabush-player` — им управляет
|
||||||
|
`app.nanabushPlayer.deployment.existingSecret`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nanabush Player
|
||||||
|
|
||||||
|
Проигрыватель учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные
|
||||||
|
курсы, тесты и эссе.
|
||||||
|
|
||||||
|
В облаке HRBox плеер живёт на отдельном поддомене. В on-premise выделенного
|
||||||
|
поддомена нет, поэтому плеер монтируется на основной домен под путём
|
||||||
|
`/nanabush-player`. Что из этого следует:
|
||||||
|
|
||||||
|
- **Изоляции cookie нет.** Плеер и HRBox делят один origin, поэтому cookie
|
||||||
|
сессии HRBox отправляются браузером и на запросы к содержимому курсов,
|
||||||
|
включая сторонние SCORM-пакеты. Если нужна настоящая изоляция —
|
||||||
|
запрашивайте у HRBox схему с отдельным поддоменом.
|
||||||
|
- **Часы должны быть синхронизированы.** Launch-тикет живёт 5 минут, сессия
|
||||||
|
проверяется с допуском 2 минуты. Настройте NTP на нодах и сервере БД;
|
||||||
|
если это невозможно, выключите плеер (`app.nanabushPlayer.enabled: false`).
|
||||||
|
|
||||||
|
Порядок первого включения:
|
||||||
|
|
||||||
|
1. Убедитесь, что миграции применились: плееру нужны таблицы проекций.
|
||||||
|
2. Дождитесь, пока **все** поды `web` перейдут на новую ревизию. Смешанный
|
||||||
|
выкат при `rollout.percent > 0` может создать вторую активную попытку по
|
||||||
|
одному назначению.
|
||||||
|
3. Проверьте, что плеер отвечает сам, а не HRBox через общий маршрут:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n hrbox exec deploy/hrbox-worker -- \
|
||||||
|
curl -s http://hrbox-nanabush-player:8094/readyz
|
||||||
|
```
|
||||||
|
|
||||||
|
Настоящий плеер вернёт JSON. HTML означает, что запрос ушёл в HRBox.
|
||||||
|
|
||||||
|
4. Проверьте публичный путь: `https://<домен>/nanabush-player/start`,
|
||||||
|
статику и API плеера под этим путём.
|
||||||
|
|
||||||
|
Служебные пути (`/healthz`, `/readyz`, метрики) живут в корне и не зависят
|
||||||
|
от `basePath`. Порт метрик наружу не публикуется намеренно.
|
||||||
|
|
||||||
|
Аварийное отключение — три независимых переключателя:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
nanabushPlayer:
|
||||||
|
enabled: false # пользователи возвращаются на старый проигрыватель
|
||||||
|
ingress:
|
||||||
|
enabled: false # маршрут снимается
|
||||||
|
deployment:
|
||||||
|
enabled: false # под останавливается
|
||||||
|
```
|
||||||
|
|
||||||
|
Выключать нужно сверху вниз: чарт не даст оставить включённым маршрут
|
||||||
|
без пода или раскатку без маршрута.
|
||||||
|
|
||||||
|
Подробности контракта — `docs/nanabush-player-deployment.md` в репозитории HRBox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Эксплуатация
|
||||||
|
|
||||||
|
### Обновление
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade hrbox .helm -n hrbox -f values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Миграции и обновление кластера запускаются автоматически как хуки
|
||||||
|
`post-upgrade` и выполняются строго по порядку. Имя джобы содержит номер
|
||||||
|
ревизии релиза, поэтому в `kubectl get jobs` видно, какой выкат её запускал.
|
||||||
|
|
||||||
|
### Масштабирование
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
replicas:
|
||||||
|
web: 4 # по числу одновременных пользователей
|
||||||
|
worker: 3 # по объёму фоновых задач
|
||||||
|
chatbox: 4 # по числу открытых вкладок
|
||||||
|
```
|
||||||
|
|
||||||
|
`conductor` всегда в одном экземпляре: второй планировщик запустил бы
|
||||||
|
каждую задачу дважды.
|
||||||
|
|
||||||
|
Для `web` при двух и более репликах создаётся PodDisruptionBudget
|
||||||
|
(`minAvailable: 75%`), чтобы drain ноды не уронил приложение целиком.
|
||||||
|
|
||||||
|
### Конвертация видео
|
||||||
|
|
||||||
|
Выключена по умолчанию: профиль ресурсов у неё на порядок тяжелее обычной
|
||||||
|
обработки файлов. Включение:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
videoConverter:
|
||||||
|
enabled: true
|
||||||
|
nodeSelector:
|
||||||
|
role: converter-dedicated
|
||||||
|
tolerations:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: video-converter
|
||||||
|
effect: NoSchedule
|
||||||
|
```
|
||||||
|
|
||||||
|
### Трассировка
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
tempoUrl: "http://tempo.monitoring:4317"
|
||||||
|
```
|
||||||
|
|
||||||
|
Рядом с `web` поднимется коллектор OpenTelemetry, PHP начнёт слать трейсы.
|
||||||
|
|
||||||
|
### Внешние Redis и NATS
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dragonfly:
|
||||||
|
enabled: false
|
||||||
|
nats:
|
||||||
|
enabled: false
|
||||||
|
app:
|
||||||
|
redis:
|
||||||
|
host: "redis.company.local"
|
||||||
|
nats:
|
||||||
|
url: "nats://n1:4222,nats://n2:4222,nats://n3:4222"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Диагностика
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n hrbox get pods
|
||||||
|
kubectl -n hrbox logs -l app=web -c fpm --tail=200
|
||||||
|
kubectl -n hrbox logs -l app=worker --tail=200
|
||||||
|
kubectl -n hrbox logs -l app=nanabush-player --tail=200
|
||||||
|
kubectl -n hrbox get jobs
|
||||||
|
```
|
||||||
|
|
||||||
|
| Симптом | Куда смотреть |
|
||||||
|
|---|---|
|
||||||
|
| Поды `web` не готовы | пробы `/healthz` и `/health-check`, логи контейнера `fpm` |
|
||||||
|
| Джоба миграций висит | init-контейнер `wait-postgres`: доступность и права в БД |
|
||||||
|
| Курс не открывается | логи `nanabush-player`, совпадение домена в `ingress.host` и `app.defaultTenantHostname` |
|
||||||
|
| Не приходят уведомления | логи `goworker`, очереди NATS, `app.launchpad.topics` |
|
||||||
|
| Пользователей разлогинивает | перезапуски `dragonfly`: сессии живут в памяти |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Миграция с версии 2.x
|
||||||
|
|
||||||
|
Часть ключей `values.yaml` переехала. Чарт проверяет старые имена и
|
||||||
|
останавливает установку с подсказкой, поэтому незамеченным ничего не
|
||||||
|
останется. Полный список изменений и порядок перехода — в [CHANGELOG.md](CHANGELOG.md).
|
||||||
|
|||||||
Reference in New Issue
Block a user