feat(deploy): add Helm chart for k3s and production Kubernetes
Include values profiles, secrets handling, TLS hooks, HPA/PDB, and in-cluster data services.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
.git
|
||||
*.md
|
||||
*.tgz
|
||||
secrets*.yaml
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v2
|
||||
name: pargar
|
||||
description: Pargar gated LMS - Go API, Next.js frontend, Postgres, Redis, S3-compatible storage
|
||||
type: application
|
||||
version: 0.2.0
|
||||
appVersion: "0.1.0"
|
||||
keywords:
|
||||
- lms
|
||||
- pargar
|
||||
- traefik
|
||||
maintainers:
|
||||
- name: Pargar
|
||||
home: https://github.com/example/pargar
|
||||
sources:
|
||||
- https://github.com/example/pargar
|
||||
@@ -0,0 +1,26 @@
|
||||
Pargar {{ .Chart.AppVersion }} installed as release "{{ .Release.Name }}" in namespace "{{ .Release.Namespace }}".
|
||||
|
||||
Public URL: {{ .Values.global.publicUrl }}
|
||||
Domain: {{ .Values.global.domain }}
|
||||
|
||||
1) Ensure DNS /hosts points to your Traefik entrypoint:
|
||||
{{ .Values.global.domain }} → Traefik LoadBalancer / node IP
|
||||
|
||||
2) Images must exist on the cluster nodes (k3s local):
|
||||
docker compose -f deploy/compose/compose.yaml -f deploy/compose/compose.dev.yaml build
|
||||
docker save pargar-backend:local pargar-frontend:local | sudo k3s ctr images import -
|
||||
|
||||
3) Upgrade / install:
|
||||
helm upgrade --install {{ .Release.Name }} ./deploy/helm/pargar \
|
||||
-n {{ .Release.Namespace }} --create-namespace \
|
||||
-f deploy/helm/pargar/values-k3s.yaml
|
||||
|
||||
4) Health:
|
||||
kubectl -n {{ .Release.Namespace }} get pods,ingressroute,svc
|
||||
curl -k {{ .Values.global.publicUrl }}/api/health
|
||||
|
||||
{{- if not .Values.ingress.tls.existingSecret }}
|
||||
|
||||
TLS: chart-managed self-signed certificate (browser warning expected).
|
||||
For production set ingress.tls.existingSecret (cert-manager).
|
||||
{{- end }}
|
||||
@@ -0,0 +1,110 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "pargar.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.labels" -}}
|
||||
helm.sh/chart: {{ include "pargar.chart" . }}
|
||||
{{ include "pargar.selectorLabels" . }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "pargar.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "pargar.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.appSecretName" -}}
|
||||
{{- if .Values.backend.existingSecret }}
|
||||
{{- .Values.backend.existingSecret }}
|
||||
{{- else }}
|
||||
{{- printf "%s-app" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.postgresSecretName" -}}
|
||||
{{- if .Values.postgres.existingSecret }}
|
||||
{{- .Values.postgres.existingSecret }}
|
||||
{{- else }}
|
||||
{{- printf "%s-postgres" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.redisSecretName" -}}
|
||||
{{- if .Values.redis.existingSecret }}
|
||||
{{- .Values.redis.existingSecret }}
|
||||
{{- else }}
|
||||
{{- printf "%s-redis" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.rustfsSecretName" -}}
|
||||
{{- if .Values.rustfs.existingSecret }}
|
||||
{{- .Values.rustfs.existingSecret }}
|
||||
{{- else }}
|
||||
{{- printf "%s-rustfs" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.tlsSecretName" -}}
|
||||
{{- if .Values.ingress.tls.existingSecret }}
|
||||
{{- .Values.ingress.tls.existingSecret }}
|
||||
{{- else }}
|
||||
{{- printf "%s-tls" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.postgresHost" -}}
|
||||
{{- printf "%s-postgres" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.redisHost" -}}
|
||||
{{- printf "%s-redis" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "pargar.rustfsHost" -}}
|
||||
{{- printf "%s-rustfs" (include "pargar.fullname" .) }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Resolve container image: optional global.imageRegistry + repository:tag
|
||||
Usage: {{ include "pargar.image" (dict "root" . "image" .Values.backend.image) }}
|
||||
*/}}
|
||||
{{- define "pargar.image" -}}
|
||||
{{- $registry := .root.Values.global.imageRegistry | default "" -}}
|
||||
{{- $repo := .image.repository -}}
|
||||
{{- $tag := .image.tag | default .root.Chart.AppVersion | toString -}}
|
||||
{{- if $registry -}}
|
||||
{{- printf "%s/%s:%s" (trimSuffix "/" $registry) $repo $tag -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s:%s" $repo $tag -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,204 @@
|
||||
{{- if .Values.backend.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-backend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.backend.service.port }}
|
||||
targetPort: http
|
||||
selector:
|
||||
{{- include "pargar.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-backend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
{{- if not .Values.backend.autoscaling.enabled }}
|
||||
replicas: {{ .Values.backend.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
serviceAccountName: {{ include "pargar.serviceAccountName" . }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: backend
|
||||
image: {{ include "pargar.image" (dict "root" . "image" .Values.backend.image) | quote }}
|
||||
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.backend.service.port }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: {{ .Values.backend.service.port | quote }}
|
||||
- name: PUBLIC_URL
|
||||
value: {{ .Values.global.publicUrl | quote }}
|
||||
- name: FRONTEND_ORIGINS
|
||||
value: {{ .Values.global.frontendOrigins | quote }}
|
||||
- name: TRUSTED_PROXY_CIDRS
|
||||
value: {{ .Values.global.trustedProxyCIDRs | quote }}
|
||||
- name: DEV_MODE
|
||||
value: {{ .Values.backend.env.devMode | quote }}
|
||||
- name: SEED_CURRICULUM
|
||||
value: {{ .Values.backend.env.seedCurriculum | quote }}
|
||||
- name: ADMIN_NAME
|
||||
value: {{ .Values.backend.admin.name | quote }}
|
||||
- name: ADMIN_USERNAME
|
||||
value: {{ .Values.backend.admin.username | quote }}
|
||||
- name: ADMIN_EMAIL
|
||||
value: {{ .Values.backend.admin.email | quote }}
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: jwt-secret
|
||||
- name: ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: admin-password
|
||||
- name: METRICS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: metrics-token
|
||||
optional: true
|
||||
- name: DONATION_ENABLED
|
||||
value: {{ .Values.backend.env.donationEnabled | quote }}
|
||||
- name: PHYSICAL_CERT_ENABLED
|
||||
value: {{ .Values.backend.env.physicalCertEnabled | quote }}
|
||||
- name: PHYSICAL_CERT_PRICE_IRR
|
||||
value: {{ .Values.backend.env.physicalCertPriceIrr | quote }}
|
||||
- name: PHYSICAL_CERT_WINDOW_DAYS
|
||||
value: {{ .Values.backend.env.physicalCertWindowDays | quote }}
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: {{ .Values.backend.env.otelEndpoint | quote }}
|
||||
- name: OTEL_SERVICE_NAME
|
||||
value: {{ .Values.backend.env.otelServiceName | quote }}
|
||||
- name: STORAGE_DRIVER
|
||||
value: s3
|
||||
- name: STORAGE_REGION
|
||||
value: {{ .Values.rustfs.region | quote }}
|
||||
- name: STORAGE_BUCKET
|
||||
value: {{ .Values.rustfs.bucket | quote }}
|
||||
{{- if .Values.postgres.enabled }}
|
||||
- name: POSTGRES_USER
|
||||
value: {{ .Values.postgres.auth.username | quote }}
|
||||
- name: POSTGRES_DB
|
||||
value: {{ .Values.postgres.auth.database | quote }}
|
||||
- name: POSTGRES_HOST
|
||||
value: {{ include "pargar.postgresHost" . | quote }}
|
||||
- name: POSTGRES_PORT
|
||||
value: "5432"
|
||||
- name: POSTGRES_SSLMODE
|
||||
value: "disable"
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.postgresSecretName" . }}
|
||||
key: postgres-password
|
||||
{{- else }}
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: database-url
|
||||
{{- end }}
|
||||
{{- if .Values.redis.enabled }}
|
||||
- name: REDIS_HOST
|
||||
value: {{ include "pargar.redisHost" . | quote }}
|
||||
- name: REDIS_PORT
|
||||
value: "6379"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.redisSecretName" . }}
|
||||
key: redis-password
|
||||
{{- else }}
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: redis-url
|
||||
{{- end }}
|
||||
{{- if .Values.rustfs.enabled }}
|
||||
- name: STORAGE_ENDPOINT
|
||||
value: {{ printf "http://%s:9000" (include "pargar.rustfsHost" .) | quote }}
|
||||
- name: STORAGE_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.rustfsSecretName" . }}
|
||||
key: access-key
|
||||
- name: STORAGE_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.rustfsSecretName" . }}
|
||||
key: secret-key
|
||||
{{- else }}
|
||||
- name: STORAGE_ENDPOINT
|
||||
value: {{ required "external.storageEndpoint required when rustfs.enabled=false" .Values.external.storageEndpoint | quote }}
|
||||
- name: STORAGE_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: storage-access-key
|
||||
- name: STORAGE_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.appSecretName" . }}
|
||||
key: storage-secret-key
|
||||
{{- end }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
{{- with .Values.backend.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,79 @@
|
||||
{{- if and .Values.postgres.enabled .Values.backup.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-backups
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backup
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
{{- if .Values.backup.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.backup.persistence.storageClass | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.backup.persistence.size | quote }}
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-pg-backup
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backup
|
||||
spec:
|
||||
schedule: {{ .Values.backup.schedule | quote }}
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 12 }}
|
||||
app.kubernetes.io/component: backup
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
fsGroup: 999
|
||||
containers:
|
||||
- name: pg-dump
|
||||
image: {{ .Values.backup.image | quote }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: {{ include "pargar.postgresHost" . | quote }}
|
||||
- name: PGPORT
|
||||
value: "5432"
|
||||
- name: PGUSER
|
||||
value: {{ .Values.postgres.auth.username | quote }}
|
||||
- name: PGDATABASE
|
||||
value: {{ .Values.postgres.auth.database | quote }}
|
||||
- name: RETENTION_DAYS
|
||||
value: {{ .Values.backup.retentionDays | quote }}
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.postgresSecretName" . }}
|
||||
key: postgres-password
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
set -euo pipefail
|
||||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
OUT="/backups/pargar-${STAMP}.sql.gz"
|
||||
echo "Writing ${OUT}"
|
||||
pg_dump --no-owner --no-acl | gzip -c > "${OUT}"
|
||||
find /backups -type f -name 'pargar-*.sql.gz' -mtime +"${RETENTION_DAYS}" -print -delete || true
|
||||
ls -lh /backups || true
|
||||
volumeMounts:
|
||||
- name: backups
|
||||
mountPath: /backups
|
||||
volumes:
|
||||
- name: backups
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "pargar.fullname" . }}-backups
|
||||
{{- end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled .Values.certManager.enabled }}
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-tls
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
secretName: {{ include "pargar.tlsSecretName" . }}
|
||||
duration: {{ .Values.certManager.duration | default "2160h" | quote }}
|
||||
renewBefore: {{ .Values.certManager.renewBefore | default "360h" | quote }}
|
||||
issuerRef:
|
||||
name: {{ required "certManager.issuerRef.name required when certManager.enabled" .Values.certManager.issuerRef.name | quote }}
|
||||
kind: {{ .Values.certManager.issuerRef.kind | default "ClusterIssuer" | quote }}
|
||||
{{- if .Values.certManager.issuerRef.group }}
|
||||
group: {{ .Values.certManager.issuerRef.group | quote }}
|
||||
{{- end }}
|
||||
dnsNames:
|
||||
- {{ .Values.global.domain | quote }}
|
||||
{{- range .Values.certManager.extraDnsNames }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,83 @@
|
||||
{{- if .Values.frontend.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-frontend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.frontend.service.port }}
|
||||
targetPort: http
|
||||
selector:
|
||||
{{- include "pargar.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-frontend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
{{- if not .Values.frontend.autoscaling.enabled }}
|
||||
replicas: {{ .Values.frontend.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
serviceAccountName: {{ include "pargar.serviceAccountName" . }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: frontend
|
||||
image: {{ include "pargar.image" (dict "root" . "image" .Values.frontend.image) | quote }}
|
||||
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: production
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.frontend.service.port }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
{{- with .Values.frontend.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,67 @@
|
||||
{{- if and .Values.backend.enabled .Values.backend.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-backend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "pargar.fullname" . }}-backend
|
||||
minReplicas: {{ .Values.backend.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.backend.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.backend.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.backend.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.backend.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
---
|
||||
{{- end }}
|
||||
{{- if and .Values.frontend.enabled .Values.frontend.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-frontend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "pargar.fullname" . }}-frontend
|
||||
minReplicas: {{ .Values.frontend.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.frontend.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.frontend.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.frontend.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.frontend.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.frontend.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,50 @@
|
||||
{{- if and .Values.ingress.enabled (not .Values.ingress.useIngressRoute) }}
|
||||
{{- $fullName := include "pargar.fullname" . }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.middlewares: {{ .Release.Namespace }}-{{ $fullName }}-security-headers@kubernetescrd,{{ .Release.Namespace }}-{{ $fullName }}-compress@kubernetescrd
|
||||
{{- if .Values.ingress.tls.clusterIssuer }}
|
||||
cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.clusterIssuer | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className | quote }}
|
||||
{{- if .Values.ingress.tls.enabled }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.global.domain | quote }}
|
||||
secretName: {{ include "pargar.tlsSecretName" . }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.global.domain | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}-backend
|
||||
port:
|
||||
number: {{ .Values.backend.service.port }}
|
||||
- path: /media
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}-backend
|
||||
port:
|
||||
number: {{ .Values.backend.service.port }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ $fullName }}-frontend
|
||||
port:
|
||||
number: {{ .Values.frontend.service.port }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,97 @@
|
||||
{{- if and .Values.ingress.enabled .Values.ingress.useIngressRoute }}
|
||||
{{- $fullName := include "pargar.fullname" . }}
|
||||
{{- $domain := .Values.global.domain }}
|
||||
{{- $tlsSecret := include "pargar.tlsSecretName" . }}
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: {{ $fullName }}-http
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`{{ $domain }}`)
|
||||
kind: Rule
|
||||
{{- if .Values.middlewares.redirectHttps }}
|
||||
middlewares:
|
||||
- name: {{ $fullName }}-redirect-https
|
||||
{{- end }}
|
||||
services:
|
||||
- name: {{ $fullName }}-frontend
|
||||
port: {{ .Values.frontend.service.port }}
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: {{ $fullName }}-https
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
# Certificate public API — highest priority + noindex
|
||||
- match: Host(`{{ $domain }}`) && PathPrefix(`/api/certificates`)
|
||||
kind: Rule
|
||||
priority: 110
|
||||
middlewares:
|
||||
{{- if .Values.middlewares.securityHeaders }}
|
||||
- name: {{ $fullName }}-security-headers
|
||||
{{- end }}
|
||||
{{- if .Values.middlewares.compress }}
|
||||
- name: {{ $fullName }}-compress
|
||||
{{- end }}
|
||||
- name: {{ $fullName }}-noindex
|
||||
services:
|
||||
- name: {{ $fullName }}-backend
|
||||
port: {{ .Values.backend.service.port }}
|
||||
# API + media (+ WebSocket /api/ws)
|
||||
- match: Host(`{{ $domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/media`))
|
||||
kind: Rule
|
||||
priority: 100
|
||||
middlewares:
|
||||
{{- if .Values.middlewares.securityHeaders }}
|
||||
- name: {{ $fullName }}-security-headers
|
||||
{{- end }}
|
||||
{{- if .Values.middlewares.compress }}
|
||||
- name: {{ $fullName }}-compress
|
||||
{{- end }}
|
||||
services:
|
||||
- name: {{ $fullName }}-backend
|
||||
port: {{ .Values.backend.service.port }}
|
||||
# Unlisted certificate pages
|
||||
- match: Host(`{{ $domain }}`) && PathPrefix(`/c`)
|
||||
kind: Rule
|
||||
priority: 50
|
||||
middlewares:
|
||||
{{- if .Values.middlewares.securityHeaders }}
|
||||
- name: {{ $fullName }}-security-headers
|
||||
{{- end }}
|
||||
{{- if .Values.middlewares.compress }}
|
||||
- name: {{ $fullName }}-compress
|
||||
{{- end }}
|
||||
- name: {{ $fullName }}-noindex
|
||||
services:
|
||||
- name: {{ $fullName }}-frontend
|
||||
port: {{ .Values.frontend.service.port }}
|
||||
# Frontend catch-all
|
||||
- match: Host(`{{ $domain }}`)
|
||||
kind: Rule
|
||||
priority: 1
|
||||
middlewares:
|
||||
{{- if .Values.middlewares.securityHeaders }}
|
||||
- name: {{ $fullName }}-security-headers
|
||||
{{- end }}
|
||||
{{- if .Values.middlewares.compress }}
|
||||
- name: {{ $fullName }}-compress
|
||||
{{- end }}
|
||||
services:
|
||||
- name: {{ $fullName }}-frontend
|
||||
port: {{ .Values.frontend.service.port }}
|
||||
{{- if .Values.ingress.tls.enabled }}
|
||||
tls:
|
||||
secretName: {{ $tlsSecret }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,63 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- if .Values.middlewares.securityHeaders }}
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-security-headers
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
headers:
|
||||
stsSeconds: 31536000
|
||||
stsIncludeSubdomains: true
|
||||
contentTypeNosniff: true
|
||||
browserXssFilter: true
|
||||
frameDeny: true
|
||||
referrerPolicy: strict-origin-when-cross-origin
|
||||
permissionsPolicy: "camera=(), microphone=(), geolocation=()"
|
||||
customResponseHeaders:
|
||||
Server: ""
|
||||
Content-Security-Policy: >-
|
||||
default-src 'self'; base-uri 'self'; form-action 'self';
|
||||
frame-ancestors 'none'; object-src 'none';
|
||||
script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data: blob:; font-src 'self' data:;
|
||||
media-src 'self' blob:; connect-src 'self' ws: wss:;
|
||||
worker-src 'self' blob:
|
||||
---
|
||||
{{- end }}
|
||||
{{- if .Values.middlewares.compress }}
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-compress
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
compress: {}
|
||||
---
|
||||
{{- end }}
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-noindex
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
headers:
|
||||
customResponseHeaders:
|
||||
X-Robots-Tag: "noindex, nofollow, noarchive"
|
||||
---
|
||||
{{- if and .Values.ingress.useIngressRoute .Values.middlewares.redirectHttps }}
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-redirect-https
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,229 @@
|
||||
{{- if .Values.networkPolicy.enabled }}
|
||||
{{- $fullName := include "pargar.fullname" . }}
|
||||
# Default deny all ingress/egress in the release namespace, then allow required paths.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-default-deny
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
---
|
||||
{{- if .Values.backend.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-backend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: backend
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
{{- range .Values.networkPolicy.ingressNamespaces }}
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ . | quote }}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- toYaml $.Values.networkPolicy.ingressPodLabels | nindent 14 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: {{ .Values.backend.service.port }}
|
||||
egress:
|
||||
# DNS
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
{{- if .Values.postgres.enabled }}
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
{{- end }}
|
||||
{{- if .Values.redis.enabled }}
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: redis
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
{{- end }}
|
||||
{{- if .Values.rustfs.enabled }}
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 9000
|
||||
{{- else }}
|
||||
# External S3 / managed data when in-cluster rustfs is off
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
- 169.254.0.0/16
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
{{- end }}
|
||||
---
|
||||
{{- end }}
|
||||
{{- if .Values.frontend.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-frontend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
{{- range .Values.networkPolicy.ingressNamespaces }}
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: {{ . | quote }}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- toYaml $.Values.networkPolicy.ingressPodLabels | nindent 14 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: {{ .Values.frontend.service.port }}
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
---
|
||||
{{- end }}
|
||||
{{- if .Values.postgres.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-postgres
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: backend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
egress: []
|
||||
---
|
||||
{{- end }}
|
||||
{{- if .Values.redis.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-redis
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: redis
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: redis
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: backend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
egress: []
|
||||
---
|
||||
{{- end }}
|
||||
{{- if .Values.rustfs.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ $fullName }}-rustfs
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 14 }}
|
||||
app.kubernetes.io/component: backend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 9000
|
||||
egress: []
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,41 @@
|
||||
{{- if and .Values.backend.enabled .Values.backend.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-backend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: backend
|
||||
spec:
|
||||
{{- if .Values.backend.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.backend.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.backend.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: backend
|
||||
---
|
||||
{{- end }}
|
||||
{{- if and .Values.frontend.enabled .Values.frontend.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-frontend
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
spec:
|
||||
{{- if .Values.frontend.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.frontend.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.frontend.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.frontend.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: frontend
|
||||
{{- end }}
|
||||
@@ -0,0 +1,93 @@
|
||||
{{- if .Values.postgres.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "pargar.postgresHost" . }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: postgres
|
||||
port: 5432
|
||||
targetPort: postgres
|
||||
selector:
|
||||
{{- include "pargar.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-postgres
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
spec:
|
||||
serviceName: {{ include "pargar.postgresHost" . }}
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: {{ .Values.postgres.image }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: postgres
|
||||
containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: {{ .Values.postgres.auth.username | quote }}
|
||||
- name: POSTGRES_DB
|
||||
value: {{ .Values.postgres.auth.database | quote }}
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.postgresSecretName" . }}
|
||||
key: postgres-password
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["pg_isready", "-U", {{ .Values.postgres.auth.username | quote }}, "-d", {{ .Values.postgres.auth.database | quote }}]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["pg_isready", "-U", {{ .Values.postgres.auth.username | quote }}, "-d", {{ .Values.postgres.auth.database | quote }}]
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
{{- with .Values.postgres.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
{{- if not .Values.postgres.persistence.enabled }}
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.postgres.persistence.enabled }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
{{- if .Values.postgres.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.postgres.persistence.storageClass | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.postgres.persistence.size }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,88 @@
|
||||
{{- if .Values.redis.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "pargar.redisHost" . }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: redis
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: redis
|
||||
selector:
|
||||
{{- include "pargar.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: redis
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-redis
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: redis
|
||||
spec:
|
||||
serviceName: {{ include "pargar.redisHost" . }}
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: {{ .Values.redis.image }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["sh", "-c", "exec redis-server --requirepass \"$REDIS_PASSWORD\" --appendonly yes"]
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.redisSecretName" . }}
|
||||
key: redis-password
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", "redis-cli -a \"$REDIS_PASSWORD\" ping | grep -q PONG"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", "redis-cli -a \"$REDIS_PASSWORD\" ping | grep -q PONG"]
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
{{- with .Values.redis.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
{{- if not .Values.redis.persistence.enabled }}
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.redis.persistence.enabled }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
{{- if .Values.redis.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.redis.persistence.storageClass | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.redis.persistence.size }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,101 @@
|
||||
{{- if .Values.rustfs.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "pargar.rustfsHost" . }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: s3
|
||||
port: 9000
|
||||
targetPort: s3
|
||||
selector:
|
||||
{{- include "pargar.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "pargar.fullname" . }}-rustfs
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
spec:
|
||||
serviceName: {{ include "pargar.rustfsHost" . }}
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "pargar.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: rustfs
|
||||
spec:
|
||||
containers:
|
||||
- name: rustfs
|
||||
image: {{ .Values.rustfs.image }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
args: ["/data"]
|
||||
ports:
|
||||
- name: s3
|
||||
containerPort: 9000
|
||||
env:
|
||||
- name: RUSTFS_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.rustfsSecretName" . }}
|
||||
key: access-key
|
||||
- name: RUSTFS_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "pargar.rustfsSecretName" . }}
|
||||
key: secret-key
|
||||
- name: RUSTFS_ADDRESS
|
||||
value: ":9000"
|
||||
- name: RUSTFS_CONSOLE_ADDRESS
|
||||
value: ":9001"
|
||||
- name: RUSTFS_CONSOLE_ENABLE
|
||||
value: {{ .Values.rustfs.consoleEnable | ternary "true" "false" | quote }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: s3
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: s3
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
{{- with .Values.rustfs.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
{{- if not .Values.rustfs.persistence.enabled }}
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- if .Values.rustfs.persistence.enabled }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
{{- if .Values.rustfs.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.rustfs.persistence.storageClass | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.rustfs.persistence.size }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,56 @@
|
||||
{{- if and .Values.backend.enabled (not .Values.backend.existingSecret) }}
|
||||
{{- $name := include "pargar.appSecretName" . }}
|
||||
{{- $existing := lookup "v1" "Secret" .Release.Namespace $name }}
|
||||
{{- $jwt := "" }}
|
||||
{{- $admin := "" }}
|
||||
{{- $metrics := .Values.backend.secrets.metricsToken | default "" }}
|
||||
{{- if and $existing $existing.data }}
|
||||
{{- /* Reuse existing secrets so upgrades do not invalidate sessions / bootstrap. */}}
|
||||
{{- if index $existing.data "jwt-secret" }}
|
||||
{{- $jwt = index $existing.data "jwt-secret" | b64dec }}
|
||||
{{- end }}
|
||||
{{- if index $existing.data "admin-password" }}
|
||||
{{- $admin = index $existing.data "admin-password" | b64dec }}
|
||||
{{- end }}
|
||||
{{- if and (not $metrics) (index $existing.data "metrics-token") }}
|
||||
{{- $metrics = index $existing.data "metrics-token" | b64dec }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not $jwt }}
|
||||
{{- if and .Values.backend.secrets.jwtSecret (not (hasPrefix "change-me" .Values.backend.secrets.jwtSecret)) (ne .Values.backend.secrets.jwtSecret "") }}
|
||||
{{- $jwt = .Values.backend.secrets.jwtSecret }}
|
||||
{{- else }}
|
||||
{{- $jwt = randAlphaNum 48 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not $admin }}
|
||||
{{- if and .Values.backend.secrets.adminPassword (not (hasPrefix "change-me" .Values.backend.secrets.adminPassword)) (ne .Values.backend.secrets.adminPassword "") }}
|
||||
{{- $admin = .Values.backend.secrets.adminPassword }}
|
||||
{{- else }}
|
||||
{{- $admin = randAlphaNum 24 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
pargar.io/secret-source: generated-or-values
|
||||
type: Opaque
|
||||
stringData:
|
||||
jwt-secret: {{ $jwt | quote }}
|
||||
admin-password: {{ $admin | quote }}
|
||||
metrics-token: {{ $metrics | quote }}
|
||||
{{- if not .Values.postgres.enabled }}
|
||||
database-url: {{ required "external.databaseUrl required when postgres.enabled=false and existingSecret is empty" .Values.external.databaseUrl | quote }}
|
||||
{{- end }}
|
||||
{{- if not .Values.redis.enabled }}
|
||||
redis-url: {{ required "external.redisUrl required when redis.enabled=false and existingSecret is empty" .Values.external.redisUrl | quote }}
|
||||
{{- end }}
|
||||
{{- if not .Values.rustfs.enabled }}
|
||||
storage-access-key: {{ required "external.storageAccessKey required when rustfs.enabled=false and existingSecret is empty" .Values.external.storageAccessKey | quote }}
|
||||
storage-secret-key: {{ required "external.storageSecretKey required when rustfs.enabled=false and existingSecret is empty" .Values.external.storageSecretKey | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,71 @@
|
||||
{{- if and .Values.postgres.enabled (not .Values.postgres.existingSecret) }}
|
||||
{{- $pgName := include "pargar.postgresSecretName" . }}
|
||||
{{- $pgExisting := lookup "v1" "Secret" .Release.Namespace $pgName }}
|
||||
{{- $pgPass := "" }}
|
||||
{{- if and $pgExisting $pgExisting.data (index $pgExisting.data "postgres-password") }}
|
||||
{{- /* Never rotate an existing DB password — Postgres data dir is bound to it. */}}
|
||||
{{- $pgPass = index $pgExisting.data "postgres-password" | b64dec }}
|
||||
{{- else if and .Values.postgres.auth.password (not (hasPrefix "change-me" .Values.postgres.auth.password)) (ne .Values.postgres.auth.password "") }}
|
||||
{{- $pgPass = .Values.postgres.auth.password }}
|
||||
{{- else }}
|
||||
{{- $pgPass = randAlphaNum 32 }}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $pgName }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
postgres-password: {{ $pgPass | quote }}
|
||||
---
|
||||
{{- end }}
|
||||
{{- if and .Values.redis.enabled (not .Values.redis.existingSecret) }}
|
||||
{{- $rdName := include "pargar.redisSecretName" . }}
|
||||
{{- $rdExisting := lookup "v1" "Secret" .Release.Namespace $rdName }}
|
||||
{{- $rdPass := "" }}
|
||||
{{- if and $rdExisting $rdExisting.data (index $rdExisting.data "redis-password") }}
|
||||
{{- $rdPass = index $rdExisting.data "redis-password" | b64dec }}
|
||||
{{- else if and .Values.redis.auth.password (not (hasPrefix "change-me" .Values.redis.auth.password)) (ne .Values.redis.auth.password "") }}
|
||||
{{- $rdPass = .Values.redis.auth.password }}
|
||||
{{- else }}
|
||||
{{- $rdPass = randAlphaNum 32 }}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $rdName }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
redis-password: {{ $rdPass | quote }}
|
||||
---
|
||||
{{- end }}
|
||||
{{- if and .Values.rustfs.enabled (not .Values.rustfs.existingSecret) }}
|
||||
{{- $rsName := include "pargar.rustfsSecretName" . }}
|
||||
{{- $rsExisting := lookup "v1" "Secret" .Release.Namespace $rsName }}
|
||||
{{- $access := .Values.rustfs.auth.accessKey | default "pargar" }}
|
||||
{{- $secret := "" }}
|
||||
{{- if and $rsExisting $rsExisting.data (index $rsExisting.data "secret-key") }}
|
||||
{{- $secret = index $rsExisting.data "secret-key" | b64dec }}
|
||||
{{- if index $rsExisting.data "access-key" }}
|
||||
{{- $access = index $rsExisting.data "access-key" | b64dec }}
|
||||
{{- end }}
|
||||
{{- else if and .Values.rustfs.auth.secretKey (not (hasPrefix "change-me" .Values.rustfs.auth.secretKey)) (ne .Values.rustfs.auth.secretKey "") }}
|
||||
{{- $secret = .Values.rustfs.auth.secretKey }}
|
||||
{{- else }}
|
||||
{{- $secret = randAlphaNum 40 }}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $rsName }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
access-key: {{ $access | quote }}
|
||||
secret-key: {{ $secret | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "pargar.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled (not .Values.ingress.tls.existingSecret) (not .Values.certManager.enabled) }}
|
||||
{{- $secretName := include "pargar.tlsSecretName" . }}
|
||||
{{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName }}
|
||||
{{- $cn := .Values.global.domain }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $secretName }}
|
||||
labels:
|
||||
{{- include "pargar.labels" . | nindent 4 }}
|
||||
type: kubernetes.tls
|
||||
data:
|
||||
{{- if $existing }}
|
||||
tls.crt: {{ index $existing.data "tls.crt" }}
|
||||
tls.key: {{ index $existing.data "tls.key" }}
|
||||
{{- else }}
|
||||
{{- $cert := genSelfSignedCert $cn nil (list $cn) 3650 }}
|
||||
tls.crt: {{ $cert.Cert | b64enc }}
|
||||
tls.key: {{ $cert.Key | b64enc }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Local k3s profile — self-signed TLS, in-cluster data, single replicas.
|
||||
# helm upgrade --install pargar ./deploy/helm/pargar -n pargar --create-namespace -f deploy/helm/pargar/values-k3s.yaml
|
||||
#
|
||||
# After install, add to /etc/hosts: <NODE_IP> pargar.local
|
||||
|
||||
global:
|
||||
domain: pargar.local
|
||||
publicUrl: https://pargar.local
|
||||
frontendOrigins: "https://pargar.local"
|
||||
|
||||
backend:
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: pargar-backend
|
||||
tag: local
|
||||
pullPolicy: IfNotPresent
|
||||
env:
|
||||
seedCurriculum: "true"
|
||||
|
||||
frontend:
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: pargar-frontend
|
||||
tag: local
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
postgres:
|
||||
enabled: true
|
||||
persistence:
|
||||
size: 5Gi
|
||||
|
||||
redis:
|
||||
enabled: true
|
||||
persistence:
|
||||
size: 1Gi
|
||||
|
||||
rustfs:
|
||||
enabled: true
|
||||
persistence:
|
||||
size: 10Gi
|
||||
consoleEnable: false
|
||||
|
||||
backup:
|
||||
enabled: true
|
||||
schedule: "0 3 * * *"
|
||||
retentionDays: 7
|
||||
persistence:
|
||||
size: 2Gi
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
useIngressRoute: true
|
||||
tls:
|
||||
enabled: true
|
||||
existingSecret: "" # chart generates self-signed for pargar.local
|
||||
@@ -0,0 +1,103 @@
|
||||
# Production profile — external data plane, registry images, HPA/PDB/NetworkPolicy, cert-manager.
|
||||
# helm upgrade --install pargar ./deploy/helm/pargar -n pargar --create-namespace \
|
||||
# -f deploy/helm/pargar/values-prod.yaml
|
||||
#
|
||||
# Prerequisites:
|
||||
# - images pushed to ghcr.io/pargar/{backend,frontend}:<tag>
|
||||
# - Secret pargar-app (jwt-secret, admin-password, metrics-token, database-url, redis-url,
|
||||
# storage-access-key, storage-secret-key)
|
||||
# - cert-manager ClusterIssuer letsencrypt-prod
|
||||
# - imagePullSecrets if the registry is private
|
||||
|
||||
global:
|
||||
domain: app.example.com
|
||||
publicUrl: https://app.example.com
|
||||
frontendOrigins: "https://app.example.com"
|
||||
imageRegistry: ghcr.io/pargar
|
||||
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
|
||||
backend:
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: backend
|
||||
tag: "0.1.0"
|
||||
pullPolicy: IfNotPresent
|
||||
existingSecret: pargar-app
|
||||
env:
|
||||
devMode: "false"
|
||||
seedCurriculum: "false"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 1Gi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 8
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
|
||||
frontend:
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: frontend
|
||||
tag: "0.1.0"
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 1Gi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 8
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
|
||||
# Prefer managed Postgres / Redis / object storage in real prod.
|
||||
postgres:
|
||||
enabled: false
|
||||
redis:
|
||||
enabled: false
|
||||
rustfs:
|
||||
enabled: false
|
||||
|
||||
external:
|
||||
databaseUrl: "" # prefer secret key database-url on pargar-app
|
||||
redisUrl: ""
|
||||
storageEndpoint: "https://s3.example.com"
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
useIngressRoute: true
|
||||
tls:
|
||||
enabled: true
|
||||
existingSecret: "" # Certificate CR owns the secret below
|
||||
clusterIssuer: letsencrypt-prod
|
||||
|
||||
certManager:
|
||||
enabled: true
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
ingressNamespaces:
|
||||
- kube-system
|
||||
ingressPodLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
@@ -0,0 +1,229 @@
|
||||
# Default values — safe for local k3s. Override with values-k3s.yaml / values-prod.yaml.
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
global:
|
||||
# Public hostname (Ingress / TLS / PUBLIC_URL)
|
||||
domain: pargar.local
|
||||
# Full public origin browsers use (must match Ingress TLS host)
|
||||
publicUrl: https://pargar.local
|
||||
frontendOrigins: "https://pargar.local"
|
||||
# Docker/k8s pod+service CIDRs — Traefik peer is trusted for X-Forwarded-*
|
||||
trustedProxyCIDRs: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8,::1/128"
|
||||
# Optional registry prefix, e.g. ghcr.io/pargar → images become ghcr.io/pargar/<repository>:<tag>
|
||||
imageRegistry: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
backend:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: pargar-backend
|
||||
tag: local
|
||||
pullPolicy: IfNotPresent
|
||||
service:
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 6
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
minAvailable: 1
|
||||
env:
|
||||
devMode: "false"
|
||||
seedCurriculum: "false"
|
||||
donationEnabled: "true"
|
||||
# Offline payment only — leave off until ops process is ready; enable in admin settings.
|
||||
physicalCertEnabled: "false"
|
||||
physicalCertPriceIrr: "2500000"
|
||||
physicalCertWindowDays: "30"
|
||||
otelEndpoint: ""
|
||||
otelServiceName: pargar-api
|
||||
# Created only when admin user is missing (see seed.ensureBootstrapAdmin)
|
||||
admin:
|
||||
name: Admin
|
||||
username: admin
|
||||
email: admin@example.com
|
||||
# Prefer existingSecret in production. When empty, chart generate-once (lookup + rand).
|
||||
existingSecret: ""
|
||||
# Optional overrides; leave empty to auto-generate strong values (placeholders like change-me-* are ignored).
|
||||
secrets:
|
||||
jwtSecret: ""
|
||||
adminPassword: ""
|
||||
metricsToken: ""
|
||||
|
||||
frontend:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: pargar-frontend
|
||||
tag: local
|
||||
pullPolicy: IfNotPresent
|
||||
service:
|
||||
port: 3000
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 6
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
minAvailable: 1
|
||||
|
||||
# In-cluster data plane (k3s / small clusters). Disable and point to managed services in prod.
|
||||
postgres:
|
||||
enabled: true
|
||||
image: postgres:16.4-alpine
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
storageClass: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
auth:
|
||||
username: pargar
|
||||
database: pargar
|
||||
# Empty → chart generate-once; placeholders change-me-* are ignored.
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
|
||||
redis:
|
||||
enabled: true
|
||||
image: redis@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 2Gi
|
||||
storageClass: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
auth:
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
|
||||
rustfs:
|
||||
enabled: true
|
||||
image: rustfs/rustfs@sha256:1803faef57627e2d9c2e7d89d655d712ddded5389040054987163043fecb6a3c
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 20Gi
|
||||
storageClass: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
auth:
|
||||
accessKey: "pargar-rustfs"
|
||||
secretKey: ""
|
||||
bucket: pargar
|
||||
region: us-east-1
|
||||
consoleEnable: false
|
||||
existingSecret: ""
|
||||
|
||||
# Postgres dump CronJob (in-cluster postgres only).
|
||||
backup:
|
||||
enabled: false
|
||||
schedule: "0 3 * * *"
|
||||
retentionDays: 7
|
||||
persistence:
|
||||
size: 5Gi
|
||||
storageClass: ""
|
||||
image: postgres:16.4-alpine
|
||||
|
||||
# External endpoints when in-cluster postgres/redis/rustfs are disabled.
|
||||
# Prefer putting these in backend.existingSecret in production.
|
||||
external:
|
||||
databaseUrl: ""
|
||||
redisUrl: ""
|
||||
storageEndpoint: ""
|
||||
storageAccessKey: ""
|
||||
storageSecretKey: ""
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
# k3s ships Traefik; use IngressRoute CRDs (recommended) or standard Ingress
|
||||
className: traefik
|
||||
# Use Traefik IngressRoute + Middleware CRDs (true) vs networking.k8s.io/Ingress (false)
|
||||
useIngressRoute: true
|
||||
annotations: {}
|
||||
tls:
|
||||
enabled: true
|
||||
# When empty and certManager.enabled=false, chart creates a self-signed Secret for k3s/dev.
|
||||
# Prefer certManager.enabled=true in prod, or set existingSecret to a pre-created TLS secret.
|
||||
existingSecret: ""
|
||||
# cert-manager ClusterIssuer name (annotation helper when useIngressRoute=false)
|
||||
clusterIssuer: ""
|
||||
|
||||
# cert-manager Certificate CR (writes TLS secret used by IngressRoute)
|
||||
certManager:
|
||||
enabled: false
|
||||
duration: 2160h
|
||||
renewBefore: 360h
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
group: ""
|
||||
extraDnsNames: []
|
||||
|
||||
# Restrict pod traffic (off for local k3s; on in values-prod.yaml)
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
# Namespaces whose pods may reach frontend/backend (Traefik)
|
||||
ingressNamespaces:
|
||||
- kube-system
|
||||
ingressPodLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
|
||||
middlewares:
|
||||
securityHeaders: true
|
||||
compress: true
|
||||
redirectHttps: true
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: ""
|
||||
annotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 10001
|
||||
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
Reference in New Issue
Block a user