diff --git a/self-hosting/helm/.gitignore b/self-hosting/helm/.gitignore new file mode 100644 index 000000000..76d97f39e --- /dev/null +++ b/self-hosting/helm/.gitignore @@ -0,0 +1,26 @@ +# Helm chart values override files +values-*.yaml +custom-*.yaml +*.values.yaml + +# Helm package files +*.tgz + +# Temporary files +*.tmp +*.bak +*.swp + +# IDE files +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Secret files +secrets/ +*.secret +*.key +*.pem diff --git a/self-hosting/helm/Chart.yaml b/self-hosting/helm/Chart.yaml new file mode 100644 index 000000000..e6ee362ef --- /dev/null +++ b/self-hosting/helm/Chart.yaml @@ -0,0 +1,17 @@ +name: svix +description: A Helm chart for Svix webhook service +apiVersion: v2 +version: 0.1.0 +keywords: + - webhooks + - svix + - api + - kubernetes +home: https://www.svix.com +sources: + - https://github.com/svix/svix-webhooks +maintainers: + - name: Svix Team + email: contact@svix.com +icon: https://avatars.githubusercontent.com/u/80175132?s=200&v=4 +appVersion: "1.0.0" diff --git a/self-hosting/helm/README.md b/self-hosting/helm/README.md new file mode 100644 index 000000000..1df14fc8c --- /dev/null +++ b/self-hosting/helm/README.md @@ -0,0 +1,226 @@ +# Svix Helm Chart + +This Helm chart deploys the complete Svix webhook service stack on Kubernetes, including: + +- **Svix Backend**: The main webhook service +- **PostgreSQL**: Database for storing webhook data +- **PgBouncer**: Connection pooler for PostgreSQL +- **Redis**: Cache and session storage + +## Prerequisites + +Before installing the Svix Helm chart, ensure you have the following components: + +- **Kubernetes Cluster**: A running Kubernetes cluster (version 1.19 or later) +- **kubectl**: Kubernetes command-line tool installed and configured to communicate with your cluster +- **Helm**: Helm package manager installed (version 3.x) +- **Persistent Storage**: Access to a storage class for provisioning PersistentVolumeClaims +- **Ingress Controller**: (Optional but recommended) An ingress controller like NGINX for external access + +## Installation + +### 1. Clone the Repository + +Since this is not an official Helm chart, you'll need to clone the Svix repository: + +```bash +git clone https://github.com/svix/svix-webhooks.git +cd svix-webhooks/self-hosting/helm +``` + +### 2. Install the Chart + +```bash +# Install with default values +helm install my-svix . --namespace default --create-namespace + +# Install with custom values +helm install my-svix . -f custom-values.yaml --namespace default --create-namespace + +# Install in a specific namespace +kubectl create namespace svix +helm install my-svix . --namespace svix +``` + +### 3. Access the Service + +```bash +# Port forward to access the service +kubectl port-forward svc/my-svix-backend 8071:8071 --namespace default + +# Access the Service at http://localhost:8071 +``` + + +## Production Deployment + +For production deployments, consider the following: + +### 1. Set Strong Secrets + +```yaml +backend: + secrets: + jwtSecret: "your-production-jwt-secret-here" + mainSecret: "your-production-main-secret-here" +``` + +### 2. Configure Ingress with TLS + +```yaml +ingress: + enabled: true + className: "nginx" + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: svix.yourdomain.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: svix-tls + hosts: + - svix.yourdomain.com +``` + +### 3. Enable Autoscaling + +```yaml +backend: + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 +``` + +### 4. Configure Resource Limits + +```yaml +backend: + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" +``` + +### 5. Use External Databases (Optional) + +If you want to use external PostgreSQL and Redis instances: + +```yaml +postgres: + enabled: false + +redis: + enabled: false + +backend: + secrets: + dbDsn: "postgresql://user:password@your-external-postgresql-host:5432/database" + redisDsn: "redis://your-external-redis-host:6379" +``` + +### 6. Configure Persistence + +```yaml +postgres: + persistence: + enabled: true + storageClass: "fast-ssd" + size: "50Gi" + +redis: + persistence: + enabled: true + storageClass: "fast-ssd" + size: "20Gi" +``` + +## Monitoring and Health Checks + +### Health Check Endpoints + +The Svix backend provides health check endpoints: + +```bash +# Check Svix health endpoint +kubectl exec -it deployment/my-svix-backend -- curl http://localhost:8071/api/v1/health + +# Expected response: {"status":"ok"} +``` + +``` + +## Security Considerations + +1. **JWT Secret**: Always use a cryptographically secure JWT secret (minimum 64 characters) +2. **Database Passwords**: Use strong, unique passwords for database access +3. **Network Policies**: Implement network policies to restrict pod-to-pod communication: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: svix-network-policy +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: svix + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + name: ingress-nginx +``` + +4. **RBAC**: Review and adjust service account permissions as needed +5. **Secrets Management**: Use external secret management systems like: + - AWS Secrets Manager + - HashiCorp Vault + - Azure Key Vault + - Google Secret Manager + +6. **Image Security**: Use specific image tags instead of `latest` and scan images for vulnerabilities + + +## Contributing + +We welcome contributions to improve this Helm chart! Please: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests if applicable +5. Submit a pull request + + +# Validate against Kubernetes +``` +helm template my-svix . | kubectl apply --dry-run=client -f - +``` + +## License + +This Helm chart is released under the MIT License. See the [LICENSE](LICENSE) file for details. + +The Svix software itself is licensed separately. Please refer to the [Svix repository](https://github.com/svix/svix-webhooks) for licensing information. + +## Support + +For issues and questions: + +- **Documentation**: Check the [Svix documentation](https://docs.svix.com/) +- **GitHub Issues**: Report bugs in the [GitHub repository](https://github.com/svix/svix-webhooks/issues) +- **Community**: Join the [Svix Slack community](https://www.svix.com/slack/) +- **Support**: Contact Svix support at support@svix.com + diff --git a/self-hosting/helm/templates/NOTES.txt b/self-hosting/helm/templates/NOTES.txt new file mode 100644 index 000000000..4c490e2ba --- /dev/null +++ b/self-hosting/helm/templates/NOTES.txt @@ -0,0 +1,37 @@ +1. Get the application URL by running these commands: +{{- if contains "NodePort" .Values.backend.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "svix.fullname" . }}-{{ .Values.backend.name }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.backend.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "svix.fullname" . }}-{{ .Values.backend.name }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "svix.fullname" . }}-{{ .Values.backend.name }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.backend.service.port | default 8071 }} +{{- else if contains "ClusterIP" .Values.backend.service.type }} + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "svix.fullname" . }}-{{ .Values.backend.name }} {{ .Values.backend.service.port | default 8071 }}:{{ .Values.backend.service.port | default 8071 }} + echo "Visit http://127.0.0.1:{{ .Values.backend.service.port | default 8071 }} to use your application" +{{- end }} + +2. Get the Svix API token: + kubectl get secret --namespace {{ .Release.Namespace }} {{ include "svix.fullname" . }}-jwt-secret -o jsonpath="{.data.jwt-secret}" | base64 -d + +3. Check the status of your deployment: + kubectl get pods --namespace {{ .Release.Namespace }} -l app.kubernetes.io/component=backend + +4. View the logs: + kubectl logs --namespace {{ .Release.Namespace }} -l app.kubernetes.io/component=backend + +5. Access the database: + kubectl exec --namespace {{ .Release.Namespace }} -it deployment/{{ include "svix.fullname" . }}-{{ .Values.postgres.name }} -- psql -U postgres + +6. Access Redis: + kubectl exec --namespace {{ .Release.Namespace }} -it deployment/{{ include "svix.fullname" . }}-{{ .Values.redis.name }} -- redis-cli + +7. PostgreSQL is running on: + kubectl get svc --namespace {{ .Release.Namespace }} {{ include "svix.fullname" . }}-{{ .Values.postgres.name }} + +8. Redis is running on: + kubectl get svc --namespace {{ .Release.Namespace }} {{ include "svix.fullname" . }}-{{ .Values.redis.name }} + +For more information, visit: https://docs.svix.com/ \ No newline at end of file diff --git a/self-hosting/helm/templates/_helpers.tpl b/self-hosting/helm/templates/_helpers.tpl new file mode 100644 index 000000000..dc8968bcf --- /dev/null +++ b/self-hosting/helm/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "svix.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "svix.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 "svix.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "svix.labels" -}} +helm.sh/chart: {{ include "svix.chart" . }} +{{ include "svix.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "svix.selectorLabels" -}} +app.kubernetes.io/name: {{ include "svix.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/self-hosting/helm/templates/backend-deployment.yaml b/self-hosting/helm/templates/backend-deployment.yaml new file mode 100644 index 000000000..fd2ef5cde --- /dev/null +++ b/self-hosting/helm/templates/backend-deployment.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.backend.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + {{- if not .Values.backend.autoscaling.enabled }} + replicas: {{ .Values.backend.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "svix.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: backend + template: + metadata: + labels: + {{- include "svix.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + spec: + initContainers: + - name: wait-for-pgbouncer + image: postgres:13.4 + command: ['sh', '-c', 'until pg_isready -h {{ include "svix.fullname" . }}-{{ .Values.pgbouncer.name }} -p 5432 -U postgres; do echo waiting for pgbouncer; sleep 2; done; echo pgbouncer is ready'] + - name: wait-for-redis + image: redis:7-alpine + command: ['sh', '-c', 'until redis-cli -h {{ include "svix.fullname" . }}-{{ .Values.redis.name }} ping; do echo waiting for redis; sleep 2; done; echo redis is ready'] + containers: + - name: {{ .Values.backend.name }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: {{ .Values.backend.healthcheck.port }} + protocol: TCP + livenessProbe: + httpGet: + path: {{ .Values.backend.healthcheck.path }} + port: {{ .Values.backend.healthcheck.port }} + initialDelaySeconds: {{ .Values.backend.healthcheck.initialDelaySeconds }} + periodSeconds: {{ .Values.backend.healthcheck.periodSeconds }} + timeoutSeconds: {{ .Values.backend.healthcheck.timeoutSeconds }} + failureThreshold: {{ .Values.backend.healthcheck.failureThreshold }} + readinessProbe: + httpGet: + path: {{ .Values.backend.healthcheck.path }} + port: {{ .Values.backend.healthcheck.port }} + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + env: + # Database Configuration - complete DSN from secret + - name: SVIX_DB_DSN + valueFrom: + secretKeyRef: + name: {{ include "svix.fullname" . }}-secrets + key: SVIX_DB_DSN + + # Redis Configuration - complete DSN from secret + - name: SVIX_REDIS_DSN + valueFrom: + secretKeyRef: + name: {{ include "svix.fullname" . }}-secrets + key: SVIX_REDIS_DSN + + # Security Configuration from secrets + - name: SVIX_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "svix.fullname" . }}-secrets + key: SVIX_JWT_SECRET + - name: SVIX_MAIN_SECRET + valueFrom: + secretKeyRef: + name: {{ include "svix.fullname" . }}-secrets + key: SVIX_MAIN_SECRET + + # Production Configuration (override defaults) + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} \ No newline at end of file diff --git a/self-hosting/helm/templates/backend-hpa.yaml b/self-hosting/helm/templates/backend-hpa.yaml new file mode 100644 index 000000000..628341fe4 --- /dev/null +++ b/self-hosting/helm/templates/backend-hpa.yaml @@ -0,0 +1,45 @@ +{{- if .Values.backend.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.backend.name }}-hpa + labels: + {{- include "svix.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "svix.fullname" . }}-{{ .Values.backend.name }} + 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 }} + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 60 + policies: + - type: Percent + value: 50 + periodSeconds: 60 +{{- end }} \ No newline at end of file diff --git a/self-hosting/helm/templates/backend-secret.yaml b/self-hosting/helm/templates/backend-secret.yaml new file mode 100644 index 000000000..848671215 --- /dev/null +++ b/self-hosting/helm/templates/backend-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "svix.fullname" . }}-secrets + labels: + {{- include "svix.labels" . | nindent 4 }} +type: Opaque +data: + SVIX_JWT_SECRET: {{ .Values.backend.secrets.jwtSecret | b64enc }} + SVIX_MAIN_SECRET: {{ .Values.backend.secrets.mainSecret | b64enc }} + SVIX_DB_DSN: {{ .Values.backend.secrets.dbDsn | b64enc }} + SVIX_REDIS_DSN: {{ .Values.backend.secrets.redisDsn | b64enc }} \ No newline at end of file diff --git a/self-hosting/helm/templates/backend-service.yaml b/self-hosting/helm/templates/backend-service.yaml new file mode 100644 index 000000000..41a36a8e1 --- /dev/null +++ b/self-hosting/helm/templates/backend-service.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.backend.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + {{- with .Values.backend.service }} + type: {{ .type | default "ClusterIP" }} + ports: + - port: {{ .port | default 8071 }} + targetPort: http + protocol: TCP + name: http + {{- else }} + type: ClusterIP + ports: + - port: 8071 + targetPort: http + protocol: TCP + name: http + {{- end }} + selector: + {{- include "svix.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend \ No newline at end of file diff --git a/self-hosting/helm/templates/pgbouncer-configmap.yaml b/self-hosting/helm/templates/pgbouncer-configmap.yaml new file mode 100644 index 000000000..fb7a7d4ad --- /dev/null +++ b/self-hosting/helm/templates/pgbouncer-configmap.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "svix.fullname" . }}-pgbouncer-config + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: pgbouncer +data: + pgbouncer.ini: | + [databases] + * = host={{ include "svix.fullname" . }}-{{ .Values.postgres.name }} port=5432 user=postgres password={{ .Values.postgres.env.POSTGRES_PASSWORD }} + + [pgbouncer] + listen_addr = 0.0.0.0 + listen_port = 5432 + auth_type = trust + ignore_startup_parameters = extra_float_digits + pool_mode = session + max_client_conn = {{ .Values.pgbouncer.env.MAX_CLIENT_CONN }} + default_pool_size = 25 + auth_file = /tmp/userlist.txt + + userlist.txt: | + "postgres" "" \ No newline at end of file diff --git a/self-hosting/helm/templates/pgbouncer-deployment.yaml b/self-hosting/helm/templates/pgbouncer-deployment.yaml new file mode 100644 index 000000000..d8e24afa4 --- /dev/null +++ b/self-hosting/helm/templates/pgbouncer-deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.pgbouncer.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: pgbouncer +spec: + replicas: {{ .Values.pgbouncer.replicaCount }} + selector: + matchLabels: + {{- include "svix.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: pgbouncer + template: + metadata: + labels: + {{- include "svix.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: pgbouncer + spec: + initContainers: + - name: wait-for-postgres + image: postgres:13.4 + command: ['sh', '-c', 'until pg_isready -h {{ include "svix.fullname" . }}-{{ .Values.postgres.name }} -p 5432 -U postgres; do echo waiting for postgres; sleep 2; done; echo postgres is ready'] + containers: + - name: {{ .Values.pgbouncer.name }} + image: "{{ .Values.pgbouncer.image.repository }}:{{ .Values.pgbouncer.image.tag }}" + ports: + - name: pgbouncer + containerPort: 5432 + protocol: TCP + env: + {{- range $key, $value := .Values.pgbouncer.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + # Add environment variables like Docker Compose + - name: DB_HOST + value: "{{ include "svix.fullname" . }}-{{ .Values.postgres.name }}" + - name: DB_USER + value: "postgres" + - name: DB_PASSWORD + value: "{{ .Values.postgres.env.POSTGRES_PASSWORD }}" + - name: MAX_CLIENT_CONN + value: "{{ .Values.pgbouncer.env.MAX_CLIENT_CONN }}" + volumeMounts: + - name: pgbouncer-config + mountPath: /etc/pgbouncer/pgbouncer.ini + subPath: pgbouncer.ini + readOnly: true + - name: pgbouncer-config + mountPath: /tmp/userlist.txt + subPath: userlist.txt + readOnly: true + livenessProbe: + exec: + command: + {{- toYaml .Values.pgbouncer.healthcheck.command | nindent 16 }} + initialDelaySeconds: 30 + periodSeconds: {{ .Values.pgbouncer.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.pgbouncer.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: {{ .Values.pgbouncer.healthcheck.retries }} + readinessProbe: + exec: + command: + {{- toYaml .Values.pgbouncer.healthcheck.command | nindent 16 }} + initialDelaySeconds: 5 + periodSeconds: {{ .Values.pgbouncer.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.pgbouncer.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: 3 + volumes: + - name: pgbouncer-config + configMap: + name: {{ include "svix.fullname" . }}-pgbouncer-config \ No newline at end of file diff --git a/self-hosting/helm/templates/pgbouncer-service.yaml b/self-hosting/helm/templates/pgbouncer-service.yaml new file mode 100644 index 000000000..ad76dc720 --- /dev/null +++ b/self-hosting/helm/templates/pgbouncer-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.pgbouncer.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: pgbouncer +spec: + type: {{ .Values.pgbouncer.service.type }} + ports: + - port: {{ .Values.pgbouncer.service.port }} + targetPort: pgbouncer + protocol: TCP + name: pgbouncer + selector: + {{- include "svix.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: pgbouncer \ No newline at end of file diff --git a/self-hosting/helm/templates/postgres-deployment.yaml b/self-hosting/helm/templates/postgres-deployment.yaml new file mode 100644 index 000000000..0309e3232 --- /dev/null +++ b/self-hosting/helm/templates/postgres-deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.postgres.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + replicas: {{ .Values.postgres.replicaCount }} + selector: + matchLabels: + {{- include "svix.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: database + template: + metadata: + labels: + {{- include "svix.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + containers: + - name: {{ .Values.postgres.name }} + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + env: + {{- range $key, $value := .Values.postgres.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + livenessProbe: + exec: + command: + {{- toYaml .Values.postgres.healthcheck.command | nindent 16 }} + initialDelaySeconds: 30 + periodSeconds: {{ .Values.postgres.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.postgres.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: {{ .Values.postgres.healthcheck.retries }} + readinessProbe: + exec: + command: + {{- toYaml .Values.postgres.healthcheck.command | nindent 16 }} + initialDelaySeconds: 5 + periodSeconds: {{ .Values.postgres.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.postgres.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: 3 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + volumes: + - name: postgres-data + {{- if .Values.postgres.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.postgres.persistence.claimName }} + {{- else }} + emptyDir: {} + {{- end }} \ No newline at end of file diff --git a/self-hosting/helm/templates/postgres-pvc.yaml b/self-hosting/helm/templates/postgres-pvc.yaml new file mode 100644 index 000000000..52059af8e --- /dev/null +++ b/self-hosting/helm/templates/postgres-pvc.yaml @@ -0,0 +1,18 @@ +{{- if .Values.postgres.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.postgres.persistence.claimName }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.postgres.persistence.storageClass }} + storageClassName: {{ .Values.postgres.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} +{{- end }} diff --git a/self-hosting/helm/templates/postgres-service.yaml b/self-hosting/helm/templates/postgres-service.yaml new file mode 100644 index 000000000..dad2f5d31 --- /dev/null +++ b/self-hosting/helm/templates/postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.postgres.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: postgres + protocol: TCP + name: postgres + selector: + {{- include "svix.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database diff --git a/self-hosting/helm/templates/redis-deployment.yaml b/self-hosting/helm/templates/redis-deployment.yaml new file mode 100644 index 000000000..9623ccba7 --- /dev/null +++ b/self-hosting/helm/templates/redis-deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.redis.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + replicas: {{ .Values.redis.replicaCount }} + selector: + matchLabels: + {{- include "svix.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: redis + template: + metadata: + labels: + {{- include "svix.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: redis + spec: + containers: + - name: {{ .Values.redis.name }} + image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + command: + - redis-server + - --save + - {{ .Values.redis.config.save | quote }} + - --appendonly + - {{ .Values.redis.config.appendonly | quote }} + - --appendfsync + - {{ .Values.redis.config.appendfsync | quote }} + env: + {{- range $key, $value := .Values.redis.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + livenessProbe: + exec: + command: + {{- toYaml .Values.redis.healthcheck.command | nindent 16 }} + initialDelaySeconds: 30 + periodSeconds: {{ .Values.redis.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.redis.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: {{ .Values.redis.healthcheck.retries }} + readinessProbe: + exec: + command: + {{- toYaml .Values.redis.healthcheck.command | nindent 16 }} + initialDelaySeconds: 5 + periodSeconds: {{ .Values.redis.healthcheck.interval | replace "s" "" | atoi }} + timeoutSeconds: {{ .Values.redis.healthcheck.timeout | replace "s" "" | atoi }} + failureThreshold: 3 + volumeMounts: + - name: redis-data + mountPath: /data + volumes: + - name: redis-data + {{- if .Values.redis.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.redis.persistence.claimName }} + {{- else }} + emptyDir: {} + {{- end }} \ No newline at end of file diff --git a/self-hosting/helm/templates/redis-pvc.yaml b/self-hosting/helm/templates/redis-pvc.yaml new file mode 100644 index 000000000..42a09a1bf --- /dev/null +++ b/self-hosting/helm/templates/redis-pvc.yaml @@ -0,0 +1,18 @@ +{{- if .Values.redis.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.redis.persistence.claimName }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.redis.persistence.storageClass }} + storageClassName: {{ .Values.redis.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.redis.persistence.size }} +{{- end }} \ No newline at end of file diff --git a/self-hosting/helm/templates/redis-service.yaml b/self-hosting/helm/templates/redis-service.yaml new file mode 100644 index 000000000..dd8c93c5b --- /dev/null +++ b/self-hosting/helm/templates/redis-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "svix.fullname" . }}-{{ .Values.redis.name }} + labels: + {{- include "svix.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +spec: + type: {{ .Values.redis.service.type }} + ports: + - port: {{ .Values.redis.service.port }} + targetPort: redis + protocol: TCP + name: redis + selector: + {{- include "svix.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: redis \ No newline at end of file diff --git a/self-hosting/helm/values.yaml b/self-hosting/helm/values.yaml new file mode 100644 index 000000000..c222e0953 --- /dev/null +++ b/self-hosting/helm/values.yaml @@ -0,0 +1,109 @@ +backend: + name: "backend" + replicaCount: 1 + service: + type: ClusterIP + port: 8071 + image: + repository: svix/svix-server + tag: "latest" + pullPolicy: IfNotPresent + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 4 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + env: + WAIT_FOR: "true" + SVIX_ENVIRONMENT: "staging" # + SVIX_LOG_FORMAT: "json" + SVIX_CACHE_TYPE: "redis" + SVIX_ENDPOINT_HTTPS_ONLY: "true" + # Remove all PostgreSQL client environment variables + secrets: + jwtSecret: "your-super-secret-jwt-token" + mainSecret: "your-super-secret-jwt-token" + dbDsn: "postgresql://postgres:postgres@svix-pgbouncer:5432/postgres" + redisDsn: "redis://svix-redis:6379" + healthcheck: + type: "http" + path: "/api/v1/health" + port: 8071 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + +postgres: + name: "postgres" + replicaCount: 1 + image: + repository: postgres + tag: "13.4" + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + # Remove all custom PostgreSQL configuration + secretName: "svix-postgres-secret" + persistence: + enabled: true + size: 8Gi + storageClass: "" + claimName: "postgres-data1" + healthcheck: + command: ["pg_isready", "-U", "postgres"] + interval: 10s + timeout: 5s + retries: 60 + +pgbouncer: + name: "pgbouncer" + replicaCount: 1 + image: + repository: edoburu/pgbouncer + tag: "1.15.0" # Same as Docker Compose + env: + MAX_CLIENT_CONN: "500" + service: + type: ClusterIP + port: 5432 + healthcheck: + command: ["pg_isready", "-h", "localhost"] + interval: 30s + timeout: 10s + retries: 3 + +redis: + name: "redis" + replicaCount: 1 + image: + repository: redis + tag: "7-alpine" + env: + REDIS_PASSWORD: "" + config: + save: "60 500" + appendonly: "yes" + appendfsync: "everysec" + persistence: + enabled: true + size: 8Gi + storageClass: "" + claimName: "redis-data1" + service: + type: ClusterIP + port: 6379 + healthcheck: + command: ["redis-cli", "ping"] + interval: 1s + timeout: 1s + retries: 600 +