revised complete k8s deployment

This commit is contained in:
liquidrinu 2025-05-21 10:13:38 +02:00
parent 0167ffdd18
commit 77f8e1554c
19 changed files with 126 additions and 129 deletions

@ -56,7 +56,7 @@ jobs:
- name: Create secrets file - name: Create secrets file
run: | run: |
cat > ./chart/secrets.prod.yml << EOF cat > ./chart/secrets.prod.yaml << EOF
backend: backend:
env: env:
POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}" POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}"
@ -72,8 +72,8 @@ jobs:
helm upgrade --install fusero ./chart \ helm upgrade --install fusero ./chart \
--namespace fusero-prod \ --namespace fusero-prod \
--create-namespace \ --create-namespace \
--values ./chart/values.prod.yml \ --values ./chart/values.prod.yaml \
--values ./chart/secrets.prod.yml \ --values ./chart/secrets.prod.yaml \
--set backend.image.repository=registry.liquidrinu.com/fusero-backend \ --set backend.image.repository=registry.liquidrinu.com/fusero-backend \
--set frontend.image.repository=registry.liquidrinu.com/fusero-frontend --set frontend.image.repository=registry.liquidrinu.com/fusero-frontend

16
.gitignore vendored

@ -125,20 +125,20 @@ values.dev.*
values.prod.* values.prod.*
# Secrets # Secrets
chart/secrets.prod.yml chart/secrets.prod.yaml
chart/secrets.*.yml chart/secrets.*.yaml
*.env *.env
.env.* .env.*
# Development values with secrets # Development values with secrets
chart/values.dev.yml chart/values.dev.yaml
# Production secrets # Production secrets
chart/secrets.prod.yml chart/secrets.prod.yaml
chart/secrets.*.yml chart/secrets.*.yaml
# Keep templates and public configs # Keep templates and public configs
!chart/secrets.prod.template.yml !chart/secrets.prod.template.yaml
!chart/values.prod.template.yml !chart/values.prod.template.yaml
!chart/values.prod.public.yml !chart/values.prod.public.yaml

106
README.md

@ -17,7 +17,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend
- [To seed the database:](#to-seed-the-database) - [To seed the database:](#to-seed-the-database)
- [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals) - [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals)
- [🛠️ Environment Setup](#-environment-setup) - [🛠️ Environment Setup](#-environment-setup)
- [For Kubernetes, these are set in chart/values.yml:](#for-kubernetes-these-are-set-in-chartvaluesyml) - [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml)
- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db) - [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db)
- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service) - [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service)
- [POSTGRES\_PORT=19095](#postgres_port19095) - [POSTGRES\_PORT=19095](#postgres_port19095)
@ -53,6 +53,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend
- [Port-Forwarding for Local Access](#port-forwarding-for-local-access) - [Port-Forwarding for Local Access](#port-forwarding-for-local-access)
- [Frontend (React app)](#frontend-react-app) - [Frontend (React app)](#frontend-react-app)
- [Backend (API)](#backend-api) - [Backend (API)](#backend-api)
- [Database](#database)
- [NGINX Backend Service Name: Docker Compose vs Kubernetes](#nginx-backend-service-name-docker-compose-vs-kubernetes) - [NGINX Backend Service Name: Docker Compose vs Kubernetes](#nginx-backend-service-name-docker-compose-vs-kubernetes)
- [How to update the NGINX config for Kubernetes](#how-to-update-the-nginx-config-for-kubernetes) - [How to update the NGINX config for Kubernetes](#how-to-update-the-nginx-config-for-kubernetes)
- [Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes](#cleaning-up-duplicate-or-crashing-deployments-and-pods-in-kubernetes) - [Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes](#cleaning-up-duplicate-or-crashing-deployments-and-pods-in-kubernetes)
@ -79,6 +80,7 @@ A full-stack application boilerplate with a React frontend and Node.js backend
- [Database Backup](#database-backup) - [Database Backup](#database-backup)
- [CI/CD \& Automated Testing](#cicd--automated-testing) - [CI/CD \& Automated Testing](#cicd--automated-testing)
- [Troubleshooting Production](#troubleshooting-production) - [Troubleshooting Production](#troubleshooting-production)
- [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting)
--- ---
@ -88,7 +90,7 @@ fusero-app-boilerplate/
├── chart/ # Helm chart for Kubernetes ├── chart/ # Helm chart for Kubernetes
│ ├── Chart.yaml │ ├── Chart.yaml
│ ├── values.dev.yaml │ ├── values.dev.yaml
│ ├── values.prod.yml │ ├── values.prod.yaml
│ └── templates/ │ └── templates/
├── config/ ├── config/
├── coverage/ ├── coverage/
@ -102,10 +104,10 @@ fusero-app-boilerplate/
├── node_modules/ ├── node_modules/
├── package.json ├── package.json
├── package-lock.json ├── package-lock.json
├── docker-compose.yml ├── docker-compose.yaml
├── docker-compose.dev.yml ├── docker-compose.dev.yaml
├── .gitignore ├── .gitignore
├── .gitea-ci.yml ├── .gitea-ci.yaml
├── .prettierrc.json ├── .prettierrc.json
├── .eslintrc.json ├── .eslintrc.json
├── architecture.excalidraw ├── architecture.excalidraw
@ -192,7 +194,7 @@ POSTGRES_USER=root
POSTGRES_PASSWORD=root123 POSTGRES_PASSWORD=root123
JWT_SECRET=your_jwt_secret_key_here JWT_SECRET=your_jwt_secret_key_here
# For Kubernetes, these are set in chart/values.yml: # For Kubernetes, these are set in chart/values.yaml:
# POSTGRES_NAME=fusero-boilerplate-db # POSTGRES_NAME=fusero-boilerplate-db
# POSTGRES_HOSTNAME=postgres-service # POSTGRES_HOSTNAME=postgres-service
# POSTGRES_PORT=19095 # POSTGRES_PORT=19095
@ -248,7 +250,6 @@ docker exec -it fusero-app-backend npm run migrate
docker exec -it fusero-app-backend npm run seed docker exec -it fusero-app-backend npm run seed
3. Ensure all required environment variables are configured. 3. Ensure all required environment variables are configured.
Never commit `.env` files.
--- ---
@ -275,7 +276,7 @@ Wrong: to="/dashboard/canvas/canvas-endpoints"
Generate a self-signed cert: Generate a self-signed cert:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt
Ensure `docker-compose.yml` mounts the certs: Ensure `docker-compose.yaml` mounts the certs:
volumes: volumes:
- ./nginx/ssl:/etc/nginx/ssl - ./nginx/ssl:/etc/nginx/ssl
@ -286,7 +287,7 @@ Configure NGINX to use the cert in production.
## 🧠 Development Best Practices ## 🧠 Development Best Practices
- Always run the DB via Docker - Always run the DB via Docker
- Use `docker-compose.dev.yml` for development - Use `docker-compose.dev.yaml` for development
- Never run PostgreSQL directly on host - Never run PostgreSQL directly on host
- Run frontend and backend separately for hot reload - Run frontend and backend separately for hot reload
- Use `.env.example` as a template - Use `.env.example` as a template
@ -341,8 +342,8 @@ lsof -i :14000
Database Issues: Database Issues:
Ensure DB is in Docker and configured correctly Ensure DB is in Docker and configured correctly
Try restarting: Try restarting:
docker-compose -f docker-compose.dev.yml down docker-compose -f docker-compose.dev.yaml down
docker-compose -f docker-compose.dev.yml up db docker-compose -f docker-compose.dev.yaml up db
CORS Issues: CORS Issues:
Check API base URL in frontend `.env` Check API base URL in frontend `.env`
@ -384,7 +385,7 @@ docker push <your-registry>/fusero-backend-dev:local
### 3. Upgrade the Helm release with the latest values ### 3. Upgrade the Helm release with the latest values
```bash ```bash
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
``` ```
### 4. Restart the backend deployment to pick up new images and env vars ### 4. Restart the backend deployment to pick up new images and env vars
@ -430,7 +431,7 @@ docker push <your-registry>/fusero-frontend-dev:local
### 3. Upgrade the Helm release ### 3. Upgrade the Helm release
```bash ```bash
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
``` ```
### 4. Restart the frontend deployment ### 4. Restart the frontend deployment
@ -446,16 +447,22 @@ To access your services running in Kubernetes from your local machine, use these
### Frontend (React app) ### Frontend (React app)
```bash ```bash
kubectl port-forward -n fusero svc/fusero-frontend-service 3000:80 kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80
``` ```
- Access at: http://localhost:3000 - Access at: http://localhost:3000
### Backend (API) ### Backend (API)
```bash ```bash
kubectl port-forward -n fusero svc/fusero-backend-service 14000:14000 kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000
``` ```
- Access at: http://localhost:14000 - Access at: http://localhost:14000
### Database
```bash
kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432
```
- Access at: localhost:5432
--- ---
## NGINX Backend Service Name: Docker Compose vs Kubernetes ## NGINX Backend Service Name: Docker Compose vs Kubernetes
@ -482,7 +489,7 @@ Then rebuild and redeploy the frontend:
```bash ```bash
docker build -t fusero-frontend-dev:local ./frontend docker build -t fusero-frontend-dev:local ./frontend
# (push if needed) # (push if needed)
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
kubectl rollout restart deployment/fusero-frontend -n fusero kubectl rollout restart deployment/fusero-frontend -n fusero
``` ```
@ -546,7 +553,7 @@ This means NGINX is trying to load an SSL certificate that does not exist in the
3. (If using a remote registry) Push the image. 3. (If using a remote registry) Push the image.
4. Redeploy with Helm: 4. Redeploy with Helm:
```bash ```bash
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
``` ```
5. Check pod status: 5. Check pod status:
```bash ```bash
@ -592,7 +599,7 @@ kubectl create namespace fusero-dev
kubectl config set-context --current --namespace=fusero-dev kubectl config set-context --current --namespace=fusero-dev
# Deploy to development namespace # Deploy to development namespace
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
``` ```
### Production Namespace Setup ### Production Namespace Setup
@ -604,7 +611,7 @@ kubectl create namespace fusero-prod
kubectl config set-context --current --namespace=fusero-prod kubectl config set-context --current --namespace=fusero-prod
# Deploy to production namespace # Deploy to production namespace
helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml
``` ```
### Namespace Management Commands ### Namespace Management Commands
@ -656,8 +663,8 @@ kubectl delete namespace <namespace-name>
You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production. You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production.
### What was changed: ### What was changed:
- All Kubernetes service templates (`backend-service.yml`, `frontend-service.yml`, `postgres-service.yml`) now use a namespace variable. - All Kubernetes service templates (`backend-service.yaml`, `frontend-service.yaml`, `postgres-service.yaml`) now use a namespace variable.
- The development values file (`chart/values.dev.yml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace. - The development values file (`chart/values.dev.yaml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace.
- Helm deployments now target the `fusero-dev` namespace for all dev resources. - Helm deployments now target the `fusero-dev` namespace for all dev resources.
### How to use: ### How to use:
@ -668,7 +675,7 @@ You have configured your Kubernetes development environment to use a dedicated n
2. **Deploy all services to the namespace:** 2. **Deploy all services to the namespace:**
```bash ```bash
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
``` ```
3. **Check running pods and services:** 3. **Check running pods and services:**
@ -681,7 +688,7 @@ You have configured your Kubernetes development environment to use a dedicated n
5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:** 5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:**
```bash ```bash
kubectl delete job <job-name> -n fusero-dev kubectl delete job <job-name> -n fusero-dev
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
``` ```
### Why use namespaces? ### Why use namespaces?
@ -703,7 +710,7 @@ The application uses a secure secrets management approach:
2. **Production Environment**: 2. **Production Environment**:
- Secrets are managed through Gitea CI/CD secrets - Secrets are managed through Gitea CI/CD secrets
- Template file: `chart/secrets.prod.template.yml` - Template file: `chart/secrets.prod.template.yaml`
- Actual secrets are generated during deployment - Actual secrets are generated during deployment
- Never commit actual secrets to the repository - Never commit actual secrets to the repository
@ -716,7 +723,7 @@ The application uses a secure secrets management approach:
4. **Secrets in CI/CD**: 4. **Secrets in CI/CD**:
- Secrets are stored in Gitea CI/CD settings - Secrets are stored in Gitea CI/CD settings
- Automatically injected during deployment - Automatically injected during deployment
- Used to generate `secrets.prod.yml` at runtime - Used to generate `secrets.prod.yaml` at runtime
5. **Security Best Practices**: 5. **Security Best Practices**:
- All secrets files are gitignored - All secrets files are gitignored
@ -757,7 +764,7 @@ The application uses a secure secrets management approach:
- name: Run Tests - name: Run Tests
run: npm test run: npm test
- name: Deploy to Kubernetes - name: Deploy to Kubernetes
run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml
``` ```
### Troubleshooting Production ### Troubleshooting Production
@ -767,3 +774,50 @@ The application uses a secure secrets management approach:
- Rollback: Use `helm rollback fusero <revision> -n fusero-prod`. - Rollback: Use `helm rollback fusero <revision> -n fusero-prod`.
--- ---
## 🆕 Recent Improvements & Troubleshooting
- **Consistent File Extensions:** All Kubernetes and Helm YAML files now use the `.yaml` extension for consistency.
- **Secrets Management:**
- Development secrets are stored in `chart/secrets.dev.yaml` (gitignored).
- Production secrets are generated by CI/CD as `chart/secrets.prod.yaml` from Gitea secrets.
- All values files (`values.dev.yaml`, `values.prod.yaml`) reference secrets via environment variables.
- **CORS Configuration:**
- For local development, set `CORS_ORIGIN: "http://localhost:3000"` in `chart/values.dev.yaml` to allow credentialed requests from the frontend.
- Do **not** use `*` for CORS origin if you need credentials.
- **Kubernetes Job Immutability:**
- If you update environment variables or secrets, you must delete the old migration/seed job before redeploying:
```bash
kubectl delete job fusero-backend-db-init -n fusero-dev
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml -f chart/secrets.dev.yaml
```
- **Port Forwarding for Local Access:**
- Backend:
```bash
kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000
```
- Frontend:
```bash
kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80
```
- Database:
```bash
kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432
```
- **Testing Login with curl:**
```bash
curl -i -X POST http://localhost:14000/api/v1/app/users/login \
-H "Content-Type: application/json" \
-H "Origin: http://localhost:3000" \
-d '{"username":"admin","password":"admin123"}'
```
- **Troubleshooting 401 Errors:**
- If login fails after a redeploy:
- Ensure secrets and values are in sync.
- Re-run the seed job as above.
- Check backend logs for authentication errors:
```bash
kubectl logs -n fusero-dev -l app=fusero-backend --tail=100
```
---

6
chart/Chart.yaml Normal file

@ -0,0 +1,6 @@
apiVersion: v2
name: fusero
description: Fusero App Boilerplate Helm Chart
type: application
version: 0.1.0
appVersion: "1.0.0"

@ -1,6 +1,6 @@
apiVersion: v2 apiVersion: v2
name: fusero name: fusero
description: Fusero application Helm chart description: Fusero App Boilerplate Helm Chart
type: application type: application
version: 0.1.0 version: 0.1.0
appVersion: "1.0.0" appVersion: "1.0.0"

@ -1,26 +0,0 @@
backend:
env:
# Database
POSTGRES_NAME: "fusero-boilerplate-db"
POSTGRES_HOSTNAME: "postgres-service.fusero-prod.svc.cluster.local"
POSTGRES_PORT: "5432"
POSTGRES_USER: "root"
POSTGRES_PASSWORD: "REPLACE_ME"
# Admin User
DEFAULT_ADMIN_USERNAME: "admin"
DEFAULT_ADMIN_EMAIL: "admin@fusero.nl"
DEFAULT_ADMIN_PASSWORD: "REPLACE_ME"
# Security
ENCRYPTION_KEY: "REPLACE_ME"
JWT_SECRET: "REPLACE_ME"
# API Keys
CHATGPT_API_KEY: "REPLACE_ME"
CANVAS_API_KEY: "REPLACE_ME"
CANVAS_API_URL: "https://talnet.instructure.com/api/v1"
# Server
FASTIFY_PORT: "14000"
NODE_ENV: "production"

@ -0,0 +1,18 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
pg_hba.conf: |
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
host all all 0.0.0.0/0 md5
postgresql.conf: |
listen_addresses = '*'
max_connections = 100
shared_buffers = 128MB
dynamic_shared_memory_type = posix
max_wal_size = 1GB
min_wal_size = 80MB

@ -27,7 +27,16 @@ spec:
volumeMounts: volumeMounts:
- mountPath: /var/lib/postgresql/data - mountPath: /var/lib/postgresql/data
name: postgres-data name: postgres-data
- mountPath: /etc/postgresql/pg_hba.conf
name: postgres-config
subPath: pg_hba.conf
- mountPath: /etc/postgresql/postgresql.conf
name: postgres-config
subPath: postgresql.conf
volumes: volumes:
- name: postgres-data - name: postgres-data
persistentVolumeClaim: persistentVolumeClaim:
claimName: postgres-pvc-fresh claimName: postgres-pvc-fresh
- name: postgres-config
configMap:
name: postgres-config

@ -1,64 +0,0 @@
global:
namespace: fusero-prod
security:
cors:
origin: "https://app.fusero.nl"
methods: "GET,POST,PUT,DELETE"
credentials: true
https:
enabled: true
certSecret: "fusero-tls-secret"
logging:
level: "info"
format: "json"
monitoring:
prometheus:
enabled: true
path: "/metrics"
backend:
image: registry.liquidrinu.com/fusero-backend:latest
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
POSTGRES_HOST: postgres-service
POSTGRES_PORT: "5432"
POSTGRES_NAME: fusero-db
POSTGRES_USER: prod_admin
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DEFAULT_ADMIN_USERNAME: admin
DEFAULT_ADMIN_EMAIL: admin@fusero.nl
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
JWT_SECRET: ${JWT_SECRET}
CHATGPT_API_KEY: ${CHATGPT_API_KEY}
CANVAS_API_KEY: ${CANVAS_API_KEY}
CANVAS_API_URL: https://talnet.instructure.com/api/v1
frontend:
image: registry.liquidrinu.com/fusero-frontend:latest
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "300m"
memory: "256Mi"
env:
VITE_API_BASE_URL: https://app.fusero.nl
postgres:
image: postgres:15
storage: 5Gi
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

@ -29,7 +29,7 @@
"migrate": "npx mikro-orm migration:up", "migrate": "npx mikro-orm migration:up",
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts", "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts",
"app:generate": "node ./utils/generate-app.js", "app:generate": "node ./utils/generate-app.js",
"k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml", "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yaml",
"k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true", "k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true",
"k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80", "k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80",
"k8s:dev:describe": "kubectl describe pod -l app=fusero-frontend", "k8s:dev:describe": "kubectl describe pod -l app=fusero-frontend",