diff --git a/README.md b/README.md index 3996b20..e3c31b4 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,40 @@ Then run: docker compose up -d ``` +### Option 4: Kubernetes with Helm + +For Kubernetes deployments, use the official Helm chart: + +```bash +helm install postgresus ./deploy/postgresus -n postgresus --create-namespace +``` + +To customize the installation, create a `values.yaml` file: + +```yaml +ingress: + hosts: + - host: backup.yourdomain.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: backup-yourdomain-com-tls + hosts: + - backup.yourdomain.com + +persistence: + size: 20Gi +``` + +Then install with your custom values: + +```bash +helm install postgresus ./deploy/postgresus -n postgresus --create-namespace -f values.yaml +``` + +See the [Helm chart README](deploy/postgresus/README.md) for all configuration options. + --- ## 🚀 Usage diff --git a/deploy/postgresus/.helmignore b/deploy/postgresus/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deploy/postgresus/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/postgresus/Chart.yaml b/deploy/postgresus/Chart.yaml new file mode 100644 index 0000000..4fdf0b5 --- /dev/null +++ b/deploy/postgresus/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: postgresus +description: A Helm chart for Postgresus - PostgreSQL backup and management system +type: application +version: 1.0.0 +appVersion: "v1.45.3" +keywords: + - postgresql + - backup + - database + - restore +home: https://github.com/RostislavDugin/postgresus diff --git a/deploy/postgresus/README.md b/deploy/postgresus/README.md new file mode 100644 index 0000000..2dd9c7d --- /dev/null +++ b/deploy/postgresus/README.md @@ -0,0 +1,84 @@ +# Postgresus Helm Chart + +## Installation + +```bash +helm install postgresus ./deploy/postgresus -n postgresus --create-namespace +``` + +## Configuration + +### Main Parameters + +| Parameter | Description | Default Value | +| ------------------ | ------------------ | --------------------------- | +| `namespace.create` | Create namespace | `true` | +| `namespace.name` | Namespace name | `postgresus` | +| `image.repository` | Docker image | `rostislavdugin/postgresus` | +| `image.tag` | Image tag | `latest` | +| `image.pullPolicy` | Image pull policy | `Always` | +| `replicaCount` | Number of replicas | `1` | + +### Resources + +| Parameter | Description | Default Value | +| --------------------------- | -------------- | ------------- | +| `resources.requests.memory` | Memory request | `1Gi` | +| `resources.requests.cpu` | CPU request | `500m` | +| `resources.limits.memory` | Memory limit | `1Gi` | +| `resources.limits.cpu` | CPU limit | `500m` | + +### Storage + +| Parameter | Description | Default Value | +| ------------------------------ | ------------------------- | ---------------------- | +| `persistence.enabled` | Enable persistent storage | `true` | +| `persistence.storageClassName` | Storage class | `""` (cluster default) | +| `persistence.accessMode` | Access mode | `ReadWriteOnce` | +| `persistence.size` | Storage size | `10Gi` | +| `persistence.mountPath` | Mount path | `/postgresus-data` | + +### Service + +| Parameter | Description | Default Value | +| -------------------------- | ----------------------- | ------------- | +| `service.type` | Service type | `ClusterIP` | +| `service.port` | Service port | `4005` | +| `service.targetPort` | Target port | `4005` | +| `service.headless.enabled` | Enable headless service | `true` | + +### Ingress + +| Parameter | Description | Default Value | +| ----------------------- | ----------------- | ------------------------ | +| `ingress.enabled` | Enable Ingress | `true` | +| `ingress.className` | Ingress class | `nginx` | +| `ingress.hosts[0].host` | Hostname | `postgresus.example.com` | +| `ingress.tls` | TLS configuration | See values.yaml | + +### Health Checks + +| Parameter | Description | Default Value | +| ------------------------ | ---------------------- | ------------- | +| `livenessProbe.enabled` | Enable liveness probe | `true` | +| `readinessProbe.enabled` | Enable readiness probe | `true` | + +## Custom Ingress Example + +```yaml +# custom-values.yaml +ingress: + hosts: + - host: backup.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: backup-example-com-tls + hosts: + - backup.example.com +``` + +```bash +helm install postgresus ./deploy/postgresus -n postgresus --create-namespace -f custom-values.yaml +``` diff --git a/deploy/postgresus/helm/_helpers.tpl b/deploy/postgresus/helm/_helpers.tpl new file mode 100644 index 0000000..6502d49 --- /dev/null +++ b/deploy/postgresus/helm/_helpers.tpl @@ -0,0 +1,72 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "postgresus.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "postgresus.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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "postgresus.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "postgresus.labels" -}} +helm.sh/chart: {{ include "postgresus.chart" . }} +{{ include "postgresus.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "postgresus.selectorLabels" -}} +app.kubernetes.io/name: {{ include "postgresus.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: postgresus +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "postgresus.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "postgresus.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Namespace +*/}} +{{- define "postgresus.namespace" -}} +{{- if .Values.namespace.create }} +{{- .Values.namespace.name }} +{{- else }} +{{- .Release.Namespace }} +{{- end }} +{{- end }} diff --git a/deploy/postgresus/helm/ingress.yaml b/deploy/postgresus/helm/ingress.yaml new file mode 100644 index 0000000..8d22b19 --- /dev/null +++ b/deploy/postgresus/helm/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "postgresus.fullname" . }}-ingress + namespace: {{ include "postgresus.namespace" . }} + labels: + {{- include "postgresus.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "postgresus.fullname" $ }}-service + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deploy/postgresus/helm/namespace.yaml b/deploy/postgresus/helm/namespace.yaml new file mode 100644 index 0000000..7377128 --- /dev/null +++ b/deploy/postgresus/helm/namespace.yaml @@ -0,0 +1,8 @@ +{{- if .Values.namespace.create }} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.namespace.name }} + labels: + {{- include "postgresus.labels" . | nindent 4 }} +{{- end }} diff --git a/deploy/postgresus/helm/service.yaml b/deploy/postgresus/helm/service.yaml new file mode 100644 index 0000000..f252b04 --- /dev/null +++ b/deploy/postgresus/helm/service.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "postgresus.fullname" . }}-service + namespace: {{ include "postgresus.namespace" . }} + labels: + {{- include "postgresus.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "postgresus.selectorLabels" . | nindent 4 }} +--- +{{- if .Values.service.headless.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "postgresus.fullname" . }}-headless + namespace: {{ include "postgresus.namespace" . }} + labels: + {{- include "postgresus.labels" . | nindent 4 }} +spec: + type: ClusterIP + clusterIP: None + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "postgresus.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/deploy/postgresus/helm/statefulset.yaml b/deploy/postgresus/helm/statefulset.yaml new file mode 100644 index 0000000..c5f982a --- /dev/null +++ b/deploy/postgresus/helm/statefulset.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "postgresus.fullname" . }} + namespace: {{ include "postgresus.namespace" . }} + labels: + {{- include "postgresus.labels" . | nindent 4 }} +spec: + serviceName: {{ include "postgresus.fullname" . }}-headless + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "postgresus.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "postgresus.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + volumeMounts: + - name: postgresus-storage + mountPath: {{ .Values.persistence.mountPath }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + {{- toYaml .Values.livenessProbe.httpGet | nindent 12 }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + {{- toYaml .Values.readinessProbe.httpGet | nindent 12 }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: postgresus-storage + spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClassName }} + storageClassName: {{ .Values.persistence.storageClassName }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- end }} + updateStrategy: + {{- toYaml .Values.updateStrategy | nindent 4 }} diff --git a/deploy/postgresus/values.yaml b/deploy/postgresus/values.yaml new file mode 100644 index 0000000..ce47067 --- /dev/null +++ b/deploy/postgresus/values.yaml @@ -0,0 +1,111 @@ +# Default values for postgresus + +# Namespace configuration +namespace: + create: true + name: postgresus + +# Image configuration +image: + repository: rostislavdugin/postgresus + tag: latest + pullPolicy: Always + +# StatefulSet configuration +replicaCount: 1 + +# Service configuration +service: + type: ClusterIP + port: 4005 + targetPort: 4005 + # Headless service for StatefulSet + headless: + enabled: true + +# Resource limits and requests +resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "500m" + +# Persistent storage configuration +persistence: + enabled: true + # Storage class name. Leave empty to use cluster default. + # Examples: "longhorn", "standard", "gp2", etc. + storageClassName: "" + accessMode: ReadWriteOnce + size: 10Gi + # Mount path in container + mountPath: /postgresus-data + +# Ingress configuration +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/use-http2: "true" + # Gzip settings + nginx.ingress.kubernetes.io/enable-gzip: "true" + nginx.ingress.kubernetes.io/gzip-types: "text/plain text/css application/json application/javascript text/javascript text/xml application/xml application/xml+rss image/svg+xml" + nginx.ingress.kubernetes.io/gzip-min-length: "1000" + nginx.ingress.kubernetes.io/gzip-level: "9" + nginx.ingress.kubernetes.io/gzip-buffers: "16 8k" + nginx.ingress.kubernetes.io/gzip-http-version: "1.1" + nginx.ingress.kubernetes.io/gzip-vary: "on" + # Cert-manager settings + cert-manager.io/cluster-issuer: "clusteriissuer-letsencrypt" + cert-manager.io/duration: "2160h" + cert-manager.io/renew-before: "360h" + hosts: + - host: postgresus.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: postgresus.example.com-tls + hosts: + - postgresus.example.com + +# Health checks configuration +# Note: The application only has /api/v1/system/health endpoint +livenessProbe: + enabled: true + httpGet: + path: /api/v1/system/health + port: 4005 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +readinessProbe: + enabled: true + httpGet: + path: /api/v1/system/health + port: 4005 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + +# StatefulSet update strategy +updateStrategy: + type: RollingUpdate + rollingUpdate: + partition: 0 + +# Pod labels and annotations +podLabels: {} +podAnnotations: {} + +# Node selector, tolerations and affinity +nodeSelector: {} +tolerations: [] +affinity: {}