Files
hrbox-helm-chart/.helm/templates/09-nanabush-player.yaml
T
gnome627andClaude Opus 5 efc506e5de 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>
2026-09-02 16:37:42 +06:00

233 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{- $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 }}