{{- $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 }}