diff --git a/README.md b/README.md index 49592bffc..e9dd1cb32 100644 --- a/README.md +++ b/README.md @@ -140,10 +140,46 @@ finmind/ ``` ## Deployment -- Backend: Dockerized Flask to Railway/Render free tier (Postgres & Redis managed or via Compose locally). -- Frontend: Vercel. -- Secrets: use environment variables (.env locally, platform secrets in cloud). -- Kubernetes manifests for full stack deployment are available in `deploy/k8s/`. + +### Docker Compose (Local Development) +```bash +docker compose up --build +``` + +### Kubernetes (Production) + +**Option 1: Helm Chart (Recommended)** +```bash +helm install finmind ./deploy/helm/finmind -f ./deploy/helm/finmind/values-production.yaml +``` + +Features: +- Horizontal Pod Autoscaling (HPA) +- TLS/HTTPS with cert-manager +- Full observability stack +- Resource limits and health probes + +See [deploy/helm/README.md](deploy/helm/README.md) for full documentation. + +**Option 2: Raw Manifests** +```bash +kubectl apply -f deploy/k8s/ +``` + +See [deploy/k8s/README.md](deploy/k8s/README.md) for details. + +### Tilt (Local K8s Development) +```bash +tilt up +``` + +Provides live reload and hot module replacement for local Kubernetes development. +See [deploy/tilt/README.md](deploy/tilt/README.md) for setup instructions. + +### Cloud Platforms +- Backend: Railway, Render, Fly.io, AWS ECS, GCP Cloud Run, Azure Container Apps +- Frontend: Vercel, Netlify +- Secrets: Use environment variables (.env locally, platform secrets in cloud) ## Local Development 1) Prereqs: Docker, Docker Compose, Node 20+, Python 3.11+ diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 000000000..8d03928a0 --- /dev/null +++ b/Tiltfile @@ -0,0 +1,359 @@ +# FinMind Tiltfile - Local Kubernetes Development +# Run with: tilt up + +# Configuration +load('ext://namespace', 'namespace_create') +load('ext://helm_resource', 'helm_resource', 'helm_repo') + +# Create namespace +namespace_create('finmind') + +# ============================================================================ +# Build Docker images with live reload +# ============================================================================ + +# Backend image with live sync +docker_build( + 'finmind-backend', + context='./packages/backend', + dockerfile='./packages/backend/Dockerfile', + live_update=[ + sync('./packages/backend/app', '/app/app'), + sync('./packages/backend/wsgi.py', '/app/wsgi.py'), + run('pip install -r requirements.txt', trigger=['./packages/backend/requirements.txt']), + ], +) + +# Frontend image with live sync +docker_build( + 'finmind-frontend', + context='./app', + dockerfile='./app/Dockerfile', + live_update=[ + sync('./app/src', '/app/src'), + sync('./app/public', '/app/public'), + run('npm install', trigger=['./app/package.json']), + ], +) + +# ============================================================================ +# Deploy infrastructure (Postgres, Redis) +# ============================================================================ + +k8s_yaml(local('cat < +kubectl logs -n finmind +``` + +### Database connection issues + +```bash +kubectl run pg-test -n finmind --rm -it --restart=Never \ + --image=postgres:16 -- psql -h postgres -U finmind -d finmind +``` + +### Check HPA status + +```bash +kubectl get hpa -n finmind +kubectl describe hpa backend-hpa -n finmind +``` diff --git a/deploy/helm/finmind/Chart.yaml b/deploy/helm/finmind/Chart.yaml new file mode 100644 index 000000000..5766855ad --- /dev/null +++ b/deploy/helm/finmind/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: finmind +description: FinMind - AI-Powered Budget & Bill Tracking Application +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - finmind + - finance + - budgeting + - kubernetes +home: https://github.com/rohitdash08/FinMind +sources: + - https://github.com/rohitdash08/FinMind +maintainers: + - name: FinMind Team + url: https://github.com/rohitdash08/FinMind diff --git a/deploy/helm/finmind/templates/NOTES.txt b/deploy/helm/finmind/templates/NOTES.txt new file mode 100644 index 000000000..06a1da108 --- /dev/null +++ b/deploy/helm/finmind/templates/NOTES.txt @@ -0,0 +1,40 @@ +FinMind has been deployed! + +{{- if .Values.ingress.enabled }} + +Access your application at: + - Frontend: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.ingress.hosts.frontend }} + - API: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.ingress.hosts.api }} +{{- if .Values.monitoring.enabled }} + - Grafana: http{{ if .Values.ingress.tls.enabled }}s{{ end }}://{{ .Values.ingress.hosts.grafana }} +{{- end }} + +{{- else }} + +Access your application using port-forward: + kubectl port-forward -n {{ .Values.global.namespace }} svc/frontend 8080:80 + kubectl port-forward -n {{ .Values.global.namespace }} svc/backend 8000:8000 +{{- if .Values.monitoring.enabled }} + kubectl port-forward -n {{ .Values.global.namespace }} svc/grafana 3000:3000 +{{- end }} + +{{- end }} + +{{- if .Values.monitoring.enabled }} + +Grafana credentials: + Username: {{ .Values.monitoring.grafana.adminUser }} + Password: (stored in secret finmind-secrets) + +To get the Grafana password: + kubectl get secret -n {{ .Values.global.namespace }} finmind-secrets -o jsonpath="{.data.GRAFANA_ADMIN_PASSWORD}" | base64 -d + +{{- end }} + +Verify deployment: + kubectl get pods -n {{ .Values.global.namespace }} + kubectl get svc -n {{ .Values.global.namespace }} + kubectl get ingress -n {{ .Values.global.namespace }} + +Check backend health: + kubectl run curl -n {{ .Values.global.namespace }} --rm -it --restart=Never --image=curlimages/curl -- http://backend:8000/health diff --git a/deploy/helm/finmind/templates/_helpers.tpl b/deploy/helm/finmind/templates/_helpers.tpl new file mode 100644 index 000000000..b4890f397 --- /dev/null +++ b/deploy/helm/finmind/templates/_helpers.tpl @@ -0,0 +1,81 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "finmind.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "finmind.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 "finmind.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "finmind.labels" -}} +helm.sh/chart: {{ include "finmind.chart" . }} +{{ include "finmind.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "finmind.selectorLabels" -}} +app.kubernetes.io/name: {{ include "finmind.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Backend selector labels +*/}} +{{- define "finmind.backend.selectorLabels" -}} +app: backend +app.kubernetes.io/name: {{ include "finmind.name" . }}-backend +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Frontend selector labels +*/}} +{{- define "finmind.frontend.selectorLabels" -}} +app: frontend +app.kubernetes.io/name: {{ include "finmind.name" . }}-frontend +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Database URL +*/}} +{{- define "finmind.databaseUrl" -}} +postgresql+psycopg2://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@postgres:5432/$(POSTGRES_DB) +{{- end }} + +{{/* +Redis URL +*/}} +{{- define "finmind.redisUrl" -}} +redis://redis:6379/0 +{{- end }} diff --git a/deploy/helm/finmind/templates/backend.yaml b/deploy/helm/finmind/templates/backend.yaml new file mode 100644 index 000000000..0fa19883d --- /dev/null +++ b/deploy/helm/finmind/templates/backend.yaml @@ -0,0 +1,147 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + {{- include "finmind.backend.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "finmind.backend.selectorLabels" . | nindent 8 }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8000" + prometheus.io/path: "/metrics" + spec: + initContainers: + - name: wait-for-postgres + image: busybox:1.36 + command: + - sh + - -c + - | + until nc -z postgres 5432; do + echo "Waiting for postgres..." + sleep 2 + done + echo "Postgres is ready" + - name: wait-for-redis + image: busybox:1.36 + command: + - sh + - -c + - | + until nc -z redis 6379; do + echo "Waiting for redis..." + sleep 2 + done + echo "Redis is ready" + containers: + - name: backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: 8000 + name: http + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_DB + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: finmind-secrets + key: JWT_SECRET + - name: GEMINI_API_KEY + valueFrom: + secretKeyRef: + name: finmind-secrets + key: GEMINI_API_KEY + optional: true + - name: DATABASE_URL + value: {{ include "finmind.databaseUrl" . | quote }} + - name: REDIS_URL + valueFrom: + configMapKeyRef: + name: finmind-config + key: REDIS_URL + - name: GEMINI_MODEL + valueFrom: + configMapKeyRef: + name: finmind-config + key: GEMINI_MODEL + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + name: finmind-config + key: LOG_LEVEL + command: + - sh + - -c + - | + python -m flask --app wsgi:app init-db && \ + export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_multiproc && \ + rm -rf $PROMETHEUS_MULTIPROC_DIR && \ + mkdir -p $PROMETHEUS_MULTIPROC_DIR && \ + gunicorn --workers=2 --threads=4 --bind 0.0.0.0:8000 wsgi:app + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + startupProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: backend +spec: + type: {{ .Values.backend.service.type }} + selector: + {{- include "finmind.backend.selectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: 8000 + name: http diff --git a/deploy/helm/finmind/templates/configmap.yaml b/deploy/helm/finmind/templates/configmap.yaml new file mode 100644 index 000000000..3b1a0745e --- /dev/null +++ b/deploy/helm/finmind/templates/configmap.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: finmind-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + LOG_LEVEL: {{ .Values.backend.env.LOG_LEVEL | quote }} + GEMINI_MODEL: {{ .Values.backend.env.GEMINI_MODEL | quote }} + REDIS_URL: {{ include "finmind.redisUrl" . | quote }} + VITE_API_URL: "https://{{ .Values.ingress.hosts.api }}" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + default.conf: | + upstream backend_servers { + server backend:8000; + } + + server { + listen 80; + server_name _; + + location / { + proxy_pass http://backend_servers; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + location /nginx_status { + stub_status; + allow all; + } + + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } diff --git a/deploy/helm/finmind/templates/exporters.yaml b/deploy/helm/finmind/templates/exporters.yaml new file mode 100644 index 000000000..b379ca0c3 --- /dev/null +++ b/deploy/helm/finmind/templates/exporters.yaml @@ -0,0 +1,224 @@ +{{- if .Values.monitoring.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: postgres-exporter +spec: + replicas: 1 + selector: + matchLabels: + app: postgres-exporter + template: + metadata: + labels: + app: postgres-exporter + spec: + containers: + - name: postgres-exporter + image: quay.io/prometheuscommunity/postgres-exporter:v0.16.0 + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: finmind-secrets + key: POSTGRES_DB + - name: DATA_SOURCE_NAME + value: postgresql://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@postgres:5432/$(POSTGRES_DB)?sslmode=disable + ports: + - containerPort: 9187 + name: metrics + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 50m + memory: 64Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: postgres-exporter + ports: + - port: 9187 + targetPort: 9187 + name: metrics +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: redis-exporter +spec: + replicas: 1 + selector: + matchLabels: + app: redis-exporter + template: + metadata: + labels: + app: redis-exporter + spec: + containers: + - name: redis-exporter + image: oliver006/redis_exporter:v1.63.0 + env: + - name: REDIS_ADDR + value: redis://redis:6379 + ports: + - containerPort: 9121 + name: metrics + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 50m + memory: 64Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: redis-exporter + ports: + - port: 9121 + targetPort: 9121 + name: metrics +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: nginx-exporter +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-exporter + template: + metadata: + labels: + app: nginx-exporter + spec: + containers: + - name: nginx-exporter + image: nginx/nginx-prometheus-exporter:1.3.0 + args: + - -nginx.scrape-uri=http://nginx/nginx_status + ports: + - containerPort: 9113 + name: metrics + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: nginx-exporter + ports: + - port: 9113 + targetPort: 9113 + name: metrics +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: node-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: node-exporter +spec: + selector: + matchLabels: + app: node-exporter + template: + metadata: + labels: + app: node-exporter + spec: + hostNetwork: true + hostPID: true + containers: + - name: node-exporter + image: quay.io/prometheus/node-exporter:v1.8.2 + args: + - --path.rootfs=/host + ports: + - containerPort: 9100 + hostPort: 9100 + name: metrics + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + volumeMounts: + - name: root + mountPath: /host + readOnly: true + volumes: + - name: root + hostPath: + path: / +--- +apiVersion: v1 +kind: Service +metadata: + name: node-exporter + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: node-exporter + ports: + - port: 9100 + targetPort: 9100 + name: metrics +{{- end }} diff --git a/deploy/helm/finmind/templates/frontend.yaml b/deploy/helm/finmind/templates/frontend.yaml new file mode 100644 index 000000000..6d107a39d --- /dev/null +++ b/deploy/helm/finmind/templates/frontend.yaml @@ -0,0 +1,58 @@ +{{- if .Values.frontend.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + {{- include "finmind.frontend.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "finmind.frontend.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: 80 + name: http + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 10 + periodSeconds: 20 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: frontend +spec: + type: {{ .Values.frontend.service.type }} + selector: + {{- include "finmind.frontend.selectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: 80 + name: http +{{- end }} diff --git a/deploy/helm/finmind/templates/grafana.yaml b/deploy/helm/finmind/templates/grafana.yaml new file mode 100644 index 000000000..53a18fb42 --- /dev/null +++ b/deploy/helm/finmind/templates/grafana.yaml @@ -0,0 +1,127 @@ +{{- if .Values.monitoring.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasources + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + datasources.yml: | + apiVersion: 1 + datasources: + - name: Prometheus + uid: prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + - name: Loki + uid: loki + type: loki + access: proxy + url: http://loki:3100 + editable: false +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grafana-data + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.monitoring.grafana.storage.size }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grafana + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: grafana +spec: + replicas: 1 + selector: + matchLabels: + app: grafana + template: + metadata: + labels: + app: grafana + spec: + containers: + - name: grafana + image: grafana/grafana-oss:11.1.5 + ports: + - containerPort: 3000 + name: http + env: + - name: GF_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: finmind-secrets + key: GRAFANA_ADMIN_USER + - name: GF_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: finmind-secrets + key: GRAFANA_ADMIN_PASSWORD + - name: GF_USERS_ALLOW_SIGN_UP + value: "false" + - name: GF_SERVER_ROOT_URL + value: "https://{{ .Values.ingress.hosts.grafana }}" + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + volumeMounts: + - name: data + mountPath: /var/lib/grafana + - name: datasources + mountPath: /etc/grafana/provisioning/datasources/datasources.yml + subPath: datasources.yml + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 30 + volumes: + - name: data + persistentVolumeClaim: + claimName: grafana-data + - name: datasources + configMap: + name: grafana-datasources +--- +apiVersion: v1 +kind: Service +metadata: + name: grafana + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: grafana + ports: + - port: 3000 + targetPort: 3000 + name: http +{{- end }} diff --git a/deploy/helm/finmind/templates/hpa.yaml b/deploy/helm/finmind/templates/hpa.yaml new file mode 100644 index 000000000..655792b21 --- /dev/null +++ b/deploy/helm/finmind/templates/hpa.yaml @@ -0,0 +1,85 @@ +{{- if .Values.backend.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: backend-hpa + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: backend +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: backend + minReplicas: {{ .Values.backend.autoscaling.minReplicas }} + maxReplicas: {{ .Values.backend.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.backend.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.backend.autoscaling.targetMemoryUtilizationPercentage }} + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 100 + periodSeconds: 15 + - type: Pods + value: 4 + periodSeconds: 15 + selectPolicy: Max +{{- end }} +--- +{{- if and .Values.frontend.enabled .Values.frontend.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: frontend-hpa + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: frontend +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: frontend + minReplicas: {{ .Values.frontend.autoscaling.minReplicas }} + maxReplicas: {{ .Values.frontend.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.frontend.autoscaling.targetCPUUtilizationPercentage }} + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 100 + periodSeconds: 15 +{{- end }} diff --git a/deploy/helm/finmind/templates/ingress.yaml b/deploy/helm/finmind/templates/ingress.yaml new file mode 100644 index 000000000..30e4e028e --- /dev/null +++ b/deploy/helm/finmind/templates/ingress.yaml @@ -0,0 +1,110 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: finmind-api + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + annotations: + {{- if .Values.certManager.enabled }} + cert-manager.io/cluster-issuer: {{ .Values.certManager.issuer | quote }} + {{- end }} + nginx.ingress.kubernetes.io/proxy-body-size: "10m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "60" + nginx.ingress.kubernetes.io/proxy-send-timeout: "60" + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hosts.api }} + secretName: {{ .Values.ingress.tls.secretName }}-api + {{- end }} + rules: + - host: {{ .Values.ingress.hosts.api }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nginx + port: + number: 80 +--- +{{- if .Values.frontend.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: finmind-frontend + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + annotations: + {{- if .Values.certManager.enabled }} + cert-manager.io/cluster-issuer: {{ .Values.certManager.issuer | quote }} + {{- end }} + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hosts.frontend }} + secretName: {{ .Values.ingress.tls.secretName }}-frontend + {{- end }} + rules: + - host: {{ .Values.ingress.hosts.frontend }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend + port: + number: 80 +{{- end }} +--- +{{- if .Values.monitoring.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: finmind-grafana + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + annotations: + {{- if .Values.certManager.enabled }} + cert-manager.io/cluster-issuer: {{ .Values.certManager.issuer | quote }} + {{- end }} + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hosts.grafana }} + secretName: {{ .Values.ingress.tls.secretName }}-grafana + {{- end }} + rules: + - host: {{ .Values.ingress.hosts.grafana }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: grafana + port: + number: 3000 +{{- end }} +{{- end }} diff --git a/deploy/helm/finmind/templates/loki.yaml b/deploy/helm/finmind/templates/loki.yaml new file mode 100644 index 000000000..0c32c92e4 --- /dev/null +++ b/deploy/helm/finmind/templates/loki.yaml @@ -0,0 +1,134 @@ +{{- if .Values.monitoring.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: loki-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + config.yml: | + auth_enabled: false + + server: + http_listen_port: 3100 + log_level: info + + common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + + schema_config: + configs: + - from: 2024-01-01 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + + limits_config: + retention_period: {{ .Values.monitoring.loki.retention }} + + compactor: + working_directory: /loki/compactor + compaction_interval: 10m + retention_enabled: true + delete_request_store: filesystem +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: loki-data + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.monitoring.loki.storage.size }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loki + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: loki +spec: + replicas: 1 + selector: + matchLabels: + app: loki + template: + metadata: + labels: + app: loki + spec: + containers: + - name: loki + image: grafana/loki:2.9.10 + args: + - -config.file=/etc/loki/config.yml + ports: + - containerPort: 3100 + name: http + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + volumeMounts: + - name: config + mountPath: /etc/loki/config.yml + subPath: config.yml + - name: data + mountPath: /loki + readinessProbe: + httpGet: + path: /ready + port: 3100 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /ready + port: 3100 + initialDelaySeconds: 30 + periodSeconds: 30 + volumes: + - name: config + configMap: + name: loki-config + - name: data + persistentVolumeClaim: + claimName: loki-data +--- +apiVersion: v1 +kind: Service +metadata: + name: loki + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: loki + ports: + - port: 3100 + targetPort: 3100 + name: http +{{- end }} diff --git a/deploy/helm/finmind/templates/namespace.yaml b/deploy/helm/finmind/templates/namespace.yaml new file mode 100644 index 000000000..982cb6ebc --- /dev/null +++ b/deploy/helm/finmind/templates/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} diff --git a/deploy/helm/finmind/templates/nginx.yaml b/deploy/helm/finmind/templates/nginx.yaml new file mode 100644 index 000000000..9e0072ed9 --- /dev/null +++ b/deploy/helm/finmind/templates/nginx.yaml @@ -0,0 +1,65 @@ +{{- if .Values.nginx.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: nginx +spec: + replicas: {{ .Values.nginx.replicaCount }} + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" + ports: + - containerPort: 80 + name: http + resources: + {{- toYaml .Values.nginx.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + readinessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 10 + periodSeconds: 20 + volumes: + - name: config + configMap: + name: nginx-config +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: nginx +spec: + type: ClusterIP + selector: + app: nginx + ports: + - port: 80 + targetPort: 80 + name: http +{{- end }} diff --git a/deploy/helm/finmind/templates/postgres.yaml b/deploy/helm/finmind/templates/postgres.yaml new file mode 100644 index 000000000..14a20d3cf --- /dev/null +++ b/deploy/helm/finmind/templates/postgres.yaml @@ -0,0 +1,97 @@ +{{- if .Values.postgres.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-data + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: postgres +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.postgres.storage.storageClass }} + storageClassName: {{ .Values.postgres.storage.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgres.storage.size }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: postgres +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + ports: + - containerPort: 5432 + name: postgres + envFrom: + - secretRef: + name: finmind-secrets + resources: + {{- toYaml .Values.postgres.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + volumes: + - name: data + persistentVolumeClaim: + claimName: postgres-data +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: postgres +spec: + type: ClusterIP + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + name: postgres +{{- end }} diff --git a/deploy/helm/finmind/templates/prometheus.yaml b/deploy/helm/finmind/templates/prometheus.yaml new file mode 100644 index 000000000..47888d26a --- /dev/null +++ b/deploy/helm/finmind/templates/prometheus.yaml @@ -0,0 +1,194 @@ +{{- if .Values.monitoring.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + prometheus.yml: | + global: + scrape_interval: 15s + evaluation_interval: 15s + + scrape_configs: + - job_name: prometheus + static_configs: + - targets: ['prometheus:9090'] + + - job_name: backend + metrics_path: /metrics + static_configs: + - targets: ['backend:8000'] + + - job_name: postgres + static_configs: + - targets: ['postgres-exporter:9187'] + + - job_name: redis + static_configs: + - targets: ['redis-exporter:9121'] + + - job_name: nginx + static_configs: + - targets: ['nginx-exporter:9113'] + + - job_name: node + static_configs: + - targets: ['node-exporter:9100'] + + - job_name: kubernetes-pods + kubernetes_sd_configs: + - role: pod + relabel_configs: + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + action: keep + regex: true + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] + action: replace + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + target_label: __address__ + - source_labels: [__meta_kubernetes_namespace] + action: replace + target_label: kubernetes_namespace + - source_labels: [__meta_kubernetes_pod_name] + action: replace + target_label: kubernetes_pod_name +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: prometheus-data + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.monitoring.prometheus.storage.size }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: prometheus +spec: + replicas: 1 + selector: + matchLabels: + app: prometheus + template: + metadata: + labels: + app: prometheus + spec: + serviceAccountName: prometheus + containers: + - name: prometheus + image: prom/prometheus:v2.54.1 + args: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.retention.time={{ .Values.monitoring.prometheus.retention }} + - --storage.tsdb.retention.size={{ .Values.monitoring.prometheus.retentionSize }} + - --web.enable-lifecycle + ports: + - containerPort: 9090 + name: http + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + volumeMounts: + - name: config + mountPath: /etc/prometheus/prometheus.yml + subPath: prometheus.yml + - name: data + mountPath: /prometheus + readinessProbe: + httpGet: + path: /-/ready + port: 9090 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /-/healthy + port: 9090 + initialDelaySeconds: 15 + periodSeconds: 20 + volumes: + - name: config + configMap: + name: prometheus-config + - name: data + persistentVolumeClaim: + claimName: prometheus-data +--- +apiVersion: v1 +kind: Service +metadata: + name: prometheus + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +spec: + selector: + app: prometheus + ports: + - port: 9090 + targetPort: 9090 + name: http +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: prometheus + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: prometheus-{{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: [nodes, nodes/proxy, services, endpoints, pods] + verbs: [get, list, watch] + - apiGroups: ["extensions"] + resources: [ingresses] + verbs: [get, list, watch] + - nonResourceURLs: [/metrics] + verbs: [get] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: prometheus-{{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: prometheus-{{ .Values.global.namespace }} +subjects: + - kind: ServiceAccount + name: prometheus + namespace: {{ .Values.global.namespace }} +{{- end }} diff --git a/deploy/helm/finmind/templates/promtail.yaml b/deploy/helm/finmind/templates/promtail.yaml new file mode 100644 index 000000000..d87f4320b --- /dev/null +++ b/deploy/helm/finmind/templates/promtail.yaml @@ -0,0 +1,128 @@ +{{- if .Values.monitoring.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: promtail-config + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +data: + config.yml: | + server: + http_listen_port: 9080 + grpc_listen_port: 0 + + positions: + filename: /tmp/positions.yaml + + clients: + - url: http://loki:3100/loki/api/v1/push + + scrape_configs: + - job_name: kubernetes-pods + kubernetes_sd_configs: + - role: pod + pipeline_stages: + - cri: {} + relabel_configs: + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_pod_container_name] + target_label: container + - source_labels: [__meta_kubernetes_pod_label_app] + target_label: app + - action: replace + source_labels: [app, container] + separator: ';' + regex: '^;(.*)$' + replacement: '$1' + target_label: app + - action: replace + source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name] + separator: / + replacement: /var/log/pods/*$1/*.log + target_label: __path__ + - action: labeldrop + regex: filename +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: promtail + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: promtail-{{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: [nodes, nodes/proxy, services, endpoints, pods] + verbs: [get, list, watch] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: promtail-{{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: promtail-{{ .Values.global.namespace }} +subjects: + - kind: ServiceAccount + name: promtail + namespace: {{ .Values.global.namespace }} +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: promtail + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: promtail +spec: + selector: + matchLabels: + app: promtail + template: + metadata: + labels: + app: promtail + spec: + serviceAccountName: promtail + containers: + - name: promtail + image: grafana/promtail:3.0.0 + args: + - -config.file=/etc/promtail/config.yml + resources: + requests: + cpu: 20m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + volumeMounts: + - name: config + mountPath: /etc/promtail/config.yml + subPath: config.yml + - name: varlog + mountPath: /var/log + readOnly: true + volumes: + - name: config + configMap: + name: promtail-config + - name: varlog + hostPath: + path: /var/log +{{- end }} diff --git a/deploy/helm/finmind/templates/redis.yaml b/deploy/helm/finmind/templates/redis.yaml new file mode 100644 index 000000000..5d74919cc --- /dev/null +++ b/deploy/helm/finmind/templates/redis.yaml @@ -0,0 +1,55 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}" + ports: + - containerPort: 6379 + name: redis + resources: + {{- toYaml .Values.redis.resources | nindent 12 }} + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 15 + periodSeconds: 20 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} + app: redis +spec: + type: ClusterIP + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 + name: redis +{{- end }} diff --git a/deploy/helm/finmind/templates/secrets.yaml b/deploy/helm/finmind/templates/secrets.yaml new file mode 100644 index 000000000..cc03bcc02 --- /dev/null +++ b/deploy/helm/finmind/templates/secrets.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Secret +metadata: + name: finmind-secrets + namespace: {{ .Values.global.namespace }} + labels: + {{- include "finmind.labels" . | nindent 4 }} +type: Opaque +stringData: + POSTGRES_USER: {{ .Values.secrets.postgres.user | quote }} + POSTGRES_PASSWORD: {{ .Values.secrets.postgres.password | default "finmind" | quote }} + POSTGRES_DB: {{ .Values.secrets.postgres.database | quote }} + JWT_SECRET: {{ .Values.secrets.jwt.secret | default (randAlphaNum 32) | quote }} + GRAFANA_ADMIN_USER: {{ .Values.monitoring.grafana.adminUser | quote }} + GRAFANA_ADMIN_PASSWORD: {{ .Values.secrets.grafana.adminPassword | default (randAlphaNum 16) | quote }} + {{- if .Values.secrets.gemini.apiKey }} + GEMINI_API_KEY: {{ .Values.secrets.gemini.apiKey | quote }} + {{- end }} + {{- if .Values.secrets.openai.apiKey }} + OPENAI_API_KEY: {{ .Values.secrets.openai.apiKey | quote }} + {{- end }} + {{- if .Values.secrets.twilio.accountSid }} + TWILIO_ACCOUNT_SID: {{ .Values.secrets.twilio.accountSid | quote }} + TWILIO_AUTH_TOKEN: {{ .Values.secrets.twilio.authToken | quote }} + TWILIO_WHATSAPP_FROM: {{ .Values.secrets.twilio.whatsappFrom | quote }} + {{- end }} + {{- if .Values.secrets.smtp.url }} + SMTP_URL: {{ .Values.secrets.smtp.url | quote }} + EMAIL_FROM: {{ .Values.secrets.smtp.emailFrom | quote }} + {{- end }} diff --git a/deploy/helm/finmind/values-local.yaml b/deploy/helm/finmind/values-local.yaml new file mode 100644 index 000000000..790bacd99 --- /dev/null +++ b/deploy/helm/finmind/values-local.yaml @@ -0,0 +1,111 @@ +global: + namespace: finmind + domain: finmind.local + +backend: + replicaCount: 1 + image: + repository: finmind-backend + tag: latest + pullPolicy: Never + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + autoscaling: + enabled: false + env: + LOG_LEVEL: DEBUG + GEMINI_MODEL: gemini-1.5-flash + +frontend: + enabled: true + replicaCount: 1 + image: + repository: finmind-frontend + tag: latest + pullPolicy: Never + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi + autoscaling: + enabled: false + +postgres: + enabled: true + storage: + size: 1Gi + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +redis: + enabled: true + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi + +nginx: + enabled: true + replicaCount: 1 + resources: + requests: + cpu: 25m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi + +ingress: + enabled: true + className: nginx + tls: + enabled: false + hosts: + api: api.finmind.local + frontend: finmind.local + grafana: grafana.finmind.local + +certManager: + enabled: false + +monitoring: + enabled: true + prometheus: + retention: 7d + retentionSize: 500MB + storage: + size: 2Gi + loki: + retention: 72h + storage: + size: 2Gi + grafana: + storage: + size: 1Gi + adminUser: admin + +secrets: + postgres: + user: finmind + password: finmind + database: finmind + jwt: + secret: local-dev-jwt-secret + grafana: + adminPassword: admin diff --git a/deploy/helm/finmind/values-production.yaml b/deploy/helm/finmind/values-production.yaml new file mode 100644 index 000000000..a998a6a5a --- /dev/null +++ b/deploy/helm/finmind/values-production.yaml @@ -0,0 +1,115 @@ +global: + namespace: finmind + domain: finmind.example.com + +backend: + replicaCount: 3 + image: + repository: ghcr.io/rohitdash08/finmind-backend + tag: latest + pullPolicy: Always + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi + autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 20 + targetCPUUtilizationPercentage: 60 + targetMemoryUtilizationPercentage: 70 + env: + LOG_LEVEL: WARNING + GEMINI_MODEL: gemini-1.5-flash + +frontend: + enabled: true + replicaCount: 3 + image: + repository: ghcr.io/rohitdash08/finmind-frontend + tag: latest + pullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + targetCPUUtilizationPercentage: 60 + +postgres: + enabled: true + storage: + size: 50Gi + storageClass: gp3 + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 2000m + memory: 4Gi + +redis: + enabled: true + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +nginx: + enabled: true + replicaCount: 2 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + tls: + enabled: true + secretName: finmind-tls + hosts: + api: api.finmind.example.com + frontend: finmind.example.com + grafana: grafana.finmind.example.com + +certManager: + enabled: true + issuer: letsencrypt-prod + email: admin@finmind.example.com + +monitoring: + enabled: true + prometheus: + retention: 30d + retentionSize: 10GB + storage: + size: 20Gi + loki: + retention: 336h + storage: + size: 20Gi + grafana: + storage: + size: 5Gi + adminUser: finmind_admin diff --git a/deploy/helm/finmind/values.yaml b/deploy/helm/finmind/values.yaml new file mode 100644 index 000000000..35ee1724f --- /dev/null +++ b/deploy/helm/finmind/values.yaml @@ -0,0 +1,149 @@ +global: + namespace: finmind + domain: finmind.local + +backend: + replicaCount: 2 + image: + repository: ghcr.io/rohitdash08/finmind-backend + tag: latest + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 8000 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + env: + LOG_LEVEL: INFO + GEMINI_MODEL: gemini-1.5-flash + +frontend: + enabled: true + replicaCount: 2 + image: + repository: ghcr.io/rohitdash08/finmind-frontend + tag: latest + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 80 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 70 + +postgres: + enabled: true + image: + repository: postgres + tag: "16" + storage: + size: 10Gi + storageClass: "" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +redis: + enabled: true + image: + repository: redis + tag: "7" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + +nginx: + enabled: true + replicaCount: 1 + image: + repository: nginx + tag: 1.27-alpine + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + +ingress: + enabled: true + className: nginx + annotations: {} + tls: + enabled: false + secretName: finmind-tls + hosts: + api: api.finmind.local + frontend: finmind.local + grafana: grafana.finmind.local + +certManager: + enabled: false + issuer: letsencrypt-prod + email: "" + +monitoring: + enabled: true + prometheus: + retention: 14d + retentionSize: 1GB + storage: + size: 5Gi + loki: + retention: 168h + storage: + size: 5Gi + grafana: + storage: + size: 2Gi + adminUser: finmind_admin + +secrets: + postgres: + user: finmind + password: "" + database: finmind + jwt: + secret: "" + grafana: + adminPassword: "" + gemini: + apiKey: "" + openai: + apiKey: "" + twilio: + accountSid: "" + authToken: "" + whatsappFrom: "" + smtp: + url: "" + emailFrom: "" diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index ccf91aef2..aec77c3ef 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -1,37 +1,68 @@ # FinMind Kubernetes Deployment -This folder provides a low-cost Kubernetes deployment path for: -- FinMind backend API +This folder provides Kubernetes deployment options for FinMind: + +## Deployment Options + +### Option 1: Helm Chart (Recommended) + +The Helm chart provides a production-grade deployment with: +- Horizontal Pod Autoscaling (HPA) +- TLS/HTTPS with cert-manager +- Configurable resource limits +- Multiple environment configurations + +See [deploy/helm/README.md](../helm/README.md) for full documentation. + +```bash +# Quick start with Helm +helm install finmind ./deploy/helm/finmind -f ./deploy/helm/finmind/values-local.yaml +``` + +### Option 2: Raw Manifests (This folder) + +For simpler deployments or learning purposes, use raw manifests. + +**Components:** +- FinMind backend API + Frontend - PostgreSQL + Redis -- Nginx reverse proxy + metrics exporter +- Nginx reverse proxy + metrics exporters - Grafana OSS + Prometheus + Loki + Promtail + node-exporter -## 1. Prerequisites +## Prerequisites + - Kubernetes cluster (k3s, EKS, AKS, GKE, or kind for local) - Ingress Nginx controller installed (`ingressClassName: nginx`) - `kubectl` configured to target the cluster -- Backend image published (default in manifests: `ghcr.io/rohitdash08/finmind-backend:latest`) +- Backend/Frontend images published (or built locally) +- metrics-server (for HPA): `kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml` + +## Setup Secrets -## 2. Setup Secrets 1. Copy and edit secrets: ```bash cp deploy/k8s/secrets.example.yaml deploy/k8s/secrets.yaml ``` 2. Update all secret values in `deploy/k8s/secrets.yaml`. -## 3. Apply Manifests +## Apply Manifests + ```bash kubectl apply -f deploy/k8s/namespace.yaml kubectl apply -f deploy/k8s/secrets.yaml kubectl apply -f deploy/k8s/app-stack.yaml +kubectl apply -f deploy/k8s/frontend.yaml +kubectl apply -f deploy/k8s/hpa.yaml kubectl apply -f deploy/k8s/monitoring-stack.yaml ``` -## 4. Validate +## Validate + ```bash kubectl get pods -n finmind kubectl get svc -n finmind kubectl get ingress -n finmind +kubectl get hpa -n finmind ``` Check API health from cluster: @@ -40,18 +71,55 @@ kubectl run curl -n finmind --rm -it --restart=Never --image=curlimages/curl -- http://backend:8000/health ``` -## 5. Port-forward (owner/admin access) -Grafana: +## Port-forward (local access) + ```bash +# Frontend +kubectl port-forward -n finmind svc/frontend 8080:80 + +# Backend API +kubectl port-forward -n finmind svc/backend 8000:8000 + +# Grafana kubectl port-forward -n finmind svc/grafana 3000:3000 + +# Prometheus +kubectl port-forward -n finmind svc/prometheus 9090:9090 ``` -Prometheus: + +## TLS Configuration + +For production TLS, install cert-manager and update the ingress annotations: + ```bash -kubectl port-forward -n finmind svc/prometheus 9090:9090 +# Install cert-manager +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml + +# Create ClusterIssuer +kubectl apply -f - < + +# Or change the port in Tiltfile +```