diff --git a/.helm/Chart.yaml b/.helm/Chart.yaml index a4f081b..c76d31c 100644 --- a/.helm/Chart.yaml +++ b/.helm/Chart.yaml @@ -1,19 +1,21 @@ apiVersion: v2 name: hrbox -description: Simplified HRBox application Helm chart +description: HRBox — HR-платформа для on-premise установки type: application -# Версия самого чарта. Увеличивается при каждом изменении чарта -version: 2.0.0 +version: 3.0.0 +appVersion: "3.1.0" -# Версия приложения, которое деплоится. Не обязательно увеличивать при изменении чарта -appVersion: "3.0.0" - -# Ключевые слова для поиска keywords: - hrbox - hr - - application + - lms + - kedo + - on-premise -# Домашняя страница проекта home: https://hrbox.io + +annotations: + hrbox.io/components: >- + web, worker, conductor, chatbox, goworker, kedoca, geonames, + file-processor, nanabush-player, dragonfly, nats diff --git a/.helm/templates/00-app-secret.yaml b/.helm/templates/00-app-secret.yaml new file mode 100644 index 0000000..db1865a --- /dev/null +++ b/.helm/templates/00-app-secret.yaml @@ -0,0 +1,26 @@ +{{- if not .Values.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 }} diff --git a/.helm/templates/00-nanabush-player-secret.yaml b/.helm/templates/00-nanabush-player-secret.yaml new file mode 100644 index 0000000..8ac26d1 --- /dev/null +++ b/.helm/templates/00-nanabush-player-secret.yaml @@ -0,0 +1,17 @@ +{{- $cfg := .Values.app.nanabushPlayer.deployment }} +{{- if and $cfg.enabled $cfg.existingSecret.create }} +--- +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 }} diff --git a/.helm/templates/00-validate.yaml b/.helm/templates/00-validate.yaml new file mode 100644 index 0000000..de11381 --- /dev/null +++ b/.helm/templates/00-validate.yaml @@ -0,0 +1,132 @@ +{{- 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 }} + +{{- 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 }} + +{{- 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 }} + +{{- $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 }} diff --git a/.helm/templates/01-nats-configmap.yaml b/.helm/templates/01-nats-configmap.yaml index 38e58d7..98841d4 100644 --- a/.helm/templates/01-nats-configmap.yaml +++ b/.helm/templates/01-nats-configmap.yaml @@ -1,11 +1,14 @@ +{{- if .Values.nats.enabled }} +{{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}} --- apiVersion: v1 kind: ConfigMap metadata: - name: {{ .Chart.Name }}-nats-config + name: {{ printf "%s-config" $name }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }} data: nats.conf: | - # PID file shared with configuration reloader. pid_file: "/var/run/nats/nats.pid" ############### @@ -42,17 +45,18 @@ data: ################################### cluster { port: 6222 - name: {{ .Chart.Name }}-nats-cluster + name: {{ printf "%s-cluster" $name }} routes = [ {{- 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 }} ] - + cluster_advertise: $CLUSTER_ADVERTISE connect_retries: 120 } {{- end }} - lame_duck_duration: 120s + lame_duck_duration: {{ .Values.nats.terminationGracePeriodSeconds }}s +{{- end }} diff --git a/.helm/templates/01-nginx-assetlinks.yaml b/.helm/templates/01-nginx-assetlinks.yaml new file mode 100644 index 0000000..901bec7 --- /dev/null +++ b/.helm/templates/01-nginx-assetlinks.yaml @@ -0,0 +1,38 @@ +--- +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": [ + { + "/": "/*" + } + ] + } + ] + } + } diff --git a/.helm/templates/01-nginx-configmap.yaml b/.helm/templates/01-nginx-configmap.yaml index ecf780d..e69e5c1 100644 --- a/.helm/templates/01-nginx-configmap.yaml +++ b/.helm/templates/01-nginx-configmap.yaml @@ -3,83 +3,83 @@ apiVersion: v1 kind: ConfigMap metadata: name: web-configuration + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} data: nginx.conf: | user www-data; worker_processes 6; pid /run/nginx.pid; - + events { - worker_connections 10000; - use epoll; + worker_connections 10000; + use epoll; multi_accept on; } - + http { - + set_real_ip_from 10.0.0.0/8; - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 0; - types_hash_max_size 2048; - server_tokens off; - + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 0; + types_hash_max_size 2048; + server_tokens off; + limit_conn_zone $binary_remote_addr zone=addr:10m; - server_names_hash_bucket_size 64; - server_name_in_redirect off; - + server_names_hash_bucket_size 64; + server_name_in_redirect off; + fastcgi_buffer_size 256k; fastcgi_buffers 4 256k; - + client_body_buffer_size 1024k; client_max_body_size 20m; - + client_header_buffer_size 1024k; large_client_header_buffers 16 1024k; - - - default_type application/octet-stream; - include /etc/nginx/mime.types; - - ## - # SSL Settings - ## - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; + + default_type application/octet-stream; + include /etc/nginx/mime.types; + + ## + # SSL Settings + ## + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - rewrite_log on; - - ## - # Gzip Settings - ## - - gzip on; - gzip_disable "msie6"; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - ## - # Virtual Host Configs - ## + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Virtual Host Configs + ## server { listen 80 default; server_name _ ; @@ -96,7 +96,73 @@ data: location ~ /\.(git) { deny all; } - + + location = /healthz { + access_log off; + return 200 "ok\n"; + } + + 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; + } + + 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; + } + + 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 / { add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; @@ -105,38 +171,43 @@ data: add_header X-Content-Type-Options "nosniff"; add_header Permissions-Policy "browsing-topics=()" always; - # Redirect everything that isn't a real file to index.php 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)$ { - + expires 30d; add_header Vary Accept-Encoding; add_header Pragma "public"; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; access_log off; tcp_nodelay off; - + try_files $uri $uri/ /index.php?$args; - + open_file_cache max=3000 inactive=120s; open_file_cache_valid 45s; open_file_cache_min_uses 2; 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$ { 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"; - + fastcgi_split_path_info ^(.+?\.php)(/.*)$; if (!-f $document_root$fastcgi_script_name) { return 404; @@ -151,4 +222,4 @@ data: fastcgi_send_timeout 60s; } } - } \ No newline at end of file + } diff --git a/.helm/templates/01-otel-configmap.yaml b/.helm/templates/01-otel-configmap.yaml new file mode 100644 index 0000000..7fbd607 --- /dev/null +++ b/.helm/templates/01-otel-configmap.yaml @@ -0,0 +1,34 @@ +{{- if .Values.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 }} diff --git a/.helm/templates/01-wait-configmap.yaml b/.helm/templates/01-wait-configmap.yaml index 98c1d03..d1eff32 100644 --- a/.helm/templates/01-wait-configmap.yaml +++ b/.helm/templates/01-wait-configmap.yaml @@ -3,10 +3,12 @@ apiVersion: v1 kind: ConfigMap metadata: name: job-scripts + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 4 }} data: 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 echo "Waiting for postgres" - sleep 2; - done \ No newline at end of file + sleep 2 + done diff --git a/.helm/templates/02-dragonfly.yaml b/.helm/templates/02-dragonfly.yaml index 920f4a6..cb2b2d9 100644 --- a/.helm/templates/02-dragonfly.yaml +++ b/.helm/templates/02-dragonfly.yaml @@ -1,46 +1,61 @@ {{- if .Values.dragonfly.enabled }} +{{- $name := include "hrbox.component" (dict "ctx" . "name" "dragonfly") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-dragonfly + name: {{ $name }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "dragonfly") | nindent 4 }} spec: revisionHistoryLimit: 3 strategy: type: Recreate selector: matchLabels: - component: {{ .Chart.Name }}-dragonfly + component: {{ $name }} template: metadata: labels: - component: {{ .Chart.Name }}-dragonfly + component: {{ $name }} + app: dragonfly spec: - imagePullSecrets: - - name: regsecret + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - - name: dragonfly - image: cr.yandex/crpnn6fi85p3dauha6nc/dragonflydb:1.15.1 - {{- if .Values.dragonfly.maxmemory }} - args: - - --maxmemory={{ .Values.dragonfly.maxmemory }} - {{- end }} - ports: - - name: dragonfly - containerPort: 6379 - livenessProbe: - tcpSocket: - port: 6379 - initialDelaySeconds: 30 + - name: dragonfly + image: {{ .Values.image.dragonfly | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.dragonfly.maxmemory }} + args: + - --maxmemory={{ . }} + {{- end }} + ports: + - name: dragonfly + containerPort: 6379 + {{- include "hrbox.resources" (dict "ctx" . "component" "dragonfly") | indent 10 }} + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 30 + periodSeconds: 10 + --- apiVersion: v1 kind: Service metadata: - name: {{ .Chart.Name }}-dragonfly + name: {{ $name }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "dragonfly") | nindent 4 }} spec: clusterIP: None selector: - component: {{ .Chart.Name }}-dragonfly + component: {{ $name }} ports: - - port: 6379 -{{- end }} \ No newline at end of file + - name: dragonfly + port: 6379 +{{- end }} diff --git a/.helm/templates/02-nats-box.yaml b/.helm/templates/02-nats-box.yaml index 4ff7e03..a610d96 100644 --- a/.helm/templates/02-nats-box.yaml +++ b/.helm/templates/02-nats-box.yaml @@ -1,18 +1,19 @@ +{{- if .Values.nats.enabled }} +{{- $name := include "hrbox.component" (dict "ctx" . "name" "nats") -}} --- apiVersion: apps/v1 kind: StatefulSet metadata: - name: {{ .Chart.Name }}-nats + name: {{ $name }} labels: - app.kubernetes.io/name: nats - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }} spec: selector: matchLabels: app.kubernetes.io/name: nats app.kubernetes.io/instance: {{ .Release.Name }} replicas: {{ .Values.nats.replicas }} - serviceName: {{ .Chart.Name }}-nats + serviceName: {{ $name }} template: metadata: labels: @@ -22,50 +23,50 @@ spec: volumes: - name: config-volume configMap: - name: {{ .Chart.Name }}-nats-config + name: {{ printf "%s-config" $name }} - name: pid emptyDir: {} shareProcessNamespace: true terminationGracePeriodSeconds: {{ .Values.nats.terminationGracePeriodSeconds }} - imagePullSecrets: - - name: regsecret + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: nats - image: {{ .Values.nats.image }} - imagePullPolicy: IfNotPresent + image: {{ .Values.image.nats | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - containerPort: 4222 - name: client - - containerPort: 6222 - name: cluster - - containerPort: 8222 - name: monitor - - containerPort: 7777 - name: metrics + - containerPort: 4222 + name: client + - containerPort: 6222 + name: cluster + - containerPort: 8222 + name: monitor + - containerPort: 7777 + name: metrics command: - - "nats-server" - - "--config" - - "/etc/nats-config/nats.conf" + - "nats-server" + - "--config" + - "/etc/nats-config/nats.conf" env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: SERVER_NAME - value: $(POD_NAME) - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: CLUSTER_ADVERTISE - value: $(POD_NAME).{{ .Chart.Name }}-nats.$(POD_NAMESPACE).{{ .Values.cluster.domain | default "cluster.local" }} + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SERVER_NAME + value: $(POD_NAME) + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLUSTER_ADVERTISE + value: $(POD_NAME).{{ $name }}.$(POD_NAMESPACE).svc.{{ .Values.cluster.domain }} + {{- include "hrbox.resources" (dict "ctx" . "component" "nats") | indent 10 }} volumeMounts: - name: config-volume mountPath: /etc/nats-config - name: pid mountPath: /var/run/nats {{- if .Values.nats.jetstream.fileStorage.enabled }} - - name: {{ .Chart.Name }}-nats-js-pvc + - name: {{ printf "%s-js-pvc" $name }} mountPath: {{ .Values.nats.jetstream.fileStorage.storageDirectory }} {{- end }} livenessProbe: @@ -84,20 +85,20 @@ spec: preStop: exec: command: - - "/bin/sh" - - "-c" - - "nats-server -sl=ldm=/var/run/nats/nats.pid && /bin/sleep 120" - + - "/bin/sh" + - "-c" + - "nats-server -sl=ldm=/var/run/nats/nats.pid && /bin/sleep {{ .Values.nats.terminationGracePeriodSeconds }}" + {{- if .Values.nats.reloader.enabled }} - name: reloader - image: natsio/nats-server-config-reloader:0.6.2 - imagePullPolicy: IfNotPresent + image: {{ .Values.image.natsConfigReloader | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} command: - - "nats-server-config-reloader" - - "-pid" - - "/var/run/nats/nats.pid" - - "-config" - - "/etc/nats-config/nats.conf" + - "nats-server-config-reloader" + - "-pid" + - "/var/run/nats/nats.pid" + - "-config" + - "/etc/nats-config/nats.conf" volumeMounts: - name: config-volume mountPath: /etc/nats-config @@ -108,15 +109,15 @@ spec: {{- if .Values.nats.jetstream.fileStorage.enabled }} volumeClaimTemplates: - metadata: - name: {{ .Chart.Name }}-nats-js-pvc + name: {{ printf "%s-js-pvc" $name }} spec: accessModes: - ReadWriteOnce resources: requests: storage: {{ .Values.nats.jetstream.fileStorage.size }} - {{- if .Values.nats.jetstream.fileStorage.storageClassName }} - storageClassName: {{ .Values.nats.jetstream.fileStorage.storageClassName }} + {{- with .Values.nats.jetstream.fileStorage.storageClassName }} + storageClassName: {{ . | quote }} {{- end }} {{- end }} @@ -124,21 +125,21 @@ spec: apiVersion: v1 kind: Service metadata: - name: {{ .Chart.Name }}-nats + name: {{ $name }} labels: - app.kubernetes.io/name: nats - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "hrbox.labels" (dict "ctx" . "name" "nats") | nindent 4 }} spec: + clusterIP: None selector: app.kubernetes.io/name: nats app.kubernetes.io/instance: {{ .Release.Name }} - clusterIP: None ports: - - name: client - port: 4222 - - name: cluster - port: 6222 - - name: monitor - port: 8222 - - name: metrics - port: 7777 \ No newline at end of file + - name: client + port: 4222 + - name: cluster + port: 6222 + - name: monitor + port: 8222 + - name: metrics + port: 7777 +{{- end }} diff --git a/.helm/templates/02-web-pdb.yaml b/.helm/templates/02-web-pdb.yaml new file mode 100644 index 0000000..2849908 --- /dev/null +++ b/.helm/templates/02-web-pdb.yaml @@ -0,0 +1,15 @@ +{{- $replicas := .Values.app.replicas.web | int }} +{{- 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 }} diff --git a/.helm/templates/02-web.yaml b/.helm/templates/02-web.yaml index dceb2a2..7b6c0de 100644 --- a/.helm/templates/02-web.yaml +++ b/.helm/templates/02-web.yaml @@ -1,48 +1,59 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "web") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-web + name: {{ $name }} labels: - app: web + {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} spec: minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 - maxUnavailable: 1 + maxUnavailable: 0 replicas: {{ .Values.app.replicas.web }} selector: matchLabels: - service: {{ .Chart.Name }}-web + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 6 }} template: metadata: labels: - service: {{ .Chart.Name }}-web + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 8 }} + app: web annotations: prometheus.io/scrape: 'true' 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: - imagePullSecrets: - - name: regsecret + terminationGracePeriodSeconds: 30 + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - 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"] ports: - name: fpm containerPort: 9000 env: {{- 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: preStop: exec: command: - sh - '-c' - - sleep 5 && kill -SIGQUIT 1 + - sleep 15 && kill -SIGQUIT 1 readinessProbe: tcpSocket: port: 9000 @@ -53,48 +64,104 @@ spec: port: 9000 initialDelaySeconds: 5 periodSeconds: 10 + - name: nginx - image: {{ .Values.app.image }} + image: {{ .Values.image.hrbox | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["/usr/sbin/nginx", "-g", "daemon off;"] ports: - name: http containerPort: 80 env: {{- include "envs_all" . | indent 12 }} - {{- include "nginx_resources" . | indent 10}} + {{- include "hrbox.resources" (dict "ctx" . "component" "nginx") | indent 10 }} lifecycle: preStop: exec: command: - sh - '-c' - - sleep 5 && /usr/sbin/nginx -s quit + - sleep 15 && /usr/sbin/nginx -s quit + 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: - name: configs mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - - name: phpfpm-exporter - image: cr.yandex/crpnn6fi85p3dauha6nc/php-fpm_exporter:latest + - 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: 9253 + - 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 + image: {{ .Values.image.phpFpmExporter | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 9253 + name: metrics env: - - name: PHP_FPM_SCRAPE_URI - value: "tcp://127.0.0.1:9000/hrbox-fpm-status" + - name: PHP_FPM_SCRAPE_URI + value: "tcp://127.0.0.1:9000/hrbox-fpm-status" + volumes: - name: configs configMap: name: web-configuration 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 kind: Service metadata: - name: {{ .Chart.Name }}-web + name: {{ $name }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} spec: selector: - service: {{ .Chart.Name }}-web + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "web") | nindent 4 }} ports: - name: http port: 80 - protocol: TCP \ No newline at end of file + protocol: TCP diff --git a/.helm/templates/03-chatbox.yaml b/.helm/templates/03-chatbox.yaml new file mode 100644 index 0000000..fdfc41c --- /dev/null +++ b/.helm/templates/03-chatbox.yaml @@ -0,0 +1,57 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "chatbox") -}} +--- +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 diff --git a/.helm/templates/03-websocket.yaml b/.helm/templates/03-websocket.yaml deleted file mode 100644 index b820086..0000000 --- a/.helm/templates/03-websocket.yaml +++ /dev/null @@ -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 diff --git a/.helm/templates/04-worker.yaml b/.helm/templates/04-worker.yaml index 259078d..8901c90 100644 --- a/.helm/templates/04-worker.yaml +++ b/.helm/templates/04-worker.yaml @@ -1,10 +1,11 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "worker") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-worker + name: {{ $name }} labels: - app: worker + {{- include "hrbox.labels" (dict "ctx" . "name" "worker") | nindent 4 }} spec: minReadySeconds: 5 strategy: @@ -12,22 +13,26 @@ spec: replicas: {{ .Values.app.replicas.worker }} selector: matchLabels: - service: {{ .Chart.Name }}-worker + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "worker") | nindent 6 }} template: metadata: labels: - service: {{ .Chart.Name }}-worker + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "worker") | nindent 8 }} + app: worker annotations: prometheus.io/scrape: 'true' prometheus.io/port: '9253' + checksum/app-secret: {{ include (print $.Template.BasePath "/00-app-secret.yaml") . | sha256sum }} spec: - imagePullSecrets: - - name: regsecret + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: worker - image: {{ .Values.app.image }} + image: {{ .Values.image.hrbox | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["hrbox-worker"] env: {{- include "envs_all" . | indent 12 }} - {{- include "worker_resources" . | indent 10}} - terminationGracePeriodSeconds: 600 \ No newline at end of file + - name: VIDEO_CONVERTER_ENABLED + value: {{ .Values.app.videoConverter.enabled | quote }} + {{- include "hrbox.resources" (dict "ctx" . "component" "worker") | indent 10 }} + terminationGracePeriodSeconds: 600 diff --git a/.helm/templates/05-conductor.yaml b/.helm/templates/05-conductor.yaml index a481e70..bf93a28 100644 --- a/.helm/templates/05-conductor.yaml +++ b/.helm/templates/05-conductor.yaml @@ -1,40 +1,72 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "conductor") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-conductor + name: {{ $name }} labels: - app: conductor + {{- include "hrbox.labels" (dict "ctx" . "name" "conductor") | nindent 4 }} spec: replicas: 1 strategy: type: Recreate selector: matchLabels: - service: {{ .Chart.Name }}-conductor + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "conductor") | nindent 6 }} template: metadata: 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: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - conductor - topologyKey: kubernetes.io/hostname - imagePullSecrets: - - name: regsecret + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - conductor + topologyKey: kubernetes.io/hostname + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: conductor - {{- include "conductor_resources" . | indent 10 }} - image: cr.yandex/crphnph9c2s776j32sjd/hrbox-conductor:2.2.0 - imagePullPolicy: IfNotPresent + image: {{ .Values.image.conductor | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8080 env: - {{- include "envs_all" . | indent 12 }} \ No newline at end of file + {{- 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" diff --git a/.helm/templates/09-file-processor-video.yaml b/.helm/templates/09-file-processor-video.yaml new file mode 100644 index 0000000..7b0b7bb --- /dev/null +++ b/.helm/templates/09-file-processor-video.yaml @@ -0,0 +1,49 @@ +{{- if .Values.app.videoConverter.enabled }} +{{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor-video") -}} +--- +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 }} diff --git a/.helm/templates/09-file-processor.yaml b/.helm/templates/09-file-processor.yaml index 200df85..772a273 100644 --- a/.helm/templates/09-file-processor.yaml +++ b/.helm/templates/09-file-processor.yaml @@ -1,11 +1,11 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "file-processor") -}} --- -{{ $name := printf "%s-file-processor" (.Chart.Name) }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ $name }} labels: - app: file-processor + {{- include "hrbox.labels" (dict "ctx" . "name" "file-processor") | nindent 4 }} spec: minReadySeconds: 5 strategy: @@ -13,22 +13,25 @@ spec: rollingUpdate: maxSurge: 1 maxUnavailable: 1 - replicas: {{ .Values.app.file_processor.replicas }} + replicas: {{ .Values.app.replicas.fileProcessor }} selector: matchLabels: - service: {{ $name }} + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "file-processor") | nindent 6 }} template: metadata: 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: - imagePullSecrets: - - name: regsecret + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: file-processor - image: {{ .Values.app.file_processor.image }} - imagePullPolicy: Always + image: {{ .Values.image.fileProcessor | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} env: - {{- include "envs_all" . | indent 12 }} + {{- include "envs_all" . | indent 12 }} - name: VIDEO_CONVERTER_ENABLED value: "false" + {{- include "hrbox.resources" (dict "ctx" . "component" "fileProcessor") | indent 10 }} diff --git a/.helm/templates/09-geonames.yaml b/.helm/templates/09-geonames.yaml new file mode 100644 index 0000000..306d6a1 --- /dev/null +++ b/.helm/templates/09-geonames.yaml @@ -0,0 +1,57 @@ +{{- $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 diff --git a/.helm/templates/09-geonames.yml b/.helm/templates/09-geonames.yml deleted file mode 100644 index 06ee5e0..0000000 --- a/.helm/templates/09-geonames.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.helm/templates/09-goworker.yaml b/.helm/templates/09-goworker.yaml index 87c7196..8186f4a 100644 --- a/.helm/templates/09-goworker.yaml +++ b/.helm/templates/09-goworker.yaml @@ -1,10 +1,11 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "goworker") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-goworker + name: {{ $name }} labels: - app: goworker + {{- include "hrbox.labels" (dict "ctx" . "name" "goworker") | nindent 4 }} spec: minReadySeconds: 5 strategy: @@ -12,39 +13,46 @@ spec: rollingUpdate: maxSurge: 1 maxUnavailable: 1 - replicas: {{ .Values.app.goworker.replicas }} + replicas: {{ .Values.app.replicas.goworker }} selector: matchLabels: - service: {{ .Chart.Name }}-goworker + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "goworker") | nindent 6 }} template: metadata: 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: - imagePullSecrets: - - name: regsecret + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: goworker + image: {{ .Values.image.goworker | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} stdin: true tty: true - image: {{ .Values.app.goworker.image }} - imagePullPolicy: Always + ports: + - name: http + containerPort: 8686 env: {{- include "envs_all" . | indent 12 }} - {{- include "goworker_resources" . | indent 10 }} + {{- include "hrbox.resources" (dict "ctx" . "component" "goworker") | indent 10 }} --- apiVersion: v1 kind: Service metadata: - name: {{ .Chart.Name }}-goworker + name: {{ $name }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "goworker") | nindent 4 }} annotations: prometheus.io/scrape: "true" prometheus.io/port: "8686" prometheus.io/path: "/metrics" spec: selector: - service: {{ .Chart.Name }}-goworker + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "goworker") | nindent 4 }} ports: - name: http port: 8686 diff --git a/.helm/templates/09-kedoca.yaml b/.helm/templates/09-kedoca.yaml index d4ee3d0..46ea5c9 100644 --- a/.helm/templates/09-kedoca.yaml +++ b/.helm/templates/09-kedoca.yaml @@ -1,10 +1,11 @@ +{{- $name := include "hrbox.component" (dict "ctx" . "name" "kedoca") -}} --- apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Chart.Name }}-kedoca + name: {{ $name }} labels: - app: kedoca + {{- include "hrbox.labels" (dict "ctx" . "name" "kedoca") | nindent 4 }} spec: minReadySeconds: 5 strategy: @@ -12,27 +13,42 @@ spec: rollingUpdate: maxSurge: 1 maxUnavailable: 1 - replicas: {{ .Values.app.kedoca.replicas }} + replicas: {{ .Values.app.replicas.kedoca }} selector: matchLabels: - service: {{ .Chart.Name }}-kedoca + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "kedoca") | nindent 6 }} template: metadata: 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: - imagePullSecrets: - - name: regsecret + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - kedoca + topologyKey: kubernetes.io/hostname + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} containers: - name: kedoca - image: {{ .Values.app.kedoca.image }} - imagePullPolicy: Always + image: {{ .Values.image.kedoca | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8558 env: {{- include "envs_all" . | indent 12 }} {{- include "envs_kedoca" . | indent 12 }} - ports: - - containerPort: 8558 - {{- include "kedoca_resources" . | indent 10 }} + {{- include "hrbox.resources" (dict "ctx" . "component" "kedoca") | indent 10 }} {{- if .Values.app.kedoca.secretName }} volumeMounts: - name: ca-cert @@ -42,22 +58,22 @@ spec: volumes: - name: ca-cert secret: - secretName: {{ .Values.app.kedoca.secretName }} + secretName: {{ .Values.app.kedoca.secretName | quote }} {{- end }} --- apiVersion: v1 kind: Service metadata: - name: {{ .Chart.Name }}-kedoca + name: {{ $name }} labels: - app: kedoca + {{- include "hrbox.labels" (dict "ctx" . "name" "kedoca") | nindent 4 }} spec: + type: ClusterIP selector: - service: {{ .Chart.Name }}-kedoca + {{- include "hrbox.selectorLabels" (dict "ctx" . "name" "kedoca") | nindent 4 }} ports: - name: http port: 8558 targetPort: 8558 protocol: TCP - type: ClusterIP diff --git a/.helm/templates/09-nanabush-player.yaml b/.helm/templates/09-nanabush-player.yaml new file mode 100644 index 0000000..6de1890 --- /dev/null +++ b/.helm/templates/09-nanabush-player.yaml @@ -0,0 +1,203 @@ +{{- $cfg := .Values.app.nanabushPlayer.deployment }} +{{- if $cfg.enabled }} +{{- $name := include "nanabushPlayer.serviceName" . -}} +{{- $secretName := include "nanabushPlayer.secretName" . -}} +--- +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: + 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 }} + - 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 }} + - name: PLAYER_TICKET_KEYS + valueFrom: + secretKeyRef: + name: {{ $secretName | quote }} + key: {{ . | quote }} + {{- with $cfg.ticket.keylessAcceptedUntil }} + - 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 }} + - 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 }} + ports: + - name: http + port: {{ .Values.app.nanabushPlayer.service.port }} + targetPort: http + protocol: TCP +{{- end }} diff --git a/.helm/templates/10-app-ingress.yaml b/.helm/templates/10-app-ingress.yaml index 79a5b89..7252fa2 100644 --- a/.helm/templates/10-app-ingress.yaml +++ b/.helm/templates/10-app-ingress.yaml @@ -1,35 +1,52 @@ +{{- if .Values.ingress.enabled }} +{{- $player := .Values.app.nanabushPlayer -}} --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ .Chart.Name }}-ingress + name: {{ include "hrbox.component" (dict "ctx" . "name" "ingress") }} + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "web") | nindent 4 }} annotations: - kubernetes.io/ingress.class: {{ .Values.ingress.className }} - nginx.org/websocket-services: {{ .Chart.Name }}-chatbox - nginx.ingress.kubernetes.io/proxy-body-size: "10m" + kubernetes.io/ingress.class: {{ .Values.ingress.className | quote }} + nginx.org/websocket-services: {{ include "hrbox.component" (dict "ctx" . "name" "chatbox") | quote }} + nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.maxBodySize | quote }} nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/configuration-snippet: | proxy_set_header X-Forwarded-For "$remote_addr, $server_addr"; proxy_set_header X-Forwarded-Port $pass_port; proxy_set_header X-Forwarded-Proto $pass_access_scheme; + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: + ingressClassName: {{ .Values.ingress.className | quote }} rules: - - host: {{ .Values.ingress.host | quote }} # something.example.com + - host: {{ .Values.ingress.host | quote }} http: 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/ pathType: Prefix backend: service: - name: {{ .Chart.Name }}-chatbox + name: {{ include "hrbox.component" (dict "ctx" . "name" "chatbox") }} port: number: 80 - path: / pathType: Prefix backend: service: - name: {{ .Chart.Name }}-web + name: {{ include "hrbox.component" (dict "ctx" . "name" "web") }} port: number: 80 {{- if .Values.ingress.tls.enabled }} @@ -37,4 +54,5 @@ spec: - hosts: - {{ .Values.ingress.host | quote }} secretName: {{ .Values.ingress.tls.secretName | quote }} - {{- end }} \ No newline at end of file + {{- end }} +{{- end }} diff --git a/.helm/templates/98-migrate-job.yaml b/.helm/templates/98-migrate-job.yaml index d5e8d20..9d3eb6f 100644 --- a/.helm/templates/98-migrate-job.yaml +++ b/.helm/templates/98-migrate-job.yaml @@ -2,20 +2,28 @@ apiVersion: batch/v1 kind: Job 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: "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" spec: - ttlSecondsAfterFinished: 100 + ttlSecondsAfterFinished: 3600 + backoffLimit: 6 template: + metadata: + labels: + {{- include "hrbox.labels" (dict "ctx" . "name" "jobs") | nindent 8 }} spec: - imagePullSecrets: - - name: regsecret + restartPolicy: OnFailure + terminationGracePeriodSeconds: 5 + {{- include "hrbox.imagePullSecrets" . | nindent 6 }} initContainers: - name: wait-postgres - image: postgres:14-alpine + image: {{ .Values.image.postgresClient | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} command: - bash - /wait.sh @@ -27,16 +35,14 @@ spec: {{- include "envs_all" . | indent 12 }} containers: - name: run-migrations - image: {{ .Values.app.image }} + image: {{ .Values.image.hrbox | quote }} + imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["php", "yii", "migrate", "--interactive=0"] env: {{- include "envs_all" . | indent 12 }} - {{- include "jobs_resources" . | indent 10 }} + {{- include "hrbox.resources" (dict "ctx" . "component" "jobs") | indent 10 }} volumes: - name: configs configMap: name: job-scripts defaultMode: 420 - restartPolicy: OnFailure - terminationGracePeriodSeconds: 5 - backoffLimit: 6 diff --git a/.helm/templates/99-update-cluser-job.yaml b/.helm/templates/99-update-cluser-job.yaml deleted file mode 100644 index 06b8215..0000000 --- a/.helm/templates/99-update-cluser-job.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.helm/templates/99-update-cluster-job.yaml b/.helm/templates/99-update-cluster-job.yaml new file mode 100644 index 0000000..360d09d --- /dev/null +++ b/.helm/templates/99-update-cluster-job.yaml @@ -0,0 +1,30 @@ +--- +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 }} diff --git a/.helm/templates/NOTES.txt b/.helm/templates/NOTES.txt new file mode 100644 index 0000000..08cdc59 --- /dev/null +++ b/.helm/templates/NOTES.txt @@ -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 }} diff --git a/.helm/templates/_envs.tpl b/.helm/templates/_envs.tpl index 4cf8e93..e694f65 100644 --- a/.helm/templates/_envs.tpl +++ b/.helm/templates/_envs.tpl @@ -1,61 +1,50 @@ {{- define "envs_all" }} -# Основные настройки приложения +# Приложение - name: INSTANCE_NAME value: {{ .Values.app.instanceName | quote }} - name: DEFAULT_TENANT_HOSTNAME value: {{ .Values.app.defaultTenantHostname | quote }} - name: APP_ENV value: "prod" +- name: APP_PORT + value: "80" - name: YII_DEBUG value: {{ .Values.app.debug | quote }} - name: YII_TRACE_LEVEL 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 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 value: {{ .Values.app.launchpad.topics | quote }} - name: SMS_SENDER_NAME - value: "HRBOX" -- name: ENABLE_FRONTEND - value: {{ .Values.app.front2 | quote }} -{{- if .Values.app.custom_policy }} + value: {{ .Values.app.sms.senderName | quote }} +- name: IFRAME_FULL_ACCESS + value: {{ .Values.app.security.iframeFullAccess | quote }} +- 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 - value: {{ .Values.app.custom_policy.enabled | default false | quote }} -{{- if .Values.app.custom_policy.enabled }} + value: {{ .Values.app.customPolicy.enabled | quote }} +{{- if .Values.app.customPolicy.enabled }} - 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 - value: {{ .Values.app.custom_policy.terms_url | default "https://hrbox.io/terms" | quote }} -{{- end }} + value: {{ .Values.app.customPolicy.termsUrl | default "https://hrbox.io/terms" | quote }} {{- else }} -- name: ALLOW_CUSTOM_POLICY_URLS - value: "false" -{{- end }} - -# NATS -- 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 }} +- name: CUSTOM_PRIVACY_POLICY_URL + value: "" +- name: CUSTOM_TERMS_URL + value: "" {{- end }} # PostgreSQL @@ -67,74 +56,35 @@ value: {{ .Values.app.postgres.database | quote }} - name: POSTGRES_USER value: {{ .Values.app.postgres.user | quote }} -- name: POSTGRES_PASSWORD - value: {{ .Values.app.postgres.password | quote }} - name: POSTGRES_CITUS 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 - value: {{ .Values.app.redis.host | quote }} + value: {{ include "hrbox.redisHost" . | quote }} - name: REDIS_PORT value: {{ .Values.app.redis.port | quote }} - 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 (опционально) -{{- if .Values.app.yandex.clientId }} -- name: YANDEX_CLIENT_ID - value: {{ .Values.app.yandex.clientId | quote }} -- name: YANDEX_CLIENT_PASSWORD - value: {{ .Values.app.yandex.clientPassword | quote }} +# NATS +- name: BROKER + value: "nats" +- name: NATS_URL + value: {{ include "hrbox.natsUrl" . | quote }} +- name: NATS_STREAM_REPLICAS +{{- if and .Values.nats.enabled .Values.nats.cluster.enabled }} + value: {{ .Values.nats.cluster.replicas | quote }} +{{- else }} + value: "1" {{- end }} -# Slack (опционально) -{{- 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 value: {{ .Values.app.s3.endpoint | quote }} - name: S3_BUCKET 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 value: {{ .Values.app.s3.prefix | quote }} - name: S3_REGION @@ -145,38 +95,118 @@ value: {{ .Values.app.s3.pathStyle | quote }} - name: S3_MULTIPART 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 - value: {{ .Values.app.hub.url | quote }} -- name: HUB_TOKEN - value: {{ .Values.app.hub.token | quote }} +# Почта +- name: SENDER_ENV + value: "PROD" +- name: SENDER_DEV_EMAIL + 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 - 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 value: "conductor" - name: CONDUCTOR_TOPIC_LAUNCHER value: "launcher" +- name: PYTHONUNBUFFERED + value: "1" +- name: DOCCONV_MAX_PARALLEL_TASKS + value: {{ .Values.app.fileProcessor.docconvMaxParallelTasks | 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: "692956564858-ccapt35khk7tmadl17b3879ht2cropp6.apps.googleusercontent.com" -- name: GOOGLE_CLIENT_SECRET - value: "nW4pl4YsVWAR371gMRStW9qg" -- name: SINGLE_TENANT - value: "0" -- name: FORCE_HTTPS - value: "true" + 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: "ab230ee1-6946-43b9-8kfa-447beb8348b0" + value: {{ .Values.app.integrations.glagol.partnerId | quote }} - 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 }} + 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 @@ -195,56 +225,67 @@ valueFrom: fieldRef: 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 }} -# Integration Services -- name: GOWORKER_URL - value: {{ .Values.app.goworker.url | quote }} -{{- if .Values.app.goworker.sentryDsn }} -- name: GOWORKER_SENTRY_DSN - value: {{ .Values.app.goworker.sentryDsn | quote }} +{{- 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 }} -- name: GOWORKER_PUSH_MIN_INTERVAL - value: {{ .Values.app.goworker.pushMinInterval | quote }} - -- name: KEDOCA_URL - value: {{ .Values.app.kedoca.url | quote }} -- name: TSP_URL - value: "http://qs.cryptopro.ru/tsp/tsp.srf" - -# 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 }} +- name: NANABUSH_PLAYER_LEGACY_FALLBACK + value: {{ $player.rollout.legacyFallback | quote }} +- name: NANABUSH_PLAYER_ALLOWED_CONTENT_TYPES + value: {{ join "," $player.rollout.allowedContentTypes | quote }} +- name: NANABUSH_PLAYER_ALLOWED_TENANTS + value: {{ join "," $player.rollout.allowedTenants | quote }} +- name: NANABUSH_PLAYER_ROLLOUT_PERCENT + value: {{ $player.rollout.percent | quote }} {{- end }} -- name: CHATBOX_WRAPPING_KEY - value: {{ .Values.app.chatbox.wrappingKey | quote }} +{{- end -}} + +{{- 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 }} +- name: NANABUSH_PLAYER_TICKET_KEYS + valueFrom: + secretKeyRef: + name: {{ include "nanabushPlayer.secretName" $ | quote }} + key: {{ . | quote }} {{- end }} +{{- end }} +{{- end -}} {{- define "envs_kedoca" }} - name: ROOT_CA_PASS @@ -255,4 +296,21 @@ value: {{ .Values.app.kedoca.algorithm | quote }} - name: KEDO_PKEYOPT value: {{ .Values.app.kedoca.pkeyopt | quote }} -{{- end }} \ No newline at end of file +{{- end }} + +{{- 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 }} diff --git a/.helm/templates/_helpers.tpl b/.helm/templates/_helpers.tpl index 092713b..2d1d224 100644 --- a/.helm/templates/_helpers.tpl +++ b/.helm/templates/_helpers.tpl @@ -1,110 +1,130 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "nats.name" -}} -{{- default .Chart.Name .Values.nats.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- define "hrbox.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{- 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 "-" -}} +{{- define "hrbox.component" -}} +{{- printf "%s-%s" (include "hrbox.name" .ctx) .name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "hrbox.selectorLabels" -}} +service: {{ include "hrbox.component" (dict "ctx" .ctx "name" .name) }} +{{- end -}} + +{{- 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 -}} + +{{- define "hrbox.imagePullSecrets" -}} +{{- if .Values.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.imagePullSecrets }} + - name: {{ .name | quote }} +{{- end }} +{{- end }} +{{- end -}} + +{{- define "hrbox.imageTag" -}} +{{- $lastSegment := splitList "/" . | last -}} +{{- if contains ":" $lastSegment -}} +{{- splitList ":" $lastSegment | last -}} +{{- end -}} +{{- end -}} + +{{- 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 -}} -{{- $name := default .Chart.Name .Values.nats.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- include "hrbox.component" (dict "ctx" . "name" "secrets") -}} +{{- end -}} +{{- end -}} + +{{- define "hrbox.secretEnv" -}} +- name: {{ .env }} + valueFrom: + secretKeyRef: + name: {{ include "hrbox.secretName" .ctx | quote }} + key: {{ .key | quote }} +{{- end -}} + +{{- define "nanabushPlayer.host" -}} +{{- required "Для Nanabush Player нужен непустой ingress.host" .Values.ingress.host -}} +{{- end -}} + +{{- define "nanabushPlayer.publicOrigin" -}} +{{- printf "https://%s" (include "nanabushPlayer.host" .) -}} +{{- end -}} + +{{- 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 -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- printf "https://%s/lms/player" (include "nanabushPlayer.host" .) -}} {{- end -}} {{- end -}} + +{{- define "nanabushPlayer.serviceName" -}} +{{- .Values.app.nanabushPlayer.service.name | default (include "hrbox.component" (dict "ctx" . "name" "nanabush-player")) -}} {{- end -}} -{{- define "nats.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "nats.labels" -}} -app.kubernetes.io/name: nats -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 -}} +{{- define "nanabushPlayer.backchannelHost" -}} +{{- .Values.app.nanabushPlayer.deployment.backchannel.host | default (include "hrbox.component" (dict "ctx" . "name" "web")) -}} {{- end -}} -{{- end }} -{{- define "nats.extraRoutes" -}} -{{- range $i, $url := .Values.nats.cluster.extraRoutes -}} -{{- printf "%s," $url -}} +{{- define "nanabushPlayer.internalOrigin" -}} +{{- printf "http://%s" (include "nanabushPlayer.backchannelHost" .) -}} {{- 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 }} +{{- define "nanabushPlayer.version" -}} +{{- $tag := include "hrbox.imageTag" .Values.image.nanabushPlayer -}} +{{- if not $tag -}} +{{- fail "image.nanabushPlayer должен содержать явный тег версии, например nanabush-player:1.0.11" -}} +{{- end -}} +{{- $tag -}} +{{- end -}} +{{- define "nanabushPlayer.secretName" -}} +{{- required "app.nanabushPlayer.deployment.existingSecret.name обязателен, когда Player включён" .Values.app.nanabushPlayer.deployment.existingSecret.name -}} +{{- end -}} -{{/* -Renders a value that contains template. -Usage: -{{ include "tplvalues.render" ( dict "value" .Values.nats.path.to.the.Value "context" $) }} -*/}} -{{- define "tplvalues.render" -}} - {{- if typeIs "string" .value }} - {{- tpl .value .context }} - {{- else }} - {{- tpl (.value | toYaml) .context }} - {{- end }} +{{- define "nanabushPlayer.redisUrl" -}} +{{- printf "redis://%s:%s/%d" (include "hrbox.redisHost" .) (.Values.app.redis.port | toString) (.Values.app.nanabushPlayer.deployment.redis.database | int) -}} +{{- end -}} + +{{- define "nanabushPlayer.redisKeyPrefix" -}} +{{- printf "hrbox:%s:%s" .Release.Namespace .Values.app.nanabushPlayer.deployment.redis.keyPrefixSuffix -}} {{- end -}} diff --git a/.helm/templates/_resources.tpl b/.helm/templates/_resources.tpl index 2072e26..9d423b3 100644 --- a/.helm/templates/_resources.tpl +++ b/.helm/templates/_resources.tpl @@ -1,119 +1,30 @@ -{{- define "app_resources" }} +{{- define "hrbox.resources" -}} +{{- $r := index .ctx.Values.resources .component -}} +{{- if $r }} resources: +{{- with $r.requests }} requests: - 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 }} + {{- with .cpu }} + cpu: {{ . | quote }} + {{- end }} + {{- with .memory }} + memory: {{ . | quote }} + {{- end }} + {{- with .ephemeralStorage }} + ephemeral-storage: {{ . | quote }} + {{- end }} {{- end }} - -{{- define "nginx_resources" }} -resources: - requests: - memory: {{ .Values.resources.nginx.requests.memory | quote }} - cpu: {{ .Values.resources.nginx.requests.cpu | quote }} - ephemeral-storage: {{ .Values.resources.nginx.requests.ephemeralStorage | quote }} +{{- with $r.limits }} limits: - memory: {{ .Values.resources.nginx.limits.memory | quote }} - cpu: {{ .Values.resources.nginx.limits.cpu | quote }} - ephemeral-storage: {{ .Values.resources.nginx.limits.ephemeralStorage | quote }} + {{- with .cpu }} + cpu: {{ . | quote }} + {{- end }} + {{- with .memory }} + memory: {{ . | quote }} + {{- end }} + {{- with .ephemeralStorage }} + ephemeral-storage: {{ . | quote }} + {{- end }} {{- end }} - -{{- define "worker_resources" }} -resources: - requests: - memory: {{ .Values.resources.worker.requests.memory | quote }} - cpu: {{ .Values.resources.worker.requests.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 }} - -{{- define "websocket_resources" }} -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 }} - -{{- define "geonames_resources" }} -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 }} - -{{- 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 }} - -{{- 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: - memory: {{ .Values.resources.jobs.limits.memory | quote }} - cpu: {{ .Values.resources.jobs.limits.cpu | quote }} - ephemeral-storage: {{ .Values.resources.jobs.limits.ephemeralStorage | quote }} -{{- end }} - -{{- define "goworker_resources" }} -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 }} - -{{- define "kedoca_resources" }} -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 }} - -{{- 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 }} \ No newline at end of file +{{- end -}} diff --git a/.helm/values.yaml b/.helm/values.yaml index b84d7fa..ffe8cc5 100644 --- a/.helm/values.yaml +++ b/.helm/values.yaml @@ -1,464 +1,701 @@ --- -# Основные настройки приложения HRBox +# Конфигурация HRBox для установки в своём кластере. +# +# Заполните перед первой установкой: +# ingress.host домен, по которому будет открываться HRBox +# app.defaultTenantHostname тот же домен +# app.postgres.* подключение к PostgreSQL +# app.s3.* хранилище файлов +# app.smtp.* почта +# app.hub.token токен HRBox Hub, выдают сотрудники HRBox +# secrets.* три ключа, генерируются openssl +# app.chatbox.wrappingKey ключ шифрования чатов +# app.nanabushPlayer.keys.* два ключа плеера курсов +# +# Если что-то из этого не заполнено, helm остановится и напишет, чего не +# хватает. Команды генерации ключей — в README, раздел «Секреты». + +# Базовое имя объектов. Пусто — имя чарта: hrbox-web, hrbox-worker и т.д. +# На уже работающей установке менять нельзя. +nameOverride: "" + +# --- Образы --------------------------------------------------------------- +image: + # Приложение: PHP-FPM, nginx, worker, миграции. + # Доступ к реестру выдают сотрудники HRBox. + hrbox: "cr.yandex/crphnph9c2s776j32sjd/hrbox:latest" + + # Чат и вебсокеты + chatbox: "cr.yandex/crphnph9c2s776j32sjd/hrbox-chatbox:5.18" + # Мессенджеры, пуши, синхронизация оргструктуры + goworker: "cr.yandex/crphnph9c2s776j32sjd/hrbox-goworker:3.21.2" + # Электронная подпись: КЭДО, МЧД + kedoca: "cr.yandex/crphnph9c2s776j32sjd/hrbox-kedoca:1.0.6" + # Справочник городов, стран, часовых поясов + geonames: "cr.yandex/crphnph9c2s776j32sjd/hrbox-geonames:3.3" + # Конвертация документов и изображений + fileProcessor: "cr.yandex/crphnph9c2s776j32sjd/hrbox-file-processor:1.4.6" + # Планировщик задач по расписанию + conductor: "cr.yandex/crphnph9c2s776j32sjd/hrbox-conductor:2.2.0" + # Плеер курсов. Нужен конкретный тег, latest не подходит: + # из тега берётся версия плеера. + nanabushPlayer: "cr.yandex/crphnph9c2s776j32sjd/nanabush-player:1.0.11" + + # Инфраструктура + dragonfly: "cr.yandex/crpnn6fi85p3dauha6nc/dragonflydb:1.15.1" + nats: "nats:2.10" + natsConfigReloader: "natsio/nats-server-config-reloader:0.6.2" + phpFpmExporter: "cr.yandex/crpnn6fi85p3dauha6nc/php-fpm_exporter:latest" + # Только для джобы миграций: ждёт готовности базы + postgresClient: "postgres:14-alpine" + # Только при app.tracing.enabled + otelCollector: "otel/opentelemetry-collector:0.109.0" + + pullPolicy: IfNotPresent + +# Secret с доступом к реестру образов. Создаётся отдельно, см. README, шаг 1. +imagePullSecrets: + - name: regsecret + +# --- Приложение ----------------------------------------------------------- app: - # Docker образ приложения HRBox - # Получите доступ к реестру у сотрудников HRBox - image: "cr.yandex/crphnph9c2s776j32sjd/hrbox:latest" - - # Настройки домена - # defaultTenantHostname - основной домен вашей HRBox инсталляции - # Например: hrbox.company.com - # Должен совпадать с ingress.host + # Домен установки. Должен совпадать с ingress.host. defaultTenantHostname: "hrbox.example.com" - - # Уникальное имя инстанса для идентификации + + # Имя установки, видно в логах и метриках instanceName: "onprem" - # Включение/отключение frontend2. - # "true" - включено - # "false" или пустое значение - отключено + # "1" — установка обслуживает одну компанию + singleTenant: "0" + + # "true" — новый интерфейс, "false" — старый front2: "true" - - # Домен для авторизации - # Оставьте пустым + + # Отдельный домен авторизации. Для on-premise обычно пусто. authHost: "" - - # Режим отладки - # "0" - production режим (рекомендуется) - # "1" - debug режим (только для разработки) + + # "0" — production, "1" — отладка debug: "0" - - # Количество реплик для каждого сервиса - # Рекомендации по масштабированию: - # - web: 2-10 реплик в зависимости от количества пользователей - # - worker: 2-5 реплик для обработки фоновых задач - # - chatbox: 2-6 реплик для real-time функционала (WebSocket) - # - geonames: 1-2 реплики (сервис не требователен к ресурсам) + + # Подставлять https во всех ссылках, которые генерирует приложение + forceHttps: "true" + + # Число подов каждого сервиса. + # web — по числу пользователей, worker — по объёму фоновых задач, + # chatbox — по числу открытых вкладок, остальным обычно хватает 1-2. replicas: web: 2 worker: 2 chatbox: 2 geonames: 1 + goworker: 2 + kedoca: 1 + fileProcessor: 1 + fileProcessorVideo: 1 - # Показ собственной политики конфиденциальности и terms of use - # privacy_url и terms_url передаются в приложение только если enabled установлена в true - # если enabled = true, но privacy_url и terms_url закомментированы, - # то будут использоваться ссылки https://hrbox.io/policy и https://hrbox.io/terms - custom_policy: + # Свои ссылки на политику конфиденциальности и правила использования. + # false — показываются ссылки HRBox. + customPolicy: enabled: false - # privacy_url: "https://example.com/privacy" - # terms_url: "https://example.com/terms" - - # PostgreSQL база данных - # Требования: PostgreSQL 14+ - # Рекомендуется настроить репликацию для отказоустойчивости + # privacyUrl: "https://example.com/privacy" + # termsUrl: "https://example.com/terms" + + # --- PostgreSQL --------------------------------------------------------- + # Нужен PostgreSQL 14 или новее. Чарт базу не разворачивает. postgres: host: "postgresql" port: "5432" database: "hrbox" user: "hrbox" - # Пароль для подключения к БД + # Попадает в Secret, в спеке подов пароля нет password: "" - # Использование Citus для шардирования - # "true" - если используете Citus расширение - # "false" - для обычного PostgreSQL + # "true", если база на Citus (шардирование) citus: "false" - - # Redis для кеша и сессий - # Потеря данных не критична - хранятся только сессии и кеш - # При перезапуске Redis все пользователи будут разлогинены + + # --- Redis / Dragonfly -------------------------------------------------- + # Сессии, кеш и очереди плеера курсов. + # Пусто — используется Dragonfly из этого же релиза. + # Адрес указывают, если Redis внешний (тогда обычно dragonfly.enabled: false). redis: - host: "hrbox-dragonfly" + host: "" port: "6379" - - # NATS JetStream - брокер сообщений - # Для отказоустойчивости используйте кластер из 3+ нод - # Формат для кластера: nats://node1:4222,nats://node2:4222,nats://node3:4222 + + # --- NATS --------------------------------------------------------------- + # Пусто — используется NATS из этого же релиза. + # Внешний кластер: nats://node1:4222,nats://node2:4222,nats://node3:4222 nats: - url: "nats://hrbox-nats:4222" - - # S3-совместимое хранилище для файлов - # Поддерживаются: AWS S3, MinIO, Yandex Object Storage, VK Cloud Storage + url: "" + + # --- S3 ----------------------------------------------------------------- + # Подойдёт AWS S3, MinIO, Yandex Object Storage, VK Cloud Storage. s3: - # URL endpoint вашего S3 - # Примеры: - # - MinIO: http://minio:9000 - # - Yandex: https://storage.yandexcloud.net - # - VK Cloud: https://hb.vkcloud-storage.ru + # MinIO: http://minio:9000 + # Yandex: https://storage.yandexcloud.net + # VK: https://hb.vkcloud-storage.ru endpoint: "https://storage.example.com" - - # Название bucket для хранения файлов bucket: "hrbox" - - # Access Key для доступа к S3 key: "" - - # Secret Key для доступа к S3 secret: "" - - # Регион S3 (обычно us-east-1 для MinIO) region: "us-east-1" - - # Версия API (оставьте "latest") version: "latest" - - # Использовать path-style URLs - # "true" для MinIO и self-hosted S3 - # "false" для AWS S3 + # "true" для MinIO и своего S3, "false" для AWS S3 pathStyle: "true" - - # Включить multipart загрузку для больших файлов multipart: "true" - - # Префикс для всех объектов (опционально) prefix: "" - - # SMTP настройки для отправки email + + # --- Почта -------------------------------------------------------------- smtp: - # SMTP сервер host: "smtp.example.com" - - # Порт SMTP - # 25 - без шифрования - # 587 - STARTTLS - # 465 - SSL/TLS + # 25 — без шифрования, 587 — STARTTLS, 465 — SSL/TLS port: "587" - - # Логин для SMTP авторизации user: "noreply@example.com" - - # Пароль для SMTP password: "" - - # Email отправителя from: "noreply@example.com" - - # Домен отправителя для HELO/EHLO fromHost: "example.com" - - # Тип шифрования: "", "tls", "ssl" + # "", "tls" или "ssl" securityType: "tls" - - # Разрешить самоподписанные сертификаты sslAllowSelfSigned: "true" - - # Проверять SSL сертификат сервера sslVerifyPeer: "false" - - # Sentry для мониторинга ошибок + + # --- Sentry ------------------------------------------------------------- sentry: - # DSN для отправки ошибок dsn: "" - - # Окружение для группировки ошибок environment: "production" - - # Настройки сервиса геолокации + # Пусто — используется общий dsn + launchpadDsn: "" + conductorDsn: "" + # Здесь пусто означает «не отправлять» + goworkerDsn: "" + chatboxDsn: "" + + # --- Внутренние сервисы ------------------------------------------------- + # Пустой url — сервис берётся из этого же релиза. + # Заполняют, если сервис вынесен наружу. geonames: - # URL внутреннего сервиса geonames - # Не меняйте, если используете стандартный деплой - url: "http://hrbox-geonames:8181" + url: "" - # Goworker goworker: - # URL внутреннего сервиса goworker - url: "http://hrbox-goworker:8686" - # Docker образ goworker - image: "cr.yandex/crphnph9c2s776j32sjd/hrbox-goworker:2.7" - # Количество реплик - replicas: 2 - # Минимальный интервал между push + url: "" + # Не чаще одного push одному пользователю за этот интервал pushMinInterval: "5m" - # Sentry DSN для мониторинга ошибок - sentryDsn: "" - # Kedoca - сервис электронной подписи (ГОСТ, OpenSSL) kedoca: - # URL внутреннего сервиса kedoca - url: "http://hrbox-kedoca:8558" - # Docker образ kedoca - image: "cr.yandex/crphnph9c2s776j32sjd/hrbox-kedoca:0.8" - # Количество реплик - replicas: 1 - # Пароль для корневого сертификата + url: "" + # Пароль корневого сертификата, который сервис создаёт при первом запуске rootCaPass: "hrbox" - # Криптографический движок: "gost" или "openssl" + # "gost" (КриптоПро) или "openssl" engine: "gost" - # Алгоритм: "gost2012_256" для GОСТ или "rsa" для OpenSSL + # "gost2012_256" для ГОСТ, "rsa" для OpenSSL algorithm: "gost2012_256" - # Параметры ключа: "paramset:A" для ГОСТ или "rsa_keygen_bits:2048" для RSA + # "paramset:A" для ГОСТ, "rsa_keygen_bits:2048" для RSA pkeyopt: "paramset:A" - # Имя Kubernetes Secret с сертификатами - # Если указано, будет смонтирован в /ca-cert + # Служба меток времени + tspUrl: "http://qs.cryptopro.ru/tsp/tsp.srf" + # Secret с сертификатами УЦ. Если задан, монтируется в /ca-cert. secretName: "" - # File Processor - обработка файлов (конвертация, ресайз изображений) - file_processor: - # Docker образ file processor - image: "cr.yandex/crphnph9c2s776j32sjd/hrbox-file-processor:1.0" - # Количество реплик - replicas: 1 + fileProcessor: + # Сколько документов конвертировать одновременно в одном поде + docconvMaxParallelTasks: "1" + # Сколько изображений обрабатывать одновременно в поде конвертации видео + imageResizeMaxParallel: "20" - # Chatbox - WebSocket сервер для real-time функционала + # --- Конвертация видео -------------------------------------------------- + # Отдельные поды: видео требует много CPU и диска, поэтому его выносят + # на выделенные ноды. + videoConverter: + enabled: false + # Внутренний адрес HRBox, откуда конвертер берёт и куда отдаёт файлы. + # Пусто — http://<релиз>-web. + internalUrl: "" + # На каких нодах запускать. Пусто — на любых. + # Пример: role: converter-dedicated + nodeSelector: {} + # Пример: + # - key: dedicated + # operator: Equal + # value: video-converter + # effect: NoSchedule + tolerations: [] + + # --- Чат ---------------------------------------------------------------- chatbox: - # Docker образ chatbox - image: "cr.yandex/crphnph9c2s776j32sjd/hrbox-chatbox:3.0" - # Wrapping key - часть ключа для ассиметричного шифрования чатов - # Сгенерируйте произвольную строку из 32 символов - # openssl rand -hex 16 + # Часть ключа шифрования чатов, 32 символа. + # Генерация: openssl rand -hex 16 wrappingKey: "" - # Sentry DSN для мониторинга ошибок - sentryDsn: "" - - # Настройки воркеров Launchpad - # Формат: "название-топика:количество-воркеров" + + # --- Видеозвонки -------------------------------------------------------- + livekit: + url: "" + apiKey: "" + apiSecret: "" + + # --- Nanabush Player ---------------------------------------------------- + # Плеер учебных материалов: SCORM 1.2 и 2004, cmi5, xAPI, нативные курсы, + # тесты и эссе. + # + # В облаке плеер стоит на отдельном поддомене, в on-premise поддомена нет, + # поэтому он живёт на основном домене по пути basePath. Домен общий, значит + # cookie HRBox доступны содержимому курсов. Если нужна изоляция сторонних + # SCORM-пакетов, обратитесь в HRBox за схемой с отдельным поддоменом. + nanabushPlayer: + # false — материалы открываются старым проигрывателем + enabled: true + + # Путь на основном домене. Корень "/" занят самим HRBox. + basePath: "/nanabush-player" + + # Issuer launch-тикета. Пусто — https:///lms/player + issuer: "" + + contractVersion: "v0-current" + + # Оба ключа обязательны и должны быть разными. + # Генерация каждого: openssl rand -base64 48 + keys: + ticketHs256: "" + sessionHs256: "" + # Набор ключей для ротации без простоя, необязательный: + # "kid1:секрет1,kid2:секрет2". Первым идёт ключ, которым подписывают + # сейчас. Запятая внутри секрета недопустима, используйте base64url. + ticketKeys: "" + + rollout: + # Какая доля запусков идёт в новый плеер. 100 — все. + percent: 100 + # Открывать материал старым проигрывателем, если плеер не ответил + legacyFallback: true + allowedContentTypes: + - document + - presentation + - video + - audio + - scorm + - cmi5 + - xapi + - course + - native + - native_quiz + - native_essay + # "*" — все компании, включая новые + allowedTenants: + - "*" + + service: + # Пусто — <релиз>-nanabush-player + name: "" + port: 8094 + + # Публиковать ли путь плеера на общем Ingress. + # Выключают только при откате. + ingress: + enabled: true + + deployment: + # Создавать ли Deployment и Service плеера + enabled: true + replicas: 1 + terminationGracePeriodSeconds: 120 + + # Умеет ли образ плеера работать не в корне домена, а по пути. + # Со старым образом без такой поддержки поставьте false — чарт + # остановит установку вместо того, чтобы развернуть нерабочий плеер. + binaryCapabilities: + basePath: true + + # Secret с ключами плеера. + # create: false — Secret заводят вне чарта, чарт только ссылается. + existingSecret: + create: true + name: "hrbox-nanabush-player" + keys: + ticketHs256: player-hs256-secret + sessionHs256: player-session-hs256-secret + redisUrl: redis-url + ticketKeys: "" + + ticket: + # Сколько живёт launch-тикет, секунды + ttlSeconds: 300 + # Сколько живут учётные данные обратного канала: сессия плеера плюс + # 2 минуты на расхождение часов + credentialTtlSeconds: 28920 + maxLifetime: "5m" + sessionTtl: "8h" + # Нужно только при первом переходе на набор ключей: до какого момента + # (RFC3339) принимать тикеты без kid. Пусто — не принимать. + keylessAcceptedUntil: "" + + redis: + # false — адрес собирается из app.redis. + # true — полный redis:// или rediss:// URL берётся из внешнего Secret + # (нужно для пароля, ACL или TLS), требует existingSecret.create: false + urlFromExistingSecret: false + database: 0 + # Полный префикс: hrbox::<суффикс>. + # Разделяет несколько установок HRBox на одном Redis. + keyPrefixSuffix: "nanabush-player" + + # Обращения плеера обратно в HRBox: за пакетом курса и с отчётами + backchannel: + # Внутренний адрес HRBox. Пусто — <релиз>-web. + host: "" + schemes: "http" + allowPrivateIps: true + packageFetchMaxBytes: "5242880" + # Должно вмещать самый большой медиафайл курса + packageContentMaxBytes: "2147483648" + scormPackageMaxBytes: "1073741824" + # Пусто — берётся значение приложения (4000 файлов) + scormPackageMaxFiles: "" + packageFetchTimeout: "15s" + # Считается до конца передачи файла в браузер учащегося, поэтому + # зависит от скорости его интернета + packageStreamTimeout: "20m" + reportTimeout: "30s" + + # Очередь отправки учебных событий в HRBox + forwarder: + queueSize: "1000" + workers: "4" + maxAttempts: "5" + initialBackoff: "1s" + maxBackoff: "30s" + perAttemptTimeout: "30s" + deadLetterSize: "256" + claimIdle: "5m" + pollInterval: "1s" + rateLimitEventsPerMinute: "600" + rateLimitBatchesPerMinute: "120" + rateLimitPayloadBytesPerMinute: "8388608" + + log: + level: info + format: json + + # Метрики отдаются на отдельном порту и наружу не публикуются + metrics: + enabled: true + port: 9090 + + probes: + readiness: + path: /readyz + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 2 + failureThreshold: 6 + liveness: + path: /healthz + initialDelaySeconds: 20 + periodSeconds: 20 + timeoutSeconds: 2 + failureThreshold: 3 + + # --- Фоновые задачи ----------------------------------------------------- + # Формат: "очередь:сколько воркеров" launchpad: topics: "launcher:1,file-processor:1,image-processor:1,send-mail:1,send-im:1,send-bell:1,send-push:1,backup:1,cleaning:1" - - # Интеграция с Yandex (опционально) + + sms: + senderName: "HRBOX" + + # --- Внешние интеграции ------------------------------------------------- + integrations: + telegram: + # Если прямого доступа к Telegram нет, укажите адрес прокси + apiUrl: "https://api.telegram.org/bot" + google: + enabled: "false" + # clientId уходит в браузер, clientSecret попадает в Secret + clientId: "692956564858-ccapt35khk7tmadl17b3879ht2cropp6.apps.googleusercontent.com" + clientSecret: "" + glagol: + partnerId: "ab230ee1-6946-43b9-8kfa-447beb8348b0" + apiUrl: "https://api.glagol.online/glagol-api" + + # Привязка мобильных приложений к домену: без неё ссылки из писем и пушей + # открываются в браузере, а не в приложении. По умолчанию указаны + # приложения HRBox, меняйте только для своей сборки. + mobileApps: + android: + - packageName: "io.hrbox.hrboxapp" + sha256CertFingerprints: + - "DE:75:B2:CA:CB:50:96:02:8D:E1:07:4F:CE:E9:92:1C:4C:ED:87:B0:64:12:1A:69:27:7B:D2:B5:1A:23:31:70" + apple: + appIds: + - "DX67FD7GQJ.io.hrbox.app" + yandex: - # Client ID для Yandex OAuth clientId: "" - # Client Secret для Yandex OAuth clientPassword: "" - - # Интеграция со Slack (опционально) + slack: - # Slack App Client ID clientId: "" - # Slack App Client Secret clientSecret: "" - # Slack Verification Token verificationToken: "" - - # HRBox Hub + + # --- HRBox Hub ---------------------------------------------------------- + # Лицензирование и общие сервисы. Токен выдают сотрудники HRBox. hub: - # URL Hub сервиса url: "https://hub.hrbox.io" - # Токен для авторизации в Hub, получите от сотрудника HrBox token: "" - - # AI интеграции (опционально) + + # --- AI ----------------------------------------------------------------- ai: - # Yandex Cloud AI + docsIndexEnabled: "true" + semanticIndexEnabled: "true" + semanticIndexUseBatch: "true" yandex: - # ID каталога в Yandex Cloud folderId: "" - # API ключ для Yandex Cloud apiKey: "" - - # Настройки безопасности + + # --- Трассировка -------------------------------------------------------- + # Включает OpenTelemetry в PHP и коллектор рядом с подом web + tracing: + enabled: false + exporterUrl: "http://localhost:4318" + # Куда коллектор отправляет трейсы: Tempo, Jaeger + tempoUrl: "" + + # --- Безопасность ------------------------------------------------------- security: - # Разрешить полный доступ к iframe - # "true" - разрешает iframe полный доступ к родительскому окну - # "false" - ограничивает доступ iframe (рекомендуется для production) + # "true" даёт встроенному iframe полный доступ к родительскому окну. + # Для production лучше "false". iframeFullAccess: "false" -# Секретные ключи приложения +# --- Секреты -------------------------------------------------------------- +# Эти значения вместе с паролями из app.* складываются в один Kubernetes +# Secret, поды читают их оттуда. secrets: - # Ключ валидации cookie (32 символа) - # Используется для защиты от XSS атак + # Имя Secret, созданного вне чарта. Если задано, чарт свой Secret не + # создаёт и берёт ключи из указанного. Список ключей — в README. + existingSecret: "" + + # Защита cookie от подделки, 32 символа. # Генерация: openssl rand -hex 16 cookieValidationKey: "" - # Ключ шифрования данных (64 символа) - # ВАЖНО: Не теряйте этот ключ! Без него невозможно расшифровать данные - # Используется для шифрования паролей интеграций (S3, ADFS и др.) + # Шифрование паролей интеграций в базе. + # Без этого ключа данные не расшифровать, храните копию отдельно. # Генерация: openssl rand -base64 48 encryptionKey: "" - # Секрет для внутренней коммуникации микросервисов (64 символа) - # Используется для защиты внутренних API вызовов между сервисами + # Проверка вызовов между сервисами внутри релиза. # Генерация: openssl rand -base64 48 servicesInternalSecret: "" -# Настройки Ingress +# --- Ingress -------------------------------------------------------------- ingress: - # Включить Ingress enabled: true - - # Класс Ingress контроллера - # Обычно: "nginx" или "traefik" + + # Обычно "nginx" или "traefik" className: "nginx" - - # Хост для доступа к приложению - # Используйте wildcard для мультитенантности: *.hrbox.company.com - # Или конкретный домен: hrbox.company.com + + # Домен установки. Для мультиарендности можно wildcard: *.hrbox.company.com host: "hrbox.example.com" - - # Настройки TLS/HTTPS + + # Дополнительные аннотации ingress-контроллера + annotations: {} + + # Максимальный размер загружаемого файла. + # У загрузки оргструктуры из 1С отдельный лимит, он выше. + maxBodySize: "10m" + tls: - # Включить HTTPS - # ВАЖНО: В production всегда используйте HTTPS enabled: true - - # Имя Kubernetes Secret с SSL сертификатом - # Создайте через: kubectl create secret tls hrbox-tls --cert=cert.pem --key=key.pem - # Или используйте cert-manager для автоматического получения сертификатов + # kubectl create secret tls hrbox-tls --cert=cert.pem --key=key.pem + # или выпустить сертификат через cert-manager secretName: "hrbox-tls" +# --- Кластер -------------------------------------------------------------- cluster: - domain: "cluster.local" # Укажите домен текущего k8s-кластера, если используется нестандартный + # Домен служебных имён Kubernetes. Менять только если он нестандартный. + domain: "cluster.local" -# Настройки ресурсов для контейнеров -# Указаны рекомендуемые значения для production +# --- Ресурсы -------------------------------------------------------------- +# Значения близки к тем, что используются в облаке HRBox. +# Можно оставить только requests: незаполненные поля в манифест не попадают. resources: - # Web-сервер (PHP-FPM + Nginx) - # Обрабатывает HTTP запросы, API, веб-интерфейс + # PHP-FPM: веб-интерфейс и API app: requests: - cpu: "0.5" # Минимум 0.5 CPU - memory: "512Mi" # Минимум 512MB RAM + cpu: "1" + memory: "2Gi" ephemeralStorage: "64Mi" - limits: - cpu: "2" # Максимум 2 CPU - memory: "2Gi" # Максимум 2GB RAM - ephemeralStorage: "256Mi" - - # Nginx (в составе web pod) - # Проксирует запросы к PHP-FPM - nginx: - requests: - cpu: "0.1" - memory: "64Mi" - ephemeralStorage: "64Mi" - limits: - cpu: "0.5" - memory: "256Mi" - ephemeralStorage: "64Mi" - - # Worker - обработчик фоновых задач - # Выполняет: конвертацию файлов, отправку email, генерацию отчетов - worker: - requests: - cpu: "0.5" - memory: "512Mi" - ephemeralStorage: "1Gi" # Больше места для обработки файлов limits: cpu: "2" memory: "2Gi" ephemeralStorage: "2Gi" - # Geonames - сервис геолокации - # Определяет города, страны, часовые пояса - geonames: + # nginx в поде web + nginx: requests: cpu: "0.1" - memory: "196Mi" + memory: "256Mi" ephemeralStorage: "64Mi" limits: cpu: "0.5" memory: "256Mi" - ephemeralStorage: "64Mi" - - # Conductor - планировщик задач - # ВАЖНО: Всегда должен быть в единственном экземпляре! - # Запускает задачи по расписанию (cron) - conductor: + ephemeralStorage: "512Mi" + + # Фоновые задачи: файлы, почта, отчёты + worker: requests: - cpu: "0.1" - memory: "64Mi" - ephemeralStorage: "64Mi" - limits: - cpu: "0.5" - memory: "256Mi" - ephemeralStorage: "64Mi" - - # Jobs - одноразовые задачи (миграции, инициализация) - jobs: - requests: - cpu: "0.5" - memory: "512Mi" - ephemeralStorage: "64Mi" + cpu: "0.6" + memory: "640Mi" + ephemeralStorage: "4Gi" limits: cpu: "2" - memory: "2Gi" - ephemeralStorage: "256Mi" + memory: "4Gi" + ephemeralStorage: "8Gi" - # Goworker - интеграции с мессенджерами - goworker: + # Планировщик задач по расписанию + conductor: requests: - cpu: "0.1" - memory: "196Mi" + cpu: "0.25" + memory: "128Mi" ephemeralStorage: "64Mi" limits: - cpu: "0.3" - memory: "256Mi" + cpu: "0.7" + memory: "384Mi" ephemeralStorage: "64Mi" - # Kedoca - электронная подпись - kedoca: - requests: - cpu: "0.1" - memory: "196Mi" - ephemeralStorage: "64Mi" - limits: - cpu: "0.5" - memory: "256Mi" - ephemeralStorage: "64Mi" - - # Chatbox - WebSocket сервер chatbox: requests: cpu: "0.3" memory: "96Mi" ephemeralStorage: "128Mi" limits: - cpu: "0.3" - memory: "192Mi" + cpu: "1" + memory: "256Mi" ephemeralStorage: "256Mi" -# Настройки NATS JetStream + geonames: + requests: + cpu: "0.1" + memory: "196Mi" + ephemeralStorage: "64Mi" + limits: + cpu: "0.5" + memory: "256Mi" + ephemeralStorage: "64Mi" + + goworker: + requests: + cpu: "0.1" + memory: "196Mi" + ephemeralStorage: "64Mi" + limits: + cpu: "0.5" + memory: "256Mi" + ephemeralStorage: "64Mi" + + kedoca: + requests: + cpu: "0.1" + memory: "196Mi" + ephemeralStorage: "64Mi" + limits: + cpu: "1" + memory: "256Mi" + ephemeralStorage: "64Mi" + + fileProcessor: + requests: + cpu: "0.5" + memory: "512Mi" + ephemeralStorage: "2Gi" + limits: + cpu: "1" + memory: "1Gi" + ephemeralStorage: "2Gi" + + # Конвертация видео: тяжёлые поды на отдельных нодах + fileProcessorVideo: + requests: + cpu: "4" + memory: "4Gi" + ephemeralStorage: "40Gi" + limits: + cpu: "8" + memory: "8Gi" + ephemeralStorage: "44Gi" + + nanabushPlayer: + requests: + cpu: "0.1" + memory: "192Mi" + limits: + memory: "512Mi" + + dragonfly: + requests: + cpu: "0.1" + memory: "256Mi" + limits: + cpu: "1" + memory: "1Gi" + + nats: + requests: + cpu: "0.1" + memory: "256Mi" + limits: + cpu: "1" + memory: "2Gi" + + # Разовые задачи: миграции и обновление кластера + jobs: + requests: + cpu: "0.5" + memory: "1Gi" + ephemeralStorage: "64Mi" + limits: + cpu: "2" + memory: "4Gi" + ephemeralStorage: "256Mi" + +# --- NATS ----------------------------------------------------------------- nats: - # Включить NATS (обязательно для работы HRBox) + # HRBox без брокера не работает. Выключают, только если NATS ставят + # отдельно и указали его адрес в app.nats.url. enabled: true - - # Docker образ NATS - image: "nats:2.10" - - # Количество реплик NATS - # Для отказоустойчивости используйте 3 или 5 + + # Для отказоустойчивости 3 или 5. Должно совпадать с cluster.replicas. replicas: 3 - - # JetStream - персистентные очереди сообщений + jetstream: enabled: true - - # Хранилище в памяти (для небольших инсталляций) + + # Хранение в памяти: подходит небольшим установкам memStorage: enabled: true size: "2Gi" - - # Хранилище на диске (для production) + + # Хранение на диске: для production fileStorage: enabled: false size: "4Gi" storageDirectory: "/data" - # StorageClass для PVC - # Используйте SSD для лучшей производительности + # JetStream нужен быстрый диск storageClassName: "fast-ssd" - - # Настройки кластера NATS + cluster: - # Включить кластеризацию для отказоустойчивости enabled: true - # Должно совпадать с replicas + # Должно совпадать с nats.replicas replicas: 3 - - # Prometheus exporter (для мониторинга) - exporter: - enabled: false - - # Config reloader - автоматическая перезагрузка конфигурации + + # Подхватывает изменения конфигурации без перезапуска NATS reloader: enabled: true - - # Время ожидания graceful shutdown в секундах + terminationGracePeriodSeconds: 120 +# --- Dragonfly ------------------------------------------------------------ +# Redis-совместимый кеш. dragonfly: - # Включает или отключает развёртывание Dragonfly (Redis). - # Установите в true, чтобы активировать, или в false, чтобы отключить. + # false — если Redis ставят отдельно; тогда заполните app.redis.host enabled: true - # По умолчанию параметр не используется. По необходимости, раскомментируйте и установите требуемое значение maxmemory - # maxmemory: "256M" \ No newline at end of file + # Ограничение памяти + # maxmemory: "256M" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a44f44..5755dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,267 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.0.0] - 2026-09-02 + +Чарт подтянут к тому, как HRBox работает в облаке: добавлен плеер учебных +материалов, пароли и ключи уехали в Secret, повторяющиеся куски шаблонов +заменены одним хелпером, а неполная конфигурация теперь останавливает +установку с понятным сообщением. + +### 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`, ресурсы `resources.nanabushPlayer` +- переменные `NANABUSH_PLAYER_*` в HRBox и `PLAYER_*` в самом плеере + +В on-premise выделенного поддомена нет, поэтому плеер стоит на основном +домене по пути. Отсюда два ограничения, они описаны в README: cookie HRBox +доступны содержимому курсов, и нужны синхронизированные часы — тикет живёт +5 минут. + +Для быстрого отката оставлены три переключателя: `enabled`, +`ingress.enabled`, `deployment.enabled`. Маршрут без пода или включённый +плеер без маршрута чарт не пропустит. + +#### Проверка конфигурации + +- `00-validate.yaml` — объектов не создаёт, но останавливает установку, если + не заполнены обязательные значения, остались ключи из версии 2.x или + нарушены требования плеера. Сообщения на русском, с указанием, что править. + +#### Secret приложения + +- `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` и коллектор OpenTelemetry рядом с `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 или Secret приложения +- образы, порты и таймауты вынесены из шаблонов в 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` было + значение по умолчанию, зашитое в чарт: все, кто его не заполнил, работали + на одном и том же секрете межсервисной аутентификации. Теперь значение + обязательное. +- **`cluster.domain` не использовался.** Маршруты кластера NATS были зашиты + на `cluster.local`, хотя параметр в 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 Масштабная актуализация чарта на основе продового окружения hrbox. Синхронизированы версии компонентов, добавлены новые микросервисы, обновлены переменные окружения. @@ -227,5 +488,6 @@ kubectl get pods -n hrbox -l app=websocket # Должно быть пусто --- +[3.0.0]: #300---2026-09-02 [2.0.0]: #200---2026-01-30 [1.2.0]: #120---previous-version diff --git a/README.md b/README.md index fdaf1f0..d8c11ef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,47 @@ # HRBox Helm Chart -Helm-чарт для развертывания приложения HRBox в Kubernetes. +Чарт для установки HRBox в свой Kubernetes-кластер. + +Сам чарт лежит в [`.helm`](.helm), настройки — в [`.helm/values.yaml`](.helm/values.yaml). +Каждый параметр там подписан, ниже — только то, что нужно сделать руками. --- -## Быстрый старт +## Что разворачивается -### 1. Создайте секрет для доступа к Docker-реестру +| Компонент | Что делает | Порт | +|---|---|---| +| `web` | PHP-FPM и nginx: веб-интерфейс и API | 80 | +| `worker` | фоновые задачи: файлы, почта, отчёты | — | +| `conductor` | задачи по расписанию, всегда 1 под | 8080 | +| `chatbox` | вебсокеты чата и уведомлений | 80 → 9797 | +| `nanabush-player` | плеер курсов: SCORM, cmi5, xAPI, нативные материалы | 8094 | +| `goworker` | мессенджеры, пуши, синхронизация оргструктуры с 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-сервер; +- доступ к реестру образов HRBox и токен HRBox Hub — выдают сотрудники HRBox. + +Базу и S3 чарт не разворачивает: эти хранилища содержат постоянные данные и должны быть развёрнуты вручную. + +--- + +## Установка + +### 1. Доступ к реестру образов ```bash kubectl create secret docker-registry regsecret \ @@ -18,57 +51,285 @@ kubectl create secret docker-registry regsecret \ --docker-email=not@used.com ``` -### 2. Создайте TLS-секрет с сертификатом для домена +### 2. TLS-сертификат ```bash kubectl create secret tls hrbox-tls --cert=cert.pem --key=key.pem ``` -### 3. Сгенерируйте секретные ключи приложения +Либо выпустите сертификат через cert-manager и укажите его Secret +в `ingress.tls.secretName`. -Для обеспечения безопасности необходимо сгенерировать и указать в `values.yaml` следующие ключи: - -- **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-релиз +### 3. Ключи ```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 -kubectl get pods - -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 +helm template hrbox .helm -f values.yaml > /dev/null ``` + +Если чего-то не хватает, команда остановится и напишет, что заполнить. + +### 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`, +поды читают их оттуда. В спеке Deployment паролей нет. + +Если секретами управляет внешний инструмент, создайте Secret сами и укажите +имя в `secrets.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 на одном домене, поэтому 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%`), +чтобы вывод ноды из обслуживания не уронил приложение целиком. + +### Конвертация видео + +Выключена по умолчанию, потому что требует заметно больше ресурсов, чем +остальная обработка файлов. Включение: + +```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).