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
| Component | Wat het is | Standaard meegeleverd? |
|---|---|---|
| server | De Spring Boot-backend (ghcr.io/ister-app/server) | altijd |
| website | De webplayer (ghcr.io/ister-app/player) | altijd |
| PostgreSQL | De database | ja (database.mode: internal) |
| RabbitMQ | De message broker (Bitnami-subchart) | ja (rabbitmq.enabled: true) |
| Typesense | De zoekmachine | ja (typesense.enabled: true) |
| Flyway | Databasemigraties (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 onderdatabase.cnpg.backup). Vereist de CNPG-operator.external— een bestaande PostgreSQL, geconfigureerd onderdatabase.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 upgradede server buiten zijn eigen database sluiten. Daarom verschilt de uitvoer vanhelm templateook van wathelm installdaadwerkelijk 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 eennameen eentype(SHOW,MOVIE,MUSIC,BOOK,PODCAST,COMIC).server.mediaVolumes— de volumes die in de server-pod worden gemount, elk gedragen door precies één vanhostPath,existingClaimofnfs, en vialibrarygekoppeld aan een bibliotheek (laat dat weg voor alleen een mount). Geen slash aan het eind vanmountPath— de scanner slaat de boom dan over.
Twee andere opslagknoppen doen ertoe:
cache— een PVC voor de cachemap van de server (transcodes, afbeeldingen). StandaardReadWriteOnce, en daarom moetserver.replicaCountop 1 blijven — de chart weigert meer replica's te renderen tenzij je overschakelt opReadWriteMany.server.tmp— kladruimte voor transcoderen. Zet dit uit wanneermountPathbinnen 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 --logsdraait de meegeleverde connectiviteitstest. - Monitoring:
monitoring.enabledrendert een Prometheus Operator-ServiceMonitor voor de actuator van de server; incnpg-modus dektdatabase.cnpg.podMonitorde database. - Verwijderen: de PVC's van database, cache en Typesense blijven standaard staan
(
retain: true), dus een uninstall is niet destructief; zetretain: falsewaar 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.
| Key | Default |
|---|---|
nameOverride | "" |
fullnameOverride | "" |
imagePullSecrets | [] |
serviceAccount.create | true |
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.replicaCount | 1 |
server.contextPath | "/api" |
server.url | "" |
server.oidc.url | "" |
server.tmdbApiKey | "" |
server.existingSecret | "" |
server.managementPort | 8081 |
server.enableServiceLinks | false |
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.runAsNonRoot | true |
server.podSecurityContext.runAsUser | 1000 |
server.podSecurityContext.fsGroup | 1000 |
server.podSecurityContext.seccompProfile.type | "RuntimeDefault" |
server.securityContext.allowPrivilegeEscalation | false |
server.securityContext.capabilities.drop | ["ALL"] |
server.probes.enabled | true |
server.probes.path | "/actuator/health" |
server.probes.startup.failureThreshold | 30 |
server.probes.startup.periodSeconds | 10 |
server.probes.liveness.periodSeconds | 20 |
server.probes.liveness.failureThreshold | 3 |
server.probes.readiness.periodSeconds | 10 |
server.probes.readiness.failureThreshold | 3 |
server.tmp.mountPath | "/tmp/ister" |
server.tmp.enabled | true |
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.replicaCount | 1 |
website.port | 8080 |
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.runAsNonRoot | true |
website.podSecurityContext.seccompProfile.type | "RuntimeDefault" |
website.securityContext.allowPrivilegeEscalation | false |
website.securityContext.capabilities.drop | ["ALL"] |
website.probes.enabled | true |
website.probes.path | "/" |
cache.enabled | true |
cache.existingClaim | "" |
cache.mountPath | "/cache" |
cache.storageClass | "" |
cache.size | "20Gi" |
cache.accessMode | "ReadWriteOnce" |
cache.retain | true |
ingress.enabled | false |
ingress.className | "nginx" |
ingress.host | "" |
ingress.annotations | {} |
ingress.tls.enabled | true |
ingress.tls.secretName | "" |
ingress.tls.certIssuer | "" |
ingress.wellKnown.enabled | false |
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.retain | true |
database.internal.podSecurityContext.runAsNonRoot | true |
database.internal.podSecurityContext.runAsUser | 999 |
database.internal.podSecurityContext.runAsGroup | 999 |
database.internal.podSecurityContext.fsGroup | 999 |
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.instances | 3 |
database.cnpg.imageName | "" |
database.cnpg.dbName | "ister" |
database.cnpg.user | "ister" |
database.cnpg.storageClass | "" |
database.cnpg.storageSize | "12Gi" |
database.cnpg.enablePodAntiAffinity | true |
database.cnpg.podMonitor.enabled | true |
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.enabled | false |
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.enabled | true |
database.cnpg.backup.schedule.cron | "0 0 3 * * *" |
database.cnpg.backup.schedule.backupOwnerReference | "self" |
database.external.host | "" |
database.external.port | 5432 |
database.external.dbName | "ister" |
database.external.user | "ister" |
database.external.password | "" |
database.external.existingSecret | "" |
rabbitmq.enabled | true |
rabbitmq.image.repository | "bitnamilegacy/rabbitmq" |
rabbitmq.global.security.allowInsecureImages | true |
rabbitmq.ingress.enabled | false |
rabbitmq.ingress.tls | false |
rabbitmq.auth.username | "user" |
rabbitmq.auth.password | "" |
rabbitmq.auth.existingPasswordSecret | "" |
rabbitmq.auth.existingErlangSecret | "" |
rabbitmq.persistence.enabled | true |
rabbitmq.persistence.size | "8Gi" |
rabbitmq.resources.requests.cpu | "100m" |
rabbitmq.resources.requests.memory | "256Mi" |
rabbitmq.resources.limits.memory | "1Gi" |
externalRabbitmq.host | "" |
externalRabbitmq.port | 5672 |
externalRabbitmq.user | "user" |
externalRabbitmq.password | "" |
externalRabbitmq.existingSecret | "" |
typesense.enabled | true |
typesense.image.repository | "docker.io/typesense/typesense" |
typesense.image.tag | "30.2" |
typesense.image.pullPolicy | "IfNotPresent" |
typesense.port | 8108 |
typesense.collection | "media" |
typesense.apiKey | "" |
typesense.existingSecret | "" |
typesense.persistence.enabled | true |
typesense.persistence.existingClaim | "" |
typesense.persistence.storageClass | "" |
typesense.persistence.size | "8Gi" |
typesense.persistence.retain | true |
typesense.podSecurityContext.runAsNonRoot | true |
typesense.podSecurityContext.runAsUser | 1000 |
typesense.podSecurityContext.fsGroup | 1000 |
typesense.podSecurityContext.seccompProfile.type | "RuntimeDefault" |
typesense.securityContext.allowPrivilegeEscalation | false |
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.enabled | true |
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.enabled | false |
monitoring.serviceMonitorLabels | {} |
monitoring.interval | "30s" |
podDisruptionBudget.enabled | false |
podDisruptionBudget.minAvailable | 1 |
tests.image.repository | "curlimages/curl" |
tests.image.tag | "8.21.0" |
tests.image.pullPolicy | "IfNotPresent" |