Ga naar hoofdinhoud

Ister installeren met Helm

Dit hoofdstuk legt uit hoe de ister Helm chart werkt en hoe je hem installeert. De chart zet de volledige ister-mediaserver neer: de API-server, de webplayer, en — tenzij je je eigen diensten meebrengt — PostgreSQL, RabbitMQ en Typesense.

Wat de chart installeert

ComponentWat het isStandaard meegeleverd?
serverDe Spring Boot-backend (ghcr.io/ister-app/server)altijd
websiteDe webplayer (ghcr.io/ister-app/player)altijd
PostgreSQLDe databaseja (database.mode: internal)
RabbitMQDe message broker (Bitnami-subchart)ja (rabbitmq.enabled: true)
TypesenseDe zoekmachineja (typesense.enabled: true)
FlywayDatabasemigraties (ghcr.io/ister-app/migrations)draait als init-container

De standaardwaarden leveren een op zichzelf staande ister zonder ingress en zonder mediavolumes — genoeg om het te proberen, geen productie-opstelling.

Vereisten

  • Een Kubernetes-cluster en Helm 3.
  • Een TMDB API-key voor film-/seriemetadata.
  • Een OIDC-issuer (bijvoorbeeld Keycloak) — ister beheert zelf geen gebruikers.
  • Voor database.mode: cnpg: de CloudNativePG-operator.
  • Voor ingress met TLS: een ingress-controller en optioneel cert-manager.

Installeren

De chart wordt gepubliceerd als OCI-artifact:

helm install ister oci://ghcr.io/ister-app/charts/ister \
--namespace ister --create-namespace \
--set server.tmdbApiKey=<jouw-key> \
--set server.oidc.url=https://keycloak.example.com/realms/auth

Voor alles voorbij een eerste test: begin met values-production.example.yaml uit de chart-repository en installeer met -f. Pin een chartversie met --version; bij elke chartversie hoort een GitHub Release waarvan de release notes de exact meegeleverde image-versies vermelden.

Gebundelde of externe datastores

Alles waar de server op leunt kan óf door de chart worden uitgerold, óf naar een bestaande dienst wijzen. Wat je ook kiest, de onderliggende templates merken het verschil nooit — dat is de centrale ontwerpregel van de chart.

PostgreSQL — database.mode

  • internal — één postgres-Deployment + PVC. Alleen voor ontwikkeling: één instantie, geen back-ups.
  • cnpg — een CloudNativePG-Cluster (standaard 3 instanties, anti-affinity, optionele Barman Cloud-back-ups naar S3 onder database.cnpg.backup). Vereist de CNPG-operator.
  • external — een bestaande PostgreSQL, geconfigureerd onder database.external.

Alle drie de modi komen uit op één Secret met de sleutels host, port, dbname, user, password — de vorm die CNPG genereert voor zijn <cluster>-app-Secret; de andere twee modi schrijven diezelfde vorm zelf. Breng je een eigen Secret mee (existingSecret), dan moet die exact deze sleutels hebben; het <cluster>-app-Secret van een buiten deze chart beheerd CNPG-cluster voldoet al.

RabbitMQ — rabbitmq.enabled

true installeert de Bitnami RabbitMQ-subchart (alles onder rabbitmq: gaat één-op-één naar die chart). false gebruikt het blok externalRabbitmq — diens existingSecret heeft de sleutel rabbitmq-password nodig.

Typesense — typesense.enabled

true installeert Typesense met een eigen PVC. false gebruikt typesense.external — diens existingSecret heeft de sleutel api-key nodig.

Secrets en wachtwoorden

  • Niets geheims hoort in een values-bestand. Elke credential heeft een existingSecret-ontsnappingsluik; gebruik dat (of een secret manager) overal waar het ertoe doet.
  • Gegenereerde wachtwoorden overleven upgrades. Laat je de chart een wachtwoord genereren, dan zoekt hij bij een upgrade het live Secret op en behoudt de bestaande waarde — anders zou elke helm upgrade de server buiten zijn eigen database sluiten. Daarom verschilt de uitvoer van helm template ook van wat helm install daadwerkelijk toepast.
  • Een Secret roteren herstart de server. Alle credentials worden gehasht in een checksum/secrets-podannotatie, dus een gewijzigd Secret rolt de Deployment.

Databasemigraties

Flyway draait als init-container op de server-pod (voorafgegaan door een wait-for-db-container), zodat de server nooit tegen een ongemigreerd schema kan starten. Het is bewust geen Helm-hook: met een gebundelde database zou een pre-install-hook wachten op een database die Helm nog niet heeft aangemaakt. De migrations-image publiceert dezelfde versie als de server, dus flyway.image.tag blijft leeg en volgt de serverversie automatisch.

Mediabibliotheken en -volumes

Twee value-lijsten verbinden de server met je media:

  • server.libraries — de bibliotheken die ister scant; elk met een name en een type (SHOW, MOVIE, MUSIC, BOOK, PODCAST, COMIC).
  • server.mediaVolumes — de volumes die in de server-pod worden gemount, elk gedragen door precies één van hostPath, existingClaim of nfs, en via library gekoppeld aan een bibliotheek (laat dat weg voor alleen een mount). Geen slash aan het eind van mountPath — de scanner slaat de boom dan over.

Twee andere opslagknoppen doen ertoe:

  • cache — een PVC voor de cachemap van de server (transcodes, afbeeldingen). Standaard ReadWriteOnce, en daarom moet server.replicaCount op 1 blijven — de chart weigert meer replica's te renderen tenzij je overschakelt op ReadWriteMany.
  • server.tmp — kladruimte voor transcoderen. Zet dit uit wanneer mountPath binnen een van je mediavolumes valt, anders overschaduwt het aparte volume dat pad.

Ingress

ingress.enabled: true publiceert de player op / en de API op server.contextPath (standaard /api) op ingress.host, met TLS via cert-manager wanneer ingress.tls.certIssuer is gezet. ingress.wellKnown.enabled serveert daarnaast /.well-known/ister voor client-discovery — geïmplementeerd als een ingress-nginx-server-snippet, dat moderne ingress-nginx standaard uitschakelt (allow-snippet-annotations); zet dat eerst aan op de controller.

Beheer

  • Upgrades: helm upgrade ister oci://ghcr.io/ister-app/charts/ister --version <v> met je values-bestand. Gegenereerde wachtwoorden blijven behouden (zie hierboven).
  • Rooktest: helm test ister -n ister --logs draait de meegeleverde connectiviteitstest.
  • Monitoring: monitoring.enabled rendert een Prometheus Operator-ServiceMonitor voor de actuator van de server; in cnpg-modus dekt database.cnpg.podMonitor de database.
  • Verwijderen: de PVC's van database, cache en Typesense blijven standaard staan (retain: true), dus een uninstall is niet destructief; zet retain: false waar je ze met de release opgeruimd wilt hebben.

Values-referentie

Elke value die de chart accepteert, met zijn standaardwaarde. Deze tabel wordt gegenereerd uit de values.yaml van de release waarmee deze documentatie is meegeleverd; values.schema.json dwingt hetzelfde contract af bij elke render.

KeyDefault
nameOverride""
fullnameOverride""
imagePullSecrets[]
serviceAccount.createtrue
serviceAccount.name""
serviceAccount.annotations{}
server.name"Ister"
server.image.repository"ghcr.io/ister-app/server"
server.image.tag"2.0.1"
server.image.digest""
server.image.pullPolicy"IfNotPresent"
server.replicaCount1
server.contextPath"/api"
server.url""
server.oidc.url""
server.tmdbApiKey""
server.existingSecret""
server.managementPort8081
server.enableServiceLinksfalse
server.extraEnv[]
server.podAnnotations{}
server.nodeSelector{}
server.tolerations[]
server.affinity{}
server.resources.requests.cpu"500m"
server.resources.requests.memory"1Gi"
server.resources.limits.memory"4Gi"
server.podSecurityContext.runAsNonRoottrue
server.podSecurityContext.runAsUser1000
server.podSecurityContext.fsGroup1000
server.podSecurityContext.seccompProfile.type"RuntimeDefault"
server.securityContext.allowPrivilegeEscalationfalse
server.securityContext.capabilities.drop["ALL"]
server.probes.enabledtrue
server.probes.path"/actuator/health"
server.probes.startup.failureThreshold30
server.probes.startup.periodSeconds10
server.probes.liveness.periodSeconds20
server.probes.liveness.failureThreshold3
server.probes.readiness.periodSeconds10
server.probes.readiness.failureThreshold3
server.tmp.mountPath"/tmp/ister"
server.tmp.enabledtrue
server.tmp.sizeLimit"10Gi"
server.tmp.existingClaim""
server.libraries[]
server.mediaVolumes[]
website.image.repository"ghcr.io/ister-app/player"
website.image.tag"1.0.0"
website.image.digest""
website.image.pullPolicy"IfNotPresent"
website.replicaCount1
website.port8080
website.podAnnotations{}
website.nodeSelector{}
website.tolerations[]
website.affinity{}
website.resources.requests.cpu"10m"
website.resources.requests.memory"32Mi"
website.resources.limits.memory"128Mi"
website.podSecurityContext.runAsNonRoottrue
website.podSecurityContext.seccompProfile.type"RuntimeDefault"
website.securityContext.allowPrivilegeEscalationfalse
website.securityContext.capabilities.drop["ALL"]
website.probes.enabledtrue
website.probes.path"/"
cache.enabledtrue
cache.existingClaim""
cache.mountPath"/cache"
cache.storageClass""
cache.size"20Gi"
cache.accessMode"ReadWriteOnce"
cache.retaintrue
ingress.enabledfalse
ingress.className"nginx"
ingress.host""
ingress.annotations{}
ingress.tls.enabledtrue
ingress.tls.secretName""
ingress.tls.certIssuer""
ingress.wellKnown.enabledfalse
database.mode"internal"
database.internal.image.repository"postgres"
database.internal.image.tag"18"
database.internal.image.pullPolicy"IfNotPresent"
database.internal.dbName"ister"
database.internal.user"ister"
database.internal.password""
database.internal.existingSecret""
database.internal.storageClass""
database.internal.storageSize"8Gi"
database.internal.retaintrue
database.internal.podSecurityContext.runAsNonRoottrue
database.internal.podSecurityContext.runAsUser999
database.internal.podSecurityContext.runAsGroup999
database.internal.podSecurityContext.fsGroup999
database.internal.podSecurityContext.seccompProfile.type"RuntimeDefault"
database.internal.resources.requests.cpu"100m"
database.internal.resources.requests.memory"256Mi"
database.internal.resources.limits.memory"1Gi"
database.cnpg.instances3
database.cnpg.imageName""
database.cnpg.dbName"ister"
database.cnpg.user"ister"
database.cnpg.storageClass""
database.cnpg.storageSize"12Gi"
database.cnpg.enablePodAntiAffinitytrue
database.cnpg.podMonitor.enabledtrue
database.cnpg.podMonitor.interval"30s"
database.cnpg.podMonitor.labels{}
database.cnpg.parameters.pgaudit.log"all, -misc"
database.cnpg.parameters.pgaudit.log_parameter"on"
database.cnpg.parameters.pgaudit.log_relation"on"
database.cnpg.resources{}
database.cnpg.backup.enabledfalse
database.cnpg.backup.destinationPath""
database.cnpg.backup.endpointURL""
database.cnpg.backup.region""
database.cnpg.backup.walCompression"gzip"
database.cnpg.backup.existingSecret""
database.cnpg.backup.accessKeyId""
database.cnpg.backup.secretAccessKey""
database.cnpg.backup.schedule.enabledtrue
database.cnpg.backup.schedule.cron"0 0 3 * * *"
database.cnpg.backup.schedule.backupOwnerReference"self"
database.external.host""
database.external.port5432
database.external.dbName"ister"
database.external.user"ister"
database.external.password""
database.external.existingSecret""
rabbitmq.enabledtrue
rabbitmq.image.repository"bitnamilegacy/rabbitmq"
rabbitmq.global.security.allowInsecureImagestrue
rabbitmq.ingress.enabledfalse
rabbitmq.ingress.tlsfalse
rabbitmq.auth.username"user"
rabbitmq.auth.password""
rabbitmq.auth.existingPasswordSecret""
rabbitmq.auth.existingErlangSecret""
rabbitmq.persistence.enabledtrue
rabbitmq.persistence.size"8Gi"
rabbitmq.resources.requests.cpu"100m"
rabbitmq.resources.requests.memory"256Mi"
rabbitmq.resources.limits.memory"1Gi"
externalRabbitmq.host""
externalRabbitmq.port5672
externalRabbitmq.user"user"
externalRabbitmq.password""
externalRabbitmq.existingSecret""
typesense.enabledtrue
typesense.image.repository"docker.io/typesense/typesense"
typesense.image.tag"30.2"
typesense.image.pullPolicy"IfNotPresent"
typesense.port8108
typesense.collection"media"
typesense.apiKey""
typesense.existingSecret""
typesense.persistence.enabledtrue
typesense.persistence.existingClaim""
typesense.persistence.storageClass""
typesense.persistence.size"8Gi"
typesense.persistence.retaintrue
typesense.podSecurityContext.runAsNonRoottrue
typesense.podSecurityContext.runAsUser1000
typesense.podSecurityContext.fsGroup1000
typesense.podSecurityContext.seccompProfile.type"RuntimeDefault"
typesense.securityContext.allowPrivilegeEscalationfalse
typesense.securityContext.capabilities.drop["ALL"]
typesense.nodeSelector{}
typesense.tolerations[]
typesense.resources.requests.cpu"100m"
typesense.resources.requests.memory"256Mi"
typesense.resources.limits.memory"1Gi"
typesense.external.host""
typesense.external.protocol"http"
typesense.external.existingSecret""
flyway.enabledtrue
flyway.image.repository"ghcr.io/ister-app/migrations"
flyway.image.tag""
flyway.image.digest""
flyway.image.pullPolicy"IfNotPresent"
flyway.waitImage.repository"postgres"
flyway.waitImage.tag"18-alpine"
flyway.waitImage.pullPolicy"IfNotPresent"
flyway.resources.requests.cpu"100m"
flyway.resources.requests.memory"256Mi"
flyway.resources.limits.memory"512Mi"
monitoring.enabledfalse
monitoring.serviceMonitorLabels{}
monitoring.interval"30s"
podDisruptionBudget.enabledfalse
podDisruptionBudget.minAvailable1
tests.image.repository"curlimages/curl"
tests.image.tag"8.21.0"
tests.image.pullPolicy"IfNotPresent"