feat: add helm support (#812)

* feat: add helm support

* fix: prettier

---------

Co-authored-by: orig <oriorigranot@gamil.com>
This commit is contained in:
orig
2024-07-14 16:41:23 +03:00
committed by GitHub
parent 6244a5ea13
commit 795b40a2a1
34 changed files with 337 additions and 276 deletions

1
.gitignore vendored
View File

@ -31,6 +31,7 @@ node_modules
/.sass-cache
/connect.lock
/coverage
/.early.coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log

View File

@ -1,3 +1,6 @@
.prettierignore
.docusaurus/
/.nx/cache
/.nx/cache
# Ignore chart files
/docker/k8s/**/*.yaml

View File

@ -157,6 +157,18 @@ npx nx run-many -t docker-build
docker compose -f docker/local/docker-compose.yml -p reduced-to up
```
### ☸ Deployment
You can deploy the app to a Kubernetes cluster by installing the [Helm](https://helm.sh/) chart.
1. Navigate to the `chart` directory (/docker/k8s/chart)
2. Update the values in the `values.yaml` file
3. Run the following command to deploy the app to your cluster:
```sh
helm install reduced-to . --namespace reduced-to --create-namespace
```
### 👷 Configuration
For the minimal configuration you can just rename the `.example.env` files to `.env`.

View File

@ -1,6 +1,6 @@
import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';
import { IoLogoDocker } from '@qwikest/icons/ionicons';
import { SiHelm } from '@qwikest/icons/simpleicons';
import { useGetCurrentUser } from '../../routes/layout';
export const Hero = component$(() => {
@ -61,8 +61,8 @@ export const Hero = component$(() => {
target="_blank"
class="flex justify-center items-center gap-x-2 p-2 ps-3 text-sm font-mono rounded-lg border border-gray-200 bg-white text-gray-800 shadow-sm hover:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-slate-900 dark:border-gray-700 dark:text-white dark:hover:bg-gray-800 dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600 sm:w-auto w-2/3 h-10"
>
Self-Hosted Deployment
<IoLogoDocker class="h-5 w-5" />
<span>Deploy with Helm</span>
<SiHelm class="h-5 w-5" />
</a>
</div>
{/* <!-- End Buttons --> */}

View File

@ -1,35 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
annotations:
# force policy will ensure that deployment is updated
# even when tag is unchanged (latest remains)
keel.sh/policy: force
keel.sh/trigger: poll # <-- actively query registry, otherwise defaults to webhooks
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: ghcr.io/origranot/reduced.to/backend:master
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: 3000
resources:
requests:
memory: '256Mi'
cpu: '100m'
limits:
memory: '1024Mi'
cpu: '500m'

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 3000
targetPort: 3000

View File

@ -1,14 +0,0 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <your-email>
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx

View File

@ -1,72 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: reduced-configmap
data:
# General
BACKEND_APP_PORT: '3000'
FRONTEND_APP_PORT: '5000'
TRACKER_APP_PORT: '3001'
NODE_ENV: 'production'
# RATE LIMIT
RATE_LIMIT_TTL: '60'
RATE_LIMIT_COUNT: '100'
# PADDLE - (Payment Gateway)
PADDLE_ENABLE: 'false'
PADDLE_WEBHOOK_KEY: 'pld_test_webhook_key'
PADDLE_SECRET_KEY: 'secret_key'
# LOGGER
LOGGER_CONSOLE_THRESHOLD: 'INFO' # DEBUG, INFO, WARN, ERROR, FATAL
# FRONTEND
DOMAIN: 'reduced.to'
PUBLIC_PADDLE_KEY: 'test_public_key'
CLIENTSIDE_API_DOMAIN: 'https://reduced.to' # Use this variable while making client-side API calls
API_DOMAIN: 'http://backend-service:3000' # Use this variable while making server-side API calls
STORAGE_DOMAIN: 'Get it from https://cloud.digitalocean.com/spaces'
# DATABASE
POSTGRES_PORT: '5432'
POSTGRES_DB: 'reduced_to_db'
POSTGRES_USER: 'postgres'
POSTGRES_PASSWORD: 'postgres'
DATABASE_URL: 'postgresql://postgres:postgres@postgres-service:5432/reduced_to_db'
# REDIS
REDIS_ENABLE: 'false'
REDIS_HOST: 'redis-service'
REDIS_PORT: '6379'
REDIS_PASSWORD: 'password'
REDIS_TTL: '1800000' # 30 minutes in ms (default TTL)
# NOVU - You don't need this when running locally (just verify your email from the database)
NOVU_API_KEY: 'Get it from https://novu.co/'
# KAFKA (Used for analytics)
KAFKA_ENABLE: 'false'
KAFKA_BROKER: ''
KAFKA_USERNAME: ''
KAFKA_PASSWORD: ''
# AUTH
AUTH_JWT_ACCESS_SECRET: 'abc1234'
AUTH_JWT_REFRESH_SECRET: 'abc1234'
AUTH_GOOGLE_CLIENT_ID: 'Get it from https://console.cloud.google.com/apis/credentials'
AUTH_GOOGLE_CLIENT_SECRET: 'Get it from https://console.cloud.google.com/apis/credentials'
# SAFE URL
SAFE_URL_GOOGLE_SAFE_BROWSING_API_KEY: 'Get it from https://console.cloud.google.com/apis/credentials'
SAFE_URL_ENABLE: 'false'
# TRACKER
TRACKER_STATS_TOPIC_NAME: 'stats'
# STORAGE
STORAGE_ENABLE: 'false'
STORAGE_ENDPOINT: 'Get it from https://cloud.digitalocean.com/spaces'
STORAGE_ACCESS_KEY: 'Get it from https://cloud.digitalocean.com/spaces'
STORAGE_SECRET_KEY: 'Get it from https://cloud.digitalocean.com/spaces'
STORAGE_BUCKET_NAME: 'Get it from https://cloud.digitalocean.com/spaces'

View File

@ -1,31 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
annotations:
service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: 'true'
service.beta.kubernetes.io/do-loadbalancer-hostname: 'reduced.to'
labels:
helm.sh/chart: ingress-nginx-4.0.15 # Make sure it's the same as the one in the ingress-nginx-controller.yaml
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 1.1.1 # Make sure it's the same as the one in the ingress-nginx-controller.yaml
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
spec:
type: LoadBalancer
externalTrafficPolicy: Local
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller

View File

@ -1,35 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
annotations:
# force policy will ensure that deployment is updated
# even when tag is unchanged (latest remains)
keel.sh/policy: force
keel.sh/trigger: poll # <-- actively query registry, otherwise defaults to webhooks
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: ghcr.io/origranot/reduced.to/frontend:master
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: 5000
resources:
requests:
memory: '256Mi'
cpu: '100m'
limits:
memory: '512Mi'
cpu: '500m'

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 5000
targetPort: 5000

View File

@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-external
spec:
selector:
app: postgres
ports:
- name: postgres-external
port: 5432
targetPort: 5432
nodePort: 30030
type: NodePort

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: tracker-service
spec:
selector:
app: tracker
ports:
- protocol: TCP
port: 3001
targetPort: 3001

View File

@ -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/

View File

@ -0,0 +1,6 @@
dependencies:
- name: keel
repository: https://charts.keel.sh
version: 1.0.3
digest: sha256:1d43c1febe81b7be16d403d12d113311cdfb2e13046837a22492a948273b6e72
generated: "2024-07-14T15:39:54.199338+03:00"

View File

@ -0,0 +1,9 @@
apiVersion: v2
name: reduced.to
version: 0.1.0
description: A Helm chart for Kubernetes
dependencies:
- name: keel
version: 1.0.3
repository: https://charts.keel.sh

Binary file not shown.

View File

@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
namespace: {{ .Values.namespace }}
annotations:
keel.sh/policy: force
keel.sh/trigger: poll
spec:
replicas: {{ .Values.backend.replicas }}
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: {{ .Values.backend.image }}
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: {{ .Values.backend.port | int }}
{{- if .Values.backend.resources }}
resources: {{ .Values.backend.resources | toYaml | nindent 12 }}
{{- else }}
resources: {{ .Values.default_resources | toYaml | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: backend
ports:
- protocol: TCP
port: {{ required "Backend port has to be defined" .Values.backend.port }}
targetPort: {{ required "Backend port has to be defined" .Values.backend.port }}
type: 'ClusterIP'

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: reduced-configmap
namespace: {{ .Values.namespace }}
data:
{{- range $key, $value := .Values.shared_configmap }}
{{ $key }}: {{ $value | quote }}
{{- end }}
API_DOMAIN: "http://backend-service:{{ .Values.backend.port }}"

View File

@ -2,9 +2,9 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: reduced-ingress
namespace: default
namespace: {{ .Values.namespace }}
annotations:
cert-manager.io/cluster-issuer: 'letsencrypt-prod'
cert-manager.io/cluster-issuer: {{ required "TLS issuer has to be defined" .Values.tls.issuer }}
kubernetes.io/ingress.class: 'nginx'
labels:
app: reduced
@ -12,10 +12,10 @@ spec:
ingressClassName: nginx
tls:
- hosts:
- reduced.to
secretName: letsencrypt-prod # Name of the secret containing the certificate
- {{ .Values.shared_configmap.DOMAIN }}
secretName: {{ required "TLS secret has to be defined" .Values.tls.secret_name }}
rules:
- host: reduced.to
- host: {{ .Values.shared_configmap.DOMAIN }}
http:
paths:
- path: /

View File

@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
namespace: {{ .Values.namespace }}
annotations:
keel.sh/policy: force
keel.sh/trigger: poll
spec:
replicas: {{ .Values.frontend.replicas }}
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: {{ .Values.frontend.image }}
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: {{ .Values.shared_configmap.FRONTEND_APP_PORT | int }}
{{- if .Values.frontend.resources }}
resources: {{ .Values.frontend.resources | toYaml | nindent 12 }}
{{- else }}
resources: {{ .Values.default_resources | toYaml | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: {{ required "Frontend port has to be defined" .Values.frontend.port }}
targetPort: {{ required "Frontend port has to be defined" .Values.frontend.port }}
type: 'ClusterIP'

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-external
namespace: {{ .Values.namespace }}
spec:
selector:
app: postgres
ports:
- name: postgres-external
port: {{ required "Postgres port has to be defined" .Values.postgres_port }}
targetPort: {{ required "Postgres port has to be defined" .Values.postgres_port }}
nodePort: 30030
type: NodePort

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: {{ required "Postgres port has to be defined" .Values.postgres_port }}
targetPort: {{ required "Postgres port has to be defined" .Values.postgres_port }}

View File

@ -2,6 +2,7 @@ apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-statefulset
namespace: {{ .Values.namespace }}
spec:
replicas: 1
selector:
@ -19,7 +20,7 @@ spec:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: 5432
- containerPort: {{ required "Postgres port has to be defined" .Values.postgres_port }}
volumeMounts:
- name: postgres-pv-claim
mountPath: /var/lib/postgresql/data

View File

@ -2,17 +2,25 @@ apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
labels:
app.kubernetes.io/managed-by: Helm
annotations:
meta.helm.sh/release-name: {{ .Release.Name }}
meta.helm.sh/release-namespace: {{ .Values.namespace }}
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /mnt/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: {{ .Values.namespace }}
spec:
accessModes:
- ReadWriteOnce

View File

@ -2,6 +2,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
namespace: {{ .Values.namespace }}
labels:
app: redis
spec:
@ -18,7 +19,7 @@ spec:
- name: redis
image: redis:7.2.3
ports:
- containerPort: 6379
- containerPort: {{ required "Redis port has to be defined" .Values.redis_port }}
env:
- name: REDIS_PASSWORD
valueFrom:

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- protocol: TCP
port: {{ required "Redis port has to be defined" .Values.redis_port }}
targetPort: {{ required "Redis port has to be defined" .Values.redis_port }}

View File

@ -2,13 +2,14 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: tracker-deployment
namespace: {{ .Values.namespace }}
annotations:
# force policy will ensure that deployment is updated
# even when tag is unchanged (latest remains)
keel.sh/policy: force
keel.sh/trigger: poll # <-- actively query registry, otherwise defaults to webhooks
spec:
replicas: 1
replicas: {{ .Values.tracker.replicas }}
selector:
matchLabels:
app: tracker
@ -19,17 +20,15 @@ spec:
spec:
containers:
- name: tracker
image: ghcr.io/origranot/reduced.to/tracker:master
image: {{ .Values.tracker.image }}
imagePullPolicy: Always
envFrom:
- configMapRef:
name: reduced-configmap
ports:
- containerPort: 3000
resources:
requests:
memory: '256Mi'
cpu: '100m'
limits:
memory: '1024Mi'
cpu: '500m'
- containerPort: {{ required "Tracker port has to be defined" .Values.tracker.port }}
{{- if .Values.tracker.resources }}
resources: {{ .Values.tracker.resources | toYaml | nindent 12 }}
{{- else }}
resources: {{ .Values.default_resources | toYaml | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: tracker-service
namespace: {{ .Values.namespace }}
spec:
selector:
app: tracker
ports:
- protocol: TCP
port: {{ required "Tracker port has to be defined" .Values.tracker.port }}
targetPort: {{ required "Tracker port has to be defined" .Values.tracker.port }}

View File

@ -0,0 +1,103 @@
# values.yaml
namespace: reduced-to # Namespace for the application
backend_port: &backend_port 3000 # Port for backend application
frontend_port: &frontend_port 5000 # Port for frontend application
tracker_port: &tracker_port 3001 # Port for tracker application
postgres_port: &postgres_port 5432 # Port for PostgreSQL database
redis_port: &redis_port 6379 # Port for Redis database
tls:
issuer: 'letsencrypt-prod' # TLS issuer
secret_name: 'letsencrypt-prod' # TLS secret name
default_resources:
requests:
memory: '256Mi' # Default memory request for deployments
cpu: '100m' # Default CPU request for deployments
limits:
memory: '1024Mi' # Default memory limit for deployments
cpu: '500m' # Default CPU limit for deployments
backend:
replicas: 1
image: 'ghcr.io/origranot/reduced.to/backend:master'
port: *backend_port
frontend:
replicas: 1
image: 'ghcr.io/origranot/reduced.to/frontend:master'
port: *frontend_port
tracker:
replicas: 1
image: 'ghcr.io/origranot/reduced.to/tracker:master'
port: *tracker_port
shared_configmap:
# General application settings
BACKEND_APP_PORT: *backend_port # Port for backend application
FRONTEND_APP_PORT: *frontend_port # Port for frontend application
TRACKER_APP_PORT: *tracker_port # Port for tracker application
NODE_ENV: 'production' # Node environment
# Rate limiting settings
RATE_LIMIT_TTL: '60' # Time to live for rate limit (seconds)
RATE_LIMIT_COUNT: '100' # Number of allowed requests per TTL
# Paddle payment gateway settings
PADDLE_ENABLE: 'false' # Enable/disable Paddle integration
PADDLE_WEBHOOK_KEY: 'pld_test_webhook_key' # Paddle webhook key
PADDLE_SECRET_KEY: 'secret_key' # Paddle secret key
# Logger settings
LOGGER_CONSOLE_THRESHOLD: 'INFO' # Log level for console output (DEBUG, INFO, WARN, ERROR, FATAL)
# Frontend settings
DOMAIN: 'reduced.to' # Domain for the frontend application
PUBLIC_PADDLE_KEY: 'test_public_key' # Public key for Paddle integration
CLIENTSIDE_API_DOMAIN: 'https://reduced.to' # Client-side API domain
STORAGE_DOMAIN: 'Get it from https://cloud.digitalocean.com/spaces' # Storage domain
# Database settings
POSTGRES_PORT: *postgres_port # Port for PostgreSQL database
POSTGRES_DB: 'reduced_to_db' # PostgreSQL database name
POSTGRES_USER: 'postgres' # PostgreSQL username
POSTGRES_PASSWORD: 'postgres' # PostgreSQL password
DATABASE_URL: 'postgresql://postgres:postgres@postgres-service:5432/reduced_to_db' # Database connection URL
# Redis settings
REDIS_ENABLE: 'false' # Enable/disable Redis integration
REDIS_HOST: 'redis-service' # Redis host
REDIS_PORT: *redis_port # Redis port
REDIS_PASSWORD: 'password' # Redis password
REDIS_TTL: '1800000' # Redis TTL (milliseconds)
# Novu settings (for email verification)
NOVU_API_KEY: 'Get it from https://novu.co/' # Novu API key
# Kafka settings (used for analytics)
KAFKA_ENABLE: 'false' # Enable/disable Kafka integration
KAFKA_BROKER: '' # Kafka broker
KAFKA_USERNAME: '' # Kafka username
KAFKA_PASSWORD: '' # Kafka password
# Authentication settings
AUTH_JWT_ACCESS_SECRET: 'abc1234' # JWT access secret
AUTH_JWT_REFRESH_SECRET: 'abc1234' # JWT refresh secret
AUTH_GOOGLE_CLIENT_ID: 'Get it from https://console.cloud.google.com/apis/credentials' # Google client ID
AUTH_GOOGLE_CLIENT_SECRET: 'Get it from https://console.cloud.google.com/apis/credentials' # Google client secret
# Safe URL settings
SAFE_URL_GOOGLE_SAFE_BROWSING_API_KEY: 'Get it from https://console.cloud.google.com/apis/credentials' # Google Safe Browsing API key
SAFE_URL_ENABLE: 'false' # Enable/disable Safe URL check
# Tracker settings
TRACKER_STATS_TOPIC_NAME: 'stats' # Kafka topic name for tracker stats
# Storage settings
STORAGE_ENABLE: 'false' # Enable/disable storage integration
STORAGE_ENDPOINT: 'Get it from https://cloud.digitalocean.com/spaces' # Storage endpoint
STORAGE_ACCESS_KEY: 'Get it from https://cloud.digitalocean.com/spaces' # Storage access key
STORAGE_SECRET_KEY: 'Get it from https://cloud.digitalocean.com/spaces' # Storage secret key
STORAGE_BUCKET_NAME: 'Get it from https://cloud.digitalocean.com/spaces' # Storage bucket name