From 1fbfeb60391126ab57176f6d0442f3faf33eaadd Mon Sep 17 00:00:00 2001 From: FYWinds Date: Wed, 12 Aug 2026 13:27:21 -0400 Subject: [PATCH] feat: helm chart for the server deployment Replaces the kustomize base/overlays as the single deployment shape. Cluster-specific values (storage class, snapshot class, image tag, sentry env, replica count) are supplied by the deploying repository. Notable differences from the kustomize rendering, all intended: - wynnsource-config ConfigMap is rendered by the chart (config values moved out of the cluster repo); a checksum annotation now rolls the deployment on config changes - CNPG Cluster and Redis carry Prune=false,Delete=false so deleting the Application can never cascade into the data - dropped the dead explicit WCS_SENTRY_DSN env (key 'sentry-dsn' never existed in the secret; envFrom provides the real value) deploy/base and deploy/overlays stay untouched until the cluster repo has switched over. --- deploy/chart/Chart.yaml | 6 ++ deploy/chart/templates/configmap.yaml | 10 +++ deploy/chart/templates/deployment.yaml | 82 +++++++++++++++++++++++ deploy/chart/templates/migration-job.yaml | 46 +++++++++++++ deploy/chart/templates/postgres.yaml | 45 +++++++++++++ deploy/chart/templates/redis.yaml | 23 +++++++ deploy/chart/templates/service.yaml | 17 +++++ deploy/chart/values.yaml | 64 ++++++++++++++++++ 8 files changed, 293 insertions(+) create mode 100644 deploy/chart/Chart.yaml create mode 100644 deploy/chart/templates/configmap.yaml create mode 100644 deploy/chart/templates/deployment.yaml create mode 100644 deploy/chart/templates/migration-job.yaml create mode 100644 deploy/chart/templates/postgres.yaml create mode 100644 deploy/chart/templates/redis.yaml create mode 100644 deploy/chart/templates/service.yaml create mode 100644 deploy/chart/values.yaml diff --git a/deploy/chart/Chart.yaml b/deploy/chart/Chart.yaml new file mode 100644 index 0000000..40f4592 --- /dev/null +++ b/deploy/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: wynnsource-server +description: WynnSource server with its PostgreSQL and Redis dependencies +type: application +version: 0.1.0 +appVersion: "0.1.5" diff --git a/deploy/chart/templates/configmap.yaml b/deploy/chart/templates/configmap.yaml new file mode 100644 index 0000000..0bc3fba --- /dev/null +++ b/deploy/chart/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: wynnsource-config + annotations: + argocd.argoproj.io/sync-wave: "0" +data: +{{- range $key, $value := .Values.config }} + {{ $key }}: {{ $value | quote }} +{{- end }} diff --git a/deploy/chart/templates/deployment.yaml b/deploy/chart/templates/deployment.yaml new file mode 100644 index 0000000..860c794 --- /dev/null +++ b/deploy/chart/templates/deployment.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wynnsource-server + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + replicas: {{ .Values.replicaCount }} + selector: + # Immutable on the pre-chart deployment; never change this. + matchLabels: + app: wynnsource-server + strategy: + type: RollingUpdate + template: + metadata: + labels: + app: wynnsource-server + annotations: + checksum/config: {{ .Values.config | toYaml | sha256sum }} + spec: + securityContext: + runAsNonRoot: true + containers: + - name: server + image: "{{ .Values.image.repository }}:{{ required "image.tag is required" .Values.image.tag }}" + ports: + - containerPort: 8000 + name: http + envFrom: + - configMapRef: + name: wynnsource-config + - secretRef: + name: {{ .Values.existingSecret }} + env: + - name: POSTGRES_HOST + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: host + - name: POSTGRES_PORT + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: port + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: password + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: dbname + - name: REDIS_HOST + value: wynnsource-redis + - name: REDIS_PORT + value: "6379" + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + httpGet: { path: /healthz, port: 8000 } + initialDelaySeconds: 10 + periodSeconds: 15 + readinessProbe: + httpGet: { path: /readyz, port: 8000 } + initialDelaySeconds: 5 + periodSeconds: 10 + startupProbe: + httpGet: { path: /healthz, port: 8000 } + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} diff --git a/deploy/chart/templates/migration-job.yaml b/deploy/chart/templates/migration-job.yaml new file mode 100644 index 0000000..3a64532 --- /dev/null +++ b/deploy/chart/templates/migration-job.yaml @@ -0,0 +1,46 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: wynnsource-migration + annotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + argocd.argoproj.io/sync-wave: "1" +spec: + backoffLimit: 3 + template: + spec: + containers: + - name: migration + image: "{{ .Values.image.repository }}:{{ required "image.tag is required" .Values.image.tag }}" + command: ["uv", "run", "--no-dev", "alembic", "upgrade", "head"] + envFrom: + - secretRef: + name: {{ .Values.existingSecret }} + env: + - name: POSTGRES_HOST + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: host + - name: POSTGRES_PORT + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: port + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: password + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: wynnsource-pg-app + key: dbname + restartPolicy: OnFailure diff --git a/deploy/chart/templates/postgres.yaml b/deploy/chart/templates/postgres.yaml new file mode 100644 index 0000000..66d61db --- /dev/null +++ b/deploy/chart/templates/postgres.yaml @@ -0,0 +1,45 @@ +{{- if .Values.postgres.enabled }} +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: wynnsource-pg + annotations: + argocd.argoproj.io/sync-wave: "0" + # Data outlives the app: ArgoCD must neither prune this nor cascade-delete + # it when the Application is removed. + argocd.argoproj.io/sync-options: Prune=false,Delete=false +spec: + instances: 1 + resources: + {{- toYaml .Values.postgres.resources | nindent 4 }} + storage: + size: {{ .Values.postgres.storageSize }} + {{- with .Values.postgres.storageClass }} + storageClass: {{ . }} + {{- end }} + postgresql: + parameters: + max_connections: {{ .Values.postgres.maxConnections | quote }} + bootstrap: + initdb: + database: {{ .Values.postgres.database }} + owner: {{ .Values.postgres.owner }} + {{- if .Values.postgres.backup.enabled }} + backup: + volumeSnapshot: + className: {{ required "postgres.backup.volumeSnapshotClassName is required when backup is enabled" .Values.postgres.backup.volumeSnapshotClassName }} + {{- end }} +{{- if .Values.postgres.backup.enabled }} +--- +apiVersion: postgresql.cnpg.io/v1 +kind: ScheduledBackup +metadata: + name: wynnsource-pg-daily +spec: + schedule: {{ .Values.postgres.backup.schedule | quote }} + backupOwnerReference: self + cluster: + name: wynnsource-pg + method: volumeSnapshot +{{- end }} +{{- end }} diff --git a/deploy/chart/templates/redis.yaml b/deploy/chart/templates/redis.yaml new file mode 100644 index 0000000..c41a8ef --- /dev/null +++ b/deploy/chart/templates/redis.yaml @@ -0,0 +1,23 @@ +{{- if .Values.redis.enabled }} +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: wynnsource-redis + annotations: + argocd.argoproj.io/sync-wave: "0" + # Cache state is cheap but the CR owns a PVC; keep it out of prune and + # cascade deletion, consistent with the postgres cluster. + argocd.argoproj.io/sync-options: Prune=false,Delete=false +spec: + kubernetesConfig: + image: {{ .Values.redis.image }} + resources: + {{- toYaml .Values.redis.resources | nindent 6 }} + storage: + volumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.redis.storageSize }} +{{- end }} diff --git a/deploy/chart/templates/service.yaml b/deploy/chart/templates/service.yaml new file mode 100644 index 0000000..7468ab9 --- /dev/null +++ b/deploy/chart/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: wynnsource-server + annotations: + argocd.argoproj.io/sync-wave: "2" + labels: + app: wynnsource-server +spec: + type: ClusterIP + selector: + app: wynnsource-server + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http diff --git a/deploy/chart/values.yaml b/deploy/chart/values.yaml new file mode 100644 index 0000000..16c713d --- /dev/null +++ b/deploy/chart/values.yaml @@ -0,0 +1,64 @@ +# Safe defaults only. Anything cluster-specific (domains, storage classes, +# snapshot classes, image tags) is supplied by the deploying repository. + +image: + repository: ghcr.io/wynnsource/wynnsource-server + # Required. Set by the deploying repo; kept current by argocd-image-updater. + tag: "" + +replicaCount: 1 + +# Rendered into the wynnsource-config ConfigMap, consumed via envFrom. +# A change here rolls the deployment (checksum annotation). +config: + LEVEL: INFO + MAX_IP_RECORDS: "10" + +# Extra environment variables appended to the server container +# (e.g. sentry environment / sample rates / version suffix). +extraEnv: [] + +# Secret consumed via envFrom; managed outside the chart (SealedSecret etc.). +existingSecret: wynnsource-secrets + +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1024Mi + +service: + port: 8000 + +postgres: + enabled: true + database: wcs_db + owner: wynnsource + storageSize: 5Gi + # Empty = cluster default StorageClass. + storageClass: "" + maxConnections: "100" + resources: + requests: + cpu: 50m + memory: 256Mi + backup: + enabled: false + # VolumeSnapshotClass name provided by the cluster. + volumeSnapshotClassName: "" + # Six-field cron; CNPG counts seconds first. + schedule: "0 15 3 * * *" + +redis: + enabled: true + image: redis:7-alpine + storageSize: 1Gi + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi