From f44cf84581ed1e642a396d17efe49c005386381f Mon Sep 17 00:00:00 2001 From: ehsanmsb Date: Thu, 29 Oct 2026 10:06:38 +0330 Subject: [PATCH 1/2] feat: standardize Helm chart deployment options --- .github/workflows/verify.yml | 6 +- charts/minigate/README.md | 48 +++++++++++++ charts/minigate/templates/NOTES.txt | 9 +++ charts/minigate/templates/_helpers.tpl | 36 +++++++++- charts/minigate/templates/deployment.yaml | 46 ++++++++----- charts/minigate/templates/hpa.yaml | 22 ++++++ charts/minigate/templates/ingress.yaml | 4 ++ charts/minigate/templates/pdb.yaml | 13 ++++ charts/minigate/templates/service.yaml | 3 +- charts/minigate/templates/serviceaccount.yaml | 13 ++++ charts/minigate/templates/servicemonitor.yaml | 4 +- charts/minigate/values.yaml | 67 ++++++++++++++++--- docs/deployment.md | 4 +- 13 files changed, 242 insertions(+), 33 deletions(-) create mode 100644 charts/minigate/README.md create mode 100644 charts/minigate/templates/NOTES.txt create mode 100644 charts/minigate/templates/hpa.yaml create mode 100644 charts/minigate/templates/pdb.yaml create mode 100644 charts/minigate/templates/serviceaccount.yaml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 2640bfa..91c1651 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -21,7 +21,11 @@ jobs: - run: go build ./cmd/minigate - uses: azure/setup-helm@v5 - run: helm lint charts/minigate --set existingSecret=minigate-config - - run: helm template minigate charts/minigate --set existingSecret=minigate-config --set replicaCount=3 --set ingress.enabled=true --set ingress.host=files.example.com --set ingress.tlsSecretName=files-tls --set serviceMonitor.enabled=true > /dev/null + - name: Check Helm chart defaults and options + run: | + helm template minigate charts/minigate --set existingSecret=minigate-config > /tmp/minigate-default.yaml + ! grep -Eq '^kind: (Ingress|ServiceMonitor|HorizontalPodAutoscaler|PodDisruptionBudget|ServiceAccount)$' /tmp/minigate-default.yaml + helm template minigate charts/minigate --set existingSecret=minigate-config --set autoscaling.enabled=true --set podDisruptionBudget.enabled=true --set serviceAccount.create=true --set ingress.enabled=true --set ingress.host=files.example.com --set ingress.tlsSecretName=files-tls --set serviceMonitor.enabled=true > /dev/null - if: github.event_name == 'pull_request' run: docker build -t minigate:ci . diff --git a/charts/minigate/README.md b/charts/minigate/README.md new file mode 100644 index 0000000..1ed8a02 --- /dev/null +++ b/charts/minigate/README.md @@ -0,0 +1,48 @@ +# MiniGate Helm chart + +This chart runs MiniGate as a stateless Kubernetes Deployment. Its default manifests are only a Deployment and a ClusterIP Service. Ingress, HPA, PDB, ServiceAccount, and ServiceMonitor are opt-in. + +## Install + +Create a Secret with a `config.yaml` key before installing. Keep S3 credentials out of chart values and Git: + +```sh +kubectl create secret generic minigate-config --from-file=config.yaml=./private-config.yaml +helm upgrade --install minigate ./charts/minigate \ + --set existingSecret=minigate-config \ + --set replicaCount=3 +``` + +For three fixed replicas that tolerate one voluntary eviction: + +```sh +helm upgrade --install minigate ./charts/minigate \ + --set existingSecret=minigate-config \ + --set replicaCount=3 \ + --set podDisruptionBudget.enabled=true +``` + +To let Kubernetes adjust replica count, enable `autoscaling.enabled` and set `minReplicas`/`maxReplicas`; `replicaCount` is ignored while HPA is enabled. CPU autoscaling needs a working resource metrics API and a CPU request, which this chart sets by default. + +## Main values + +| Value | Default | Purpose | +| --- | --- | --- | +| `existingSecret` | `""` | Required Secret containing `config.yaml` | +| `replicaCount` | `1` | Fixed replicas when HPA is off | +| `image.repository` | `ghcr.io/ehsanmsb/minigate` | Container image | +| `image.tag` | `""` | Uses the chart's `appVersion` when empty | +| `imagePullSecrets` | `[]` | Credentials for a private registry | +| `logLevel` | `WARN` | Application log level | +| `service.port` | `80` | Internal HTTP Service port | +| `service.metricsPort` | `9090` | Internal Prometheus Service port | +| `ingress.enabled` | `false` | Expose file URLs through an Ingress | +| `serviceMonitor.enabled` | `false` | Create a Prometheus Operator ServiceMonitor | +| `autoscaling.enabled` | `false` | Create a CPU-based HPA | +| `podDisruptionBudget.enabled` | `false` | Create a PDB with `maxUnavailable: 1` | +| `serviceAccount.create` | `false` | Create a dedicated ServiceAccount | +| `resources.requests` | `100m` CPU, `64Mi` memory | Pod scheduling and HPA CPU baseline | + +The chart also supports `nameOverride`, `fullnameOverride`, `podAnnotations`, `podLabels`, `podSecurityContext`, `securityContext`, `nodeSelector`, `tolerations`, `affinity`, and configurable probes. See [`values.yaml`](values.yaml) for all defaults. + +Ingress sends traffic only to the file-serving port. Metrics stay on the internal Service. Enable ServiceMonitor only when its CRD is installed. The chart does not create or store S3 credentials. diff --git a/charts/minigate/templates/NOTES.txt b/charts/minigate/templates/NOTES.txt new file mode 100644 index 0000000..8554f65 --- /dev/null +++ b/charts/minigate/templates/NOTES.txt @@ -0,0 +1,9 @@ +MiniGate is available inside the cluster at: + http://{{ include "minigate.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}/health + +{{- if .Values.ingress.enabled }} +File URLs are available at: + {{ if .Values.ingress.tlsSecretName }}https{{ else }}http{{ end }}://{{ .Values.ingress.host }}// +{{- end }} + +Prometheus metrics: port {{ .Values.service.metricsPort }}, path /metrics. diff --git a/charts/minigate/templates/_helpers.tpl b/charts/minigate/templates/_helpers.tpl index 8d8b2c8..8fc0892 100644 --- a/charts/minigate/templates/_helpers.tpl +++ b/charts/minigate/templates/_helpers.tpl @@ -1,9 +1,41 @@ +{{- define "minigate.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + {{- define "minigate.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := include "minigate.name" . -}} +{{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} {{- end -}} -{{- define "minigate.labels" -}} -app.kubernetes.io/name: minigate +{{- define "minigate.selectorLabels" -}} +app.kubernetes.io/name: {{ include "minigate.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{- define "minigate.podLabels" -}} +{{- $selector := dict "app.kubernetes.io/name" (include "minigate.name" .) "app.kubernetes.io/instance" .Release.Name -}} +{{- toYaml (mergeOverwrite (deepCopy .Values.podLabels) $selector) -}} +{{- end -}} + +{{- define "minigate.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} +{{ include "minigate.selectorLabels" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "minigate.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- default (include "minigate.fullname" .) .Values.serviceAccount.name -}} +{{- else -}} +{{- default "default" .Values.serviceAccount.name -}} +{{- end -}} {{- end -}} diff --git a/charts/minigate/templates/deployment.yaml b/charts/minigate/templates/deployment.yaml index 539f076..4bd4e0c 100644 --- a/charts/minigate/templates/deployment.yaml +++ b/charts/minigate/templates/deployment.yaml @@ -8,21 +8,30 @@ metadata: labels: {{- include "minigate.labels" . | nindent 4 }} spec: + {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} + {{- end }} selector: matchLabels: - app.kubernetes.io/name: minigate - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "minigate.selectorLabels" . | nindent 6 }} template: metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} labels: - {{- include "minigate.labels" . | nindent 8 }} + {{- include "minigate.podLabels" . | nindent 8 }} spec: - terminationGracePeriodSeconds: 35 + serviceAccountName: {{ include "minigate.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} containers: - name: minigate image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" @@ -36,25 +45,18 @@ spec: containerPort: 8080 - name: metrics containerPort: 9090 - readinessProbe: - httpGet: - path: /health - port: http livenessProbe: - httpGet: - path: /health - port: http + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} volumeMounts: - name: config mountPath: /etc/minigate readOnly: true - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: ["ALL"] volumes: - name: config secret: @@ -62,3 +64,15 @@ spec: items: - key: config.yaml path: config.yaml + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/minigate/templates/hpa.yaml b/charts/minigate/templates/hpa.yaml new file mode 100644 index 0000000..bda33d7 --- /dev/null +++ b/charts/minigate/templates/hpa.yaml @@ -0,0 +1,22 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "minigate.fullname" . }} + labels: + {{- include "minigate.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "minigate.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/minigate/templates/ingress.yaml b/charts/minigate/templates/ingress.yaml index fd9229d..daf2372 100644 --- a/charts/minigate/templates/ingress.yaml +++ b/charts/minigate/templates/ingress.yaml @@ -8,6 +8,10 @@ metadata: name: {{ include "minigate.fullname" . }} labels: {{- include "minigate.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: {{- with .Values.ingress.className }} ingressClassName: {{ . | quote }} diff --git a/charts/minigate/templates/pdb.yaml b/charts/minigate/templates/pdb.yaml new file mode 100644 index 0000000..bb307a7 --- /dev/null +++ b/charts/minigate/templates/pdb.yaml @@ -0,0 +1,13 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "minigate.fullname" . }} + labels: + {{- include "minigate.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "minigate.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/minigate/templates/service.yaml b/charts/minigate/templates/service.yaml index 9edd302..6e9c861 100644 --- a/charts/minigate/templates/service.yaml +++ b/charts/minigate/templates/service.yaml @@ -14,5 +14,4 @@ spec: port: {{ .Values.service.metricsPort }} targetPort: metrics selector: - app.kubernetes.io/name: minigate - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "minigate.selectorLabels" . | nindent 4 }} diff --git a/charts/minigate/templates/serviceaccount.yaml b/charts/minigate/templates/serviceaccount.yaml new file mode 100644 index 0000000..6c246e2 --- /dev/null +++ b/charts/minigate/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "minigate.serviceAccountName" . }} + labels: + {{- include "minigate.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/minigate/templates/servicemonitor.yaml b/charts/minigate/templates/servicemonitor.yaml index 372baad..996d5ae 100644 --- a/charts/minigate/templates/servicemonitor.yaml +++ b/charts/minigate/templates/servicemonitor.yaml @@ -11,9 +11,9 @@ metadata: spec: selector: matchLabels: - app.kubernetes.io/name: minigate - app.kubernetes.io/instance: {{ .Release.Name }} + {{- include "minigate.selectorLabels" . | nindent 6 }} endpoints: - port: metrics path: /metrics + interval: {{ .Values.serviceMonitor.interval }} {{- end }} diff --git a/charts/minigate/values.yaml b/charts/minigate/values.yaml index 26a30f5..6619afd 100644 --- a/charts/minigate/values.yaml +++ b/charts/minigate/values.yaml @@ -1,29 +1,78 @@ replicaCount: 1 -logLevel: WARN + +nameOverride: "" +fullnameOverride: "" image: repository: ghcr.io/ehsanmsb/minigate - tag: "" pullPolicy: IfNotPresent - + tag: "" # Defaults to Chart.appVersion. imagePullSecrets: [] -# Create this Secret separately. Its config.yaml key contains S3 credentials. +# Create this Secret separately with a config.yaml key containing S3 credentials. existingSecret: "" +logLevel: WARN + +serviceAccount: + create: false + automount: false + annotations: {} + name: "" + +podAnnotations: {} +podLabels: {} +podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] service: type: ClusterIP port: 80 metricsPort: 9090 -serviceMonitor: - enabled: false - additionalLabels: {} - ingress: enabled: false className: "" + annotations: {} host: "" tlsSecretName: "" -resources: {} +resources: + requests: + cpu: 100m + memory: 64Mi + +livenessProbe: + httpGet: + path: /health + port: http +readinessProbe: + httpGet: + path: /health + port: http +terminationGracePeriodSeconds: 35 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + +podDisruptionBudget: + enabled: false + maxUnavailable: 1 + +serviceMonitor: + enabled: false + additionalLabels: {} + interval: 30s + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/docs/deployment.md b/docs/deployment.md index c43e9e4..2d49ef1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,6 +1,6 @@ # Kubernetes deployment -The chart is in [`charts/minigate`](../charts/minigate). Create a Secret from a local configuration file; keep real credentials out of Git: +The chart is in [`charts/minigate`](../charts/minigate); its [values and optional templates](../charts/minigate/README.md) are documented there. Create a Secret from a local configuration file; keep real credentials out of Git: ```sh kubectl create secret generic minigate-config --from-file=config.yaml=./private-config.yaml @@ -11,6 +11,8 @@ helm upgrade --install minigate ./charts/minigate \ The Secret needs a `config.yaml` key. Every replica mounts the same Secret read-only and starts with the same routes. Change the Secret and restart the Deployment to load a new configuration. The application has no shared state or database. +Only Deployment and Service are enabled by default. For three fixed replicas and protection against one voluntary eviction, add `--set podDisruptionBudget.enabled=true` to the install command. HPA and ServiceAccount also default to disabled; enable them only when needed. + The chart defaults to `logLevel: WARN`, so routine requests do not fill the logs. Use `--set logLevel=INFO` to include access denials and invalid ranges, or `--set logLevel=DEBUG` to record every object request. Health probes stay silent. For a public hostname, configure your ingress controller and TLS Secret, then enable the optional Ingress: From bc2cb1c1328fdef7c7f285b6763fcb1e8850179f Mon Sep 17 00:00:00 2001 From: ehsanmsb Date: Thu, 29 Oct 2026 10:12:10 +0330 Subject: [PATCH 2/2] feat: add OpenShift and Contour exposure options --- .github/workflows/verify.yml | 6 ++++- charts/minigate/README.md | 11 +++++++- charts/minigate/templates/NOTES.txt | 7 +++++ charts/minigate/templates/httpproxy.yaml | 24 +++++++++++++++++ charts/minigate/templates/route.yaml | 26 +++++++++++++++++++ charts/minigate/values.yaml | 17 ++++++++++++ docs/deployment.md | 33 ++++++++++++++++++++++++ 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 charts/minigate/templates/httpproxy.yaml create mode 100644 charts/minigate/templates/route.yaml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 91c1651..6c53a32 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -24,8 +24,12 @@ jobs: - name: Check Helm chart defaults and options run: | helm template minigate charts/minigate --set existingSecret=minigate-config > /tmp/minigate-default.yaml - ! grep -Eq '^kind: (Ingress|ServiceMonitor|HorizontalPodAutoscaler|PodDisruptionBudget|ServiceAccount)$' /tmp/minigate-default.yaml + ! grep -Eq '^kind: (Ingress|Route|HTTPProxy|ServiceMonitor|HorizontalPodAutoscaler|PodDisruptionBudget|ServiceAccount)$' /tmp/minigate-default.yaml helm template minigate charts/minigate --set existingSecret=minigate-config --set autoscaling.enabled=true --set podDisruptionBudget.enabled=true --set serviceAccount.create=true --set ingress.enabled=true --set ingress.host=files.example.com --set ingress.tlsSecretName=files-tls --set serviceMonitor.enabled=true > /dev/null + helm template minigate charts/minigate --set existingSecret=minigate-config --set route.enabled=true > /tmp/minigate-route.yaml + grep -q '^kind: Route$' /tmp/minigate-route.yaml + helm template minigate charts/minigate --set existingSecret=minigate-config --set httpProxy.enabled=true --set httpProxy.fqdn=files.example.com --set httpProxy.tlsSecretName=files-tls > /tmp/minigate-httpproxy.yaml + grep -q '^kind: HTTPProxy$' /tmp/minigate-httpproxy.yaml - if: github.event_name == 'pull_request' run: docker build -t minigate:ci . diff --git a/charts/minigate/README.md b/charts/minigate/README.md index 1ed8a02..8557b79 100644 --- a/charts/minigate/README.md +++ b/charts/minigate/README.md @@ -1,6 +1,6 @@ # MiniGate Helm chart -This chart runs MiniGate as a stateless Kubernetes Deployment. Its default manifests are only a Deployment and a ClusterIP Service. Ingress, HPA, PDB, ServiceAccount, and ServiceMonitor are opt-in. +This chart runs MiniGate as a stateless Kubernetes Deployment. Its default manifests are only a Deployment and a ClusterIP Service. Ingress, OpenShift Route, Contour HTTPProxy, HPA, PDB, ServiceAccount, and ServiceMonitor are opt-in. ## Install @@ -37,12 +37,21 @@ To let Kubernetes adjust replica count, enable `autoscaling.enabled` and set `mi | `service.port` | `80` | Internal HTTP Service port | | `service.metricsPort` | `9090` | Internal Prometheus Service port | | `ingress.enabled` | `false` | Expose file URLs through an Ingress | +| `route.enabled` | `false` | Create an OpenShift Route | +| `route.host` | `""` | Let OpenShift assign the host when empty | +| `route.tls.enabled` | `true` | Edge TLS and HTTP-to-HTTPS redirect when Route is enabled | +| `httpProxy.enabled` | `false` | Create a Contour HTTPProxy | +| `httpProxy.fqdn` | `""` | Required hostname when HTTPProxy is enabled | +| `httpProxy.tlsSecretName` | `""` | Optional TLS Secret for HTTPProxy | | `serviceMonitor.enabled` | `false` | Create a Prometheus Operator ServiceMonitor | | `autoscaling.enabled` | `false` | Create a CPU-based HPA | | `podDisruptionBudget.enabled` | `false` | Create a PDB with `maxUnavailable: 1` | | `serviceAccount.create` | `false` | Create a dedicated ServiceAccount | | `resources.requests` | `100m` CPU, `64Mi` memory | Pod scheduling and HPA CPU baseline | +| `resources.limits` | `500m` CPU, `256Mi` memory | Default container limits; tune for traffic and object sizes | The chart also supports `nameOverride`, `fullnameOverride`, `podAnnotations`, `podLabels`, `podSecurityContext`, `securityContext`, `nodeSelector`, `tolerations`, `affinity`, and configurable probes. See [`values.yaml`](values.yaml) for all defaults. Ingress sends traffic only to the file-serving port. Metrics stay on the internal Service. Enable ServiceMonitor only when its CRD is installed. The chart does not create or store S3 credentials. + +For OpenShift, see the [deployment guide](../../docs/deployment.md#openshift). HTTPProxy requires Contour and its `projectcontour.io/v1` CRD; it is not an OpenShift built-in. Enable only the exposure template your cluster uses. Route and HTTPProxy also send traffic only to the file-serving Service port. diff --git a/charts/minigate/templates/NOTES.txt b/charts/minigate/templates/NOTES.txt index 8554f65..0525ab8 100644 --- a/charts/minigate/templates/NOTES.txt +++ b/charts/minigate/templates/NOTES.txt @@ -5,5 +5,12 @@ MiniGate is available inside the cluster at: File URLs are available at: {{ if .Values.ingress.tlsSecretName }}https{{ else }}http{{ end }}://{{ .Values.ingress.host }}// {{- end }} +{{- if .Values.route.enabled }} +OpenShift Route: run `oc get route {{ include "minigate.fullname" . }}` to see its hostname. +{{- end }} +{{- if .Values.httpProxy.enabled }} +Contour HTTPProxy file URLs: + {{ if .Values.httpProxy.tlsSecretName }}https{{ else }}http{{ end }}://{{ .Values.httpProxy.fqdn }}// +{{- end }} Prometheus metrics: port {{ .Values.service.metricsPort }}, path /metrics. diff --git a/charts/minigate/templates/httpproxy.yaml b/charts/minigate/templates/httpproxy.yaml new file mode 100644 index 0000000..736366e --- /dev/null +++ b/charts/minigate/templates/httpproxy.yaml @@ -0,0 +1,24 @@ +{{- if .Values.httpProxy.enabled }} +{{- if not .Values.httpProxy.fqdn }} +{{- fail "httpProxy.fqdn is required when httpProxy.enabled=true" }} +{{- end }} +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: {{ include "minigate.fullname" . }} + labels: + {{- include "minigate.labels" . | nindent 4 }} +spec: + virtualhost: + fqdn: {{ .Values.httpProxy.fqdn | quote }} + {{- with .Values.httpProxy.tlsSecretName }} + tls: + secretName: {{ . | quote }} + {{- end }} + routes: + - conditions: + - prefix: / + services: + - name: {{ include "minigate.fullname" . }} + port: {{ .Values.service.port }} +{{- end }} diff --git a/charts/minigate/templates/route.yaml b/charts/minigate/templates/route.yaml new file mode 100644 index 0000000..93ec9b6 --- /dev/null +++ b/charts/minigate/templates/route.yaml @@ -0,0 +1,26 @@ +{{- if .Values.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "minigate.fullname" . }} + labels: + {{- include "minigate.labels" . | nindent 4 }} + {{- with .Values.route.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.route.host }} + host: {{ . | quote }} + {{- end }} + port: + targetPort: http + to: + kind: Service + name: {{ include "minigate.fullname" . }} + {{- if .Values.route.tls.enabled }} + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + {{- end }} +{{- end }} diff --git a/charts/minigate/values.yaml b/charts/minigate/values.yaml index 6619afd..b2dd8f4 100644 --- a/charts/minigate/values.yaml +++ b/charts/minigate/values.yaml @@ -43,10 +43,27 @@ ingress: host: "" tlsSecretName: "" +# OpenShift Route. The router's default certificate must cover the assigned host. +route: + enabled: false + host: "" # Leave empty to let OpenShift assign a hostname. + annotations: {} + tls: + enabled: true # Edge TLS with HTTP-to-HTTPS redirect. + +# Contour HTTPProxy requires the projectcontour.io CRD and a Contour controller. +httpProxy: + enabled: false + fqdn: "" + tlsSecretName: "" # Optional TLS Secret in the release namespace. + resources: requests: cpu: 100m memory: 64Mi + limits: + cpu: 500m + memory: 256Mi livenessProbe: httpGet: diff --git a/docs/deployment.md b/docs/deployment.md index 2d49ef1..bfe8ddf 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -13,6 +13,8 @@ The Secret needs a `config.yaml` key. Every replica mounts the same Secret read- Only Deployment and Service are enabled by default. For three fixed replicas and protection against one voluntary eviction, add `--set podDisruptionBudget.enabled=true` to the install command. HPA and ServiceAccount also default to disabled; enable them only when needed. +The chart requests `100m` CPU and `64Mi` memory and limits each container to `500m` CPU and `256Mi` memory. Adjust `resources` in your values file after measuring production traffic. + The chart defaults to `logLevel: WARN`, so routine requests do not fill the logs. Use `--set logLevel=INFO` to include access denials and invalid ranges, or `--set logLevel=DEBUG` to record every object request. Health probes stay silent. For a public hostname, configure your ingress controller and TLS Secret, then enable the optional Ingress: @@ -30,3 +32,34 @@ helm upgrade --install minigate ./charts/minigate \ Use a released chart archive from a GitHub release for a matching image tag. If deploying directly from source before a release, set `image.tag` to an existing GHCR tag. The packaged chart defaults to its matching released image version. The service listens internally on port 8080; Kubernetes Service exposes port 80. `/health` is used for readiness and liveness. Ensure the cluster allows outbound HTTPS to each configured storage endpoint and can pull from GHCR. Prometheus metrics are available on the internal Service port `9090` at `/metrics`. The Ingress routes only to the file-serving port, so it does not expose metrics publicly. If your cluster has Prometheus Operator and its ServiceMonitor CRD, enable `--set serviceMonitor.enabled=true`; set `serviceMonitor.additionalLabels` as needed by your Prometheus selector. Each replica exports its own metrics, including `minigate_configured_buckets`; scrape every pod through service discovery. Prometheus generates `up` per scrape target, while `/health` is the Kubernetes readiness and liveness check. + +## OpenShift + +Log in with `oc`, select your project, and create the configuration Secret in that project. The Route is disabled by default; enable it to expose file URLs through the OpenShift router: + +```sh +oc login +oc project +oc create secret generic minigate-config --from-file=config.yaml=./private-config.yaml +helm upgrade --install minigate ./charts/minigate \ + --set existingSecret=minigate-config \ + --set replicaCount=3 \ + --set route.enabled=true +oc get route minigate +``` + +OpenShift assigns a hostname when `route.host` is empty. The resulting URL is `https:////`. By default, the Route terminates TLS at the router and redirects HTTP to HTTPS. Its router certificate must cover the assigned host. To use a custom domain, set `route.host`, configure DNS for that host, and arrange a matching certificate on the router. If TLS is handled elsewhere, set `route.tls.enabled=false` and configure HTTPS at that edge. The Route targets only the HTTP Service port; metrics remain internal. + +The container does not request a fixed user ID, so an OpenShift project can assign a UID under its security context constraints. The image and chart run without privilege escalation and do not write to the root filesystem. Confirm that the cluster can pull the GHCR image and reach your S3 endpoints. If your environment requires a pull Secret, set `imagePullSecrets`. + +If your OpenShift cluster uses Contour instead of the built-in router, install Contour and its `projectcontour.io/v1` HTTPProxy CRD first, then enable HTTPProxy with a hostname: + +```sh +helm upgrade --install minigate ./charts/minigate \ + --set existingSecret=minigate-config \ + --set httpProxy.enabled=true \ + --set httpProxy.fqdn=files.example.com \ + --set httpProxy.tlsSecretName=files-tls +``` + +The `files-tls` Secret must contain a certificate for `files.example.com`. Leave `httpProxy.tlsSecretName` empty only if HTTP is intentionally served behind another TLS edge. Enable only the exposure template used by your cluster; Ingress, Route, and HTTPProxy are all disabled by default.