Per-app ingress and service, media subPaths
Three review changes to the freesailarr chart. Ingress: the chart no longer derives hosts as <app>.<ingress.domain> from one Ingress object. The global ingress.domain is gone; every app carries its own <app>.ingress.host and renders its own Ingress <release>-<chart>-<app>-ingress with its own TLS secret tls-<release>-<chart>-<app>-ingress. The hosts are therefore independent and may live in unrelated domains. The host is required, but the required() call sits inside the loop over the *enabled* app ingresses, so switching an app's ingress off must not make the chart demand a host for it. clusterIssuer, className and annotations stay global defaults and are overridable per app; an explicitly set per-app value wins (nil check instead of "default", so an empty override is honoured). Media: media.<category>.subPath mounts a sub-directory of the claim instead of its root, default null (claim root = previous behaviour). The same existingClaim may back all three categories with different subPaths. That is the point rather than cosmetics: Radarr and Sonarr import by moving, and a move within one filesystem can be a hardlink instead of a copy - across volume boundaries every import duplicates the file. Services: the collective Service with named ports is replaced by one ClusterIP Service <release>-<chart>-<app>-service per enabled app that listens on a port, flaresolverr included; recyclarr serves no port in daemon mode and gets none. Each ingress points at its own app's service. All containers share gluetun's network namespace, so the services differ only in their port - they provide stable per-app DNS names, not network separation. The StatefulSet's governing serviceName no longer names a removed object.
This commit is contained in:
6 files changed
+169
-46
No files matched your search
@@ -148,17 +148,24 @@ Usage:
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The enabled apps that expose a web UI, as a JSON array of {name, port} dicts
|
||||
(container port name == app name == ingress subdomain). Consumed by the
|
||||
service, the ingress and the firewall-ports helper below:
|
||||
The enabled app containers that listen on a port, as a JSON array of
|
||||
{name, port} dicts (container port name == app name == service name suffix).
|
||||
Consumed by the per-app services, the per-app ingresses and the firewall-ports
|
||||
helper below:
|
||||
|
||||
{{- $apps := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
{{- $apps := include "freesailarr.portApps" . | fromJsonArray }}
|
||||
|
||||
recyclarr is deliberately absent: in daemon mode it serves no port, so it gets
|
||||
no container port, no service and no ingress. flaresolverr is present - it
|
||||
listens on 8191 and therefore gets a service - but it has no ingress block in
|
||||
values and is never exposed from outside the cluster.
|
||||
*/}}
|
||||
{{- define "freesailarr.uiApps" -}}
|
||||
{{- define "freesailarr.portApps" -}}
|
||||
{{- $apps := list -}}
|
||||
{{- range $app := list
|
||||
(dict "name" "qbittorrent" "port" 8080)
|
||||
(dict "name" "prowlarr" "port" 9696)
|
||||
(dict "name" "flaresolverr" "port" 8191)
|
||||
(dict "name" "radarr" "port" 7878)
|
||||
(dict "name" "sonarr" "port" 8989)
|
||||
(dict "name" "seerr" "port" 5055)
|
||||
@@ -171,19 +178,33 @@ service, the ingress and the firewall-ports helper below:
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Comma-separated FIREWALL_INPUT_PORTS for gluetun: the UI ports of the enabled
|
||||
apps (ingress traffic arrives on the pod IP), flaresolverr's 8191 when enabled
|
||||
(the kubelet probes connect to the pod IP too) and 9999 for gluetun's own
|
||||
health server. Without these the gluetun firewall drops the traffic.
|
||||
The apps that render an Ingress: the enabled port apps whose values carry an
|
||||
ingress block with enabled: true. Used by the ingress template.
|
||||
|
||||
{{- $apps := include "freesailarr.ingressApps" . | fromJsonArray }}
|
||||
*/}}
|
||||
{{- define "freesailarr.ingressApps" -}}
|
||||
{{- $apps := list -}}
|
||||
{{- range $app := include "freesailarr.portApps" . | fromJsonArray -}}
|
||||
{{- $config := index $.Values $app.name -}}
|
||||
{{- if and $config.ingress $config.ingress.enabled -}}
|
||||
{{- $apps = append $apps $app -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- toJson $apps -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Comma-separated FIREWALL_INPUT_PORTS for gluetun: the ports of the enabled app
|
||||
containers (ingress traffic and the kubelet probes both arrive on the pod IP)
|
||||
plus 9999 for gluetun's own health server. Without these the gluetun firewall
|
||||
drops the traffic.
|
||||
*/}}
|
||||
{{- define "freesailarr.firewallInputPorts" -}}
|
||||
{{- $ports := list -}}
|
||||
{{- range $app := include "freesailarr.uiApps" . | fromJsonArray -}}
|
||||
{{- range $app := include "freesailarr.portApps" . | fromJsonArray -}}
|
||||
{{- $ports = append $ports (int $app.port) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.flaresolverr.enabled -}}
|
||||
{{- $ports = append $ports 8191 -}}
|
||||
{{- end -}}
|
||||
{{- $ports = append $ports 9999 -}}
|
||||
{{- join "," $ports -}}
|
||||
{{- end -}}
|
||||
@@ -1,38 +1,51 @@
|
||||
{{- $apps := list }}
|
||||
{{- range $app := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
{{- if (index $.Values $app.name).ingress.enabled }}
|
||||
{{- $apps = append $apps $app }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and .Values.ingress.enabled (gt (len $apps) 0) }}
|
||||
{{/*
|
||||
One Ingress object per app whose ingress is enabled, each with its own host and
|
||||
its own TLS secret. The hosts are independent values - they do not have to
|
||||
share a domain - and each ingress points at its own app's service.
|
||||
|
||||
<app>.ingress.host is required, but only for an app whose ingress is actually
|
||||
enabled: the required call sits inside the loop over the enabled apps, so
|
||||
turning an app's ingress off must never make the chart demand a host for it.
|
||||
*/}}
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $app := include "freesailarr.ingressApps" $ | fromJsonArray }}
|
||||
{{- $config := (index $.Values $app.name).ingress }}
|
||||
{{- /* clusterIssuer, className and annotations default to the global ingress
|
||||
block and are overridable per app; an explicitly set per-app value wins
|
||||
even when it is empty, hence the nil check instead of "default". */}}
|
||||
{{- $clusterIssuer := $.Values.ingress.clusterIssuer }}
|
||||
{{- if not (kindIs "invalid" $config.clusterIssuer) }}{{- $clusterIssuer = $config.clusterIssuer }}{{- end }}
|
||||
{{- $className := $.Values.ingress.className }}
|
||||
{{- if not (kindIs "invalid" $config.className) }}{{- $className = $config.className }}{{- end }}
|
||||
{{- $annotations := $.Values.ingress.annotations }}
|
||||
{{- if not (kindIs "invalid" $config.annotations) }}{{- $annotations = $config.annotations }}{{- end }}
|
||||
{{- $host := required (printf "%s.ingress.host must be provided while %s.ingress.enabled is true" $app.name $app.name) $config.host }}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
name: {{ $.Release.Name }}-{{ $.Chart.Name }}-{{ $app.name }}-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" $clusterIssuer }}
|
||||
{{- with $annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
ingressClassName: {{ $className }}
|
||||
rules:
|
||||
{{- range $apps }}
|
||||
- host: {{ .name }}.{{ required "A Domain/Host must be provided" $.Values.ingress.domain }}
|
||||
- host: {{ $host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ $.Release.Name }}-{{ $.Chart.Name }}-service
|
||||
name: {{ $.Release.Name }}-{{ $.Chart.Name }}-{{ $app.name }}-service
|
||||
port:
|
||||
name: {{ .name }}
|
||||
name: {{ $app.name }}
|
||||
path: /
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
tls:
|
||||
- hosts:
|
||||
{{- range $apps }}
|
||||
- {{ .name }}.{{ $.Values.ingress.domain }}
|
||||
{{- end }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
- {{ $host | quote }}
|
||||
secretName: tls-{{ $.Release.Name }}-{{ $.Chart.Name }}-{{ $app.name }}-ingress
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,18 +1,25 @@
|
||||
{{- $apps := include "freesailarr.uiApps" . | fromJsonArray }}
|
||||
{{- if gt (len $apps) 0 }}
|
||||
{{/*
|
||||
One ClusterIP service per enabled app container that listens on a port
|
||||
(recyclarr has none in daemon mode, so it gets no service).
|
||||
|
||||
All containers share gluetun's network namespace, so every one of these
|
||||
services resolves to the same pod IP and they differ only in their port. Their
|
||||
purpose is a stable, per-app cluster-internal DNS name - each ingress points at
|
||||
its own app's service - not network separation between the apps.
|
||||
*/}}
|
||||
{{- range $app := include "freesailarr.portApps" $ | fromJsonArray }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
name: {{ $.Release.Name }}-{{ $.Chart.Name }}-{{ $app.name }}-service
|
||||
spec:
|
||||
selector:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
app: {{ $.Release.Name }}-{{ $.Chart.Name }}-sfs
|
||||
ports:
|
||||
{{- range $apps }}
|
||||
- protocol: TCP
|
||||
port: {{ int .port }}
|
||||
targetPort: {{ .name }}
|
||||
name: {{ .name }}
|
||||
{{- end }}
|
||||
port: {{ int $app.port }}
|
||||
targetPort: {{ $app.name }}
|
||||
name: {{ $app.name }}
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -6,7 +6,11 @@ metadata:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
spec:
|
||||
replicas: {{ include "freesailarr.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.replicas) }}
|
||||
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
# Governing service name. Each app has its own service (see
|
||||
# freesailarr.service.yaml); nothing consumes the per-pod DNS name a
|
||||
# governing service would provide, so the chart deliberately creates no
|
||||
# object under this name - the field only has to be set.
|
||||
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||
@@ -200,6 +204,9 @@ spec:
|
||||
name: qbittorrent-config
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
{{- with .Values.media.downloads.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.prowlarr.enabled }}
|
||||
- name: prowlarr
|
||||
@@ -293,8 +300,14 @@ spec:
|
||||
name: radarr-config
|
||||
- mountPath: /movies
|
||||
name: media-movies
|
||||
{{- with .Values.media.movies.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
{{- with .Values.media.downloads.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.recyclarr.enabled }}
|
||||
- name: recyclarr
|
||||
@@ -357,8 +370,14 @@ spec:
|
||||
name: sonarr-config
|
||||
- mountPath: /tv
|
||||
name: media-tv
|
||||
{{- with .Values.media.tv.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
- mountPath: /downloads
|
||||
name: media-downloads
|
||||
{{- with .Values.media.downloads.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.seerr.enabled }}
|
||||
- name: seerr
|
||||
@@ -421,8 +440,14 @@ spec:
|
||||
name: bazarr-config
|
||||
- mountPath: /movies
|
||||
name: media-movies
|
||||
{{- with .Values.media.movies.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
- mountPath: /tv
|
||||
name: media-tv
|
||||
{{- with .Values.media.tv.subPath }}
|
||||
subPath: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
|
||||
Reference in new issue
Block a user