diff --git a/helm/warehouse13/ARCHITECTURE.md b/helm/warehouse13/ARCHITECTURE.md new file mode 100644 index 0000000..bec85b5 --- /dev/null +++ b/helm/warehouse13/ARCHITECTURE.md @@ -0,0 +1,281 @@ +# Warehouse13 Architecture + +## Overview + +Warehouse13 uses a **unified application container** that includes both the frontend and backend in a single Docker image using a multi-stage build. + +## Docker Build Strategy + +### Multi-Stage Dockerfile + +```dockerfile +# Stage 1: Build Angular Frontend +FROM node:24-alpine AS frontend-build +WORKDIR /frontend +COPY frontend/package*.json ./ +RUN npm install +COPY frontend/ ./ +RUN npm run build:prod + +# Stage 2: Python Backend with Static Frontend +FROM python:3.11-alpine +WORKDIR /app +# Install Python dependencies +COPY requirements.txt . +RUN pip install -r requirements.txt +# Copy backend code +COPY app/ ./app/ +# Copy built frontend from stage 1 +COPY --from=frontend-build /frontend/dist/frontend/browser ./static/ +# Run FastAPI server +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +``` + +### Benefits + +1. **Simplified Deployment** - Single container to manage +2. **Reduced Resource Usage** - No separate nginx container needed +3. **Easier Scaling** - Scale one deployment instead of two +4. **Consistent Versioning** - Frontend and backend versions always match +5. **Faster Deployments** - Fewer containers to orchestrate + +## Service Architecture + +``` +┌─────────────────────────────────────────┐ +│ warehouse13-app │ +│ ┌────────────────────────────────────┐ │ +│ │ FastAPI Backend (Port 8000) │ │ +│ │ ├── /api/* → REST API │ │ +│ │ ├── /health → Health check │ │ +│ │ ├── /docs → API documentation │ │ +│ │ └── /* → Angular SPA │ │ +│ │ │ │ +│ │ Static Files: /static/ │ │ +│ │ └── Angular build output │ │ +│ └────────────────────────────────────┘ │ +└─────────────────────────────────────────┘ + │ + ├────────────────┐ + ↓ ↓ + ┌──────────┐ ┌────────────┐ + │PostgreSQL│ │ MinIO │ + │(Metadata)│ │ (Blobs) │ + └──────────┘ └────────────┘ +``` + +## Helm Chart Structure + +### Single Application Deployment + +The Helm chart creates: + +1. **1 Deployment**: `warehouse13-app` + - Runs the unified container + - Configurable replicas (default: 2) + - Health checks on `/health` endpoint + +2. **1 Service**: `warehouse13-app` + - Exposes port 8000 + - Routes all traffic to the application + +3. **Optional Ingress** + - All paths route to `warehouse13-app:8000` + - FastAPI handles routing internally + +### Kubernetes Resources + +```yaml +# warehouse13-app Deployment +- Replicas: 2 (configurable) +- Port: 8000 +- Health checks: /health +- Environment: DATABASE_URL, MINIO_* vars + +# warehouse13-app Service +- Type: ClusterIP +- Port: 8000 → 8000 + +# Ingress (optional) +- Path: / → warehouse13-app:8000 +``` + +## Configuration + +### Image Configuration + +In `values.yaml`: + +```yaml +app: + enabled: true + image: + repository: warehouse13/app # Single unified image + tag: latest + pullPolicy: IfNotPresent + replicas: 2 + resources: + requests: + memory: "384Mi" # Combined frontend + backend + cpu: "350m" + limits: + memory: "768Mi" + cpu: "750m" +``` + +### Accessing the Application + +**Via Port Forward:** +```bash +kubectl port-forward svc/warehouse13-app 8000:8000 +``` + +Then access: +- Frontend: http://localhost:8000 +- API: http://localhost:8000/api +- API Docs: http://localhost:8000/docs +- Health: http://localhost:8000/health + +**Via Ingress:** +```yaml +ingress: + enabled: true + hosts: + - host: warehouse13.example.com + paths: + - path: / + pathType: Prefix + backend: app # All traffic to one service +``` + +## Migration from Separate Services + +If you previously had separate `api` and `frontend` deployments: + +### Before (Old Architecture) +```yaml +# values.yaml (old) +api: + image: warehouse13/api + replicas: 2 + +frontend: + image: warehouse13/frontend + replicas: 2 + +# Two deployments, two services +``` + +### After (Current Architecture) +```yaml +# values.yaml (current) +app: + image: warehouse13/app # Unified image + replicas: 2 + +# One deployment, one service +``` + +### Migration Steps + +1. **Update values.yaml** - Change from `api`/`frontend` to `app` +2. **Update image references** - Use `warehouse13/app` instead of separate images +3. **Update ingress** - Point all paths to `app` backend +4. **Deploy** - Helm will handle the transition +5. **Verify** - Check that both frontend and API work through single service + +## Development Workflow + +### Building the Image + +```bash +# Build unified image +docker build -t warehouse13/app:dev . + +# Or for air-gapped environments with custom registry +docker build \ + --build-arg NPM_REGISTRY=https://registry.npmjs.org/ \ + -t warehouse13/app:v1.0.0 . +``` + +### Testing Locally + +```bash +docker run -p 8000:8000 \ + -e DATABASE_URL=postgresql://user:pass@host/db \ + -e MINIO_ENDPOINT=minio:9000 \ + warehouse13/app:dev +``` + +Access: +- Frontend: http://localhost:8000 +- API: http://localhost:8000/docs + +## Performance Considerations + +### Resource Allocation + +The unified container combines both frontend serving and API processing: + +- **Memory**: Angular assets (~50MB) + Python runtime (~100MB) + working memory +- **CPU**: Primarily used for API requests; static file serving is lightweight +- **Recommended Minimum**: 384Mi memory, 350m CPU +- **Production**: 768Mi memory, 750m CPU per replica + +### Scaling Strategy + +Scale horizontally by increasing replicas: + +```bash +# Scale to 5 replicas +kubectl scale deployment warehouse13-app --replicas=5 + +# Or via Helm +helm upgrade warehouse13 ./helm/warehouse13 --set app.replicas=5 +``` + +### Caching + +FastAPI serves static files efficiently with: +- ETag support +- Browser caching headers +- Gzip compression (if enabled in FastAPI config) + +## Troubleshooting + +### Frontend Not Loading + +```bash +# Check if static files exist in container +kubectl exec -it warehouse13-app-xxx -- ls -la /app/static/ + +# Should see: index.html, *.js, *.css files +``` + +### API Not Working + +```bash +# Check API health +kubectl exec -it warehouse13-app-xxx -- curl http://localhost:8000/health + +# Check logs +kubectl logs warehouse13-app-xxx -f +``` + +### Both Frontend and API Issues + +```bash +# Check if app is running +kubectl get pods -l app.kubernetes.io/component=app + +# Check service +kubectl get svc warehouse13-app + +# Test connectivity +kubectl port-forward svc/warehouse13-app 8000:8000 +curl http://localhost:8000/health +``` + +## Summary + +The unified architecture simplifies deployment and operations while maintaining the same functionality. All routing, caching, and API requests are handled by a single FastAPI application that serves both the Angular SPA and the REST API endpoints. diff --git a/helm/warehouse13/templates/NOTES.txt b/helm/warehouse13/templates/NOTES.txt index 602c65a..ba1371d 100644 --- a/helm/warehouse13/templates/NOTES.txt +++ b/helm/warehouse13/templates/NOTES.txt @@ -19,18 +19,13 @@ Namespace: {{ .Release.Namespace }} DEPLOYMENT INFORMATION: -{{- if .Values.frontend.enabled }} -Frontend: - Service: warehouse13-frontend - Replicas: {{ .Values.frontend.replicas }} - Image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }} -{{- end }} - -{{- if .Values.api.enabled }} -API: - Service: warehouse13-api - Replicas: {{ .Values.api.replicas }} - Image: {{ .Values.api.image.repository }}:{{ .Values.api.image.tag }} +{{- if .Values.app.enabled }} +Application (Unified API + Frontend): + Service: warehouse13-app + Replicas: {{ .Values.app.replicas }} + Image: {{ .Values.app.image.repository }}:{{ .Values.app.image.tag }} + Port: {{ .Values.app.service.port }} + Note: Multi-stage build includes both Angular frontend and FastAPI backend {{- end }} {{- if .Values.postgres.enabled }} @@ -62,13 +57,12 @@ ACCESSING YOUR APPLICATION: 1. Using Port Forwarding: - # Frontend - kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-frontend 4200:80 - Then visit: http://localhost:4200 - - # API - kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-api 8000:8000 - Then visit: http://localhost:8000/docs + # Application (Frontend + API) + kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-app 8000:8000 + Then visit: + - Frontend: http://localhost:8000 + - API Docs: http://localhost:8000/docs + - Health: http://localhost:8000/health # MinIO Console kubectl port-forward -n {{ .Release.Namespace }} svc/warehouse13-minio 9001:9001 @@ -91,8 +85,7 @@ CHECKING STATUS: kubectl get svc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} # View logs - kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=api -f - kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=frontend -f + kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=app -f --- diff --git a/helm/warehouse13/templates/api-deployment.yaml b/helm/warehouse13/templates/app-deployment.yaml similarity index 69% rename from helm/warehouse13/templates/api-deployment.yaml rename to helm/warehouse13/templates/app-deployment.yaml index d8bdb1a..fdf32a7 100644 --- a/helm/warehouse13/templates/api-deployment.yaml +++ b/helm/warehouse13/templates/app-deployment.yaml @@ -1,32 +1,32 @@ -{{- if .Values.api.enabled }} +{{- if .Values.app.enabled }} apiVersion: apps/v1 kind: Deployment metadata: - name: warehouse13-api + name: warehouse13-app labels: {{- include "warehouse13.labels" . | nindent 4 }} - app.kubernetes.io/component: api + app.kubernetes.io/component: app spec: - replicas: {{ .Values.api.replicas }} + replicas: {{ .Values.app.replicas }} selector: matchLabels: {{- include "warehouse13.selectorLabels" . | nindent 6 }} - app.kubernetes.io/component: api + app.kubernetes.io/component: app template: metadata: labels: {{- include "warehouse13.selectorLabels" . | nindent 8 }} - app.kubernetes.io/component: api + app.kubernetes.io/component: app spec: serviceAccountName: {{ include "warehouse13.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - - name: api + - name: app securityContext: {{- toYaml .Values.securityContext | nindent 10 }} - image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" - imagePullPolicy: {{ .Values.api.image.pullPolicy }} + image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}" + imagePullPolicy: {{ .Values.app.image.pullPolicy }} ports: - name: http containerPort: 8000 @@ -57,26 +57,30 @@ spec: secretKeyRef: name: warehouse13-secrets key: minio-root-password + - name: MINIO_BUCKET_NAME + value: "test-artifacts" + - name: MINIO_SECURE + value: "false" - name: DEPLOYMENT_MODE valueFrom: configMapKeyRef: name: warehouse13-config key: DEPLOYMENT_MODE resources: - {{- toYaml .Values.api.resources | nindent 10 }} - {{- if .Values.api.healthCheck.enabled }} + {{- toYaml .Values.app.resources | nindent 10 }} + {{- if .Values.app.healthCheck.enabled }} livenessProbe: httpGet: - path: {{ .Values.api.healthCheck.liveness.path }} + path: {{ .Values.app.healthCheck.liveness.path }} port: http - initialDelaySeconds: {{ .Values.api.healthCheck.liveness.initialDelaySeconds }} - periodSeconds: {{ .Values.api.healthCheck.liveness.periodSeconds }} + initialDelaySeconds: {{ .Values.app.healthCheck.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.app.healthCheck.liveness.periodSeconds }} readinessProbe: httpGet: - path: {{ .Values.api.healthCheck.readiness.path }} + path: {{ .Values.app.healthCheck.readiness.path }} port: http - initialDelaySeconds: {{ .Values.api.healthCheck.readiness.initialDelaySeconds }} - periodSeconds: {{ .Values.api.healthCheck.readiness.periodSeconds }} + initialDelaySeconds: {{ .Values.app.healthCheck.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.app.healthCheck.readiness.periodSeconds }} {{- end }} {{- with .Values.nodeSelector }} nodeSelector: diff --git a/helm/warehouse13/templates/api-service.yaml b/helm/warehouse13/templates/app-service.yaml similarity index 55% rename from helm/warehouse13/templates/api-service.yaml rename to helm/warehouse13/templates/app-service.yaml index 34a5144..9a462ee 100644 --- a/helm/warehouse13/templates/api-service.yaml +++ b/helm/warehouse13/templates/app-service.yaml @@ -1,19 +1,19 @@ -{{- if .Values.api.enabled }} +{{- if .Values.app.enabled }} apiVersion: v1 kind: Service metadata: - name: warehouse13-api + name: warehouse13-app labels: {{- include "warehouse13.labels" . | nindent 4 }} - app.kubernetes.io/component: api + app.kubernetes.io/component: app spec: - type: {{ .Values.api.service.type }} + type: {{ .Values.app.service.type }} ports: - - port: {{ .Values.api.service.port }} + - port: {{ .Values.app.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "warehouse13.selectorLabels" . | nindent 4 }} - app.kubernetes.io/component: api + app.kubernetes.io/component: app {{- end }} diff --git a/helm/warehouse13/templates/frontend-deployment.yaml b/helm/warehouse13/templates/frontend-deployment.yaml deleted file mode 100644 index 7ed9c89..0000000 --- a/helm/warehouse13/templates/frontend-deployment.yaml +++ /dev/null @@ -1,73 +0,0 @@ -{{- if .Values.frontend.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: warehouse13-frontend - labels: - {{- include "warehouse13.labels" . | nindent 4 }} - app.kubernetes.io/component: frontend -spec: - replicas: {{ .Values.frontend.replicas }} - selector: - matchLabels: - {{- include "warehouse13.selectorLabels" . | nindent 6 }} - app.kubernetes.io/component: frontend - template: - metadata: - labels: - {{- include "warehouse13.selectorLabels" . | nindent 8 }} - app.kubernetes.io/component: frontend - spec: - serviceAccountName: {{ include "warehouse13.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: frontend - securityContext: - {{- toYaml .Values.securityContext | nindent 10 }} - image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" - imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} - ports: - - name: http - containerPort: 80 - protocol: TCP - env: - - name: DEPLOYMENT_MODE - valueFrom: - configMapKeyRef: - name: warehouse13-config - key: DEPLOYMENT_MODE - - name: STORAGE_BACKEND - valueFrom: - configMapKeyRef: - name: warehouse13-config - key: STORAGE_BACKEND - resources: - {{- toYaml .Values.frontend.resources | nindent 10 }} - {{- if .Values.frontend.healthCheck.enabled }} - livenessProbe: - httpGet: - path: {{ .Values.frontend.healthCheck.liveness.path }} - port: http - initialDelaySeconds: {{ .Values.frontend.healthCheck.liveness.initialDelaySeconds }} - periodSeconds: {{ .Values.frontend.healthCheck.liveness.periodSeconds }} - readinessProbe: - httpGet: - path: {{ .Values.frontend.healthCheck.readiness.path }} - port: http - initialDelaySeconds: {{ .Values.frontend.healthCheck.readiness.initialDelaySeconds }} - periodSeconds: {{ .Values.frontend.healthCheck.readiness.periodSeconds }} - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/helm/warehouse13/templates/frontend-service.yaml b/helm/warehouse13/templates/frontend-service.yaml deleted file mode 100644 index 256356c..0000000 --- a/helm/warehouse13/templates/frontend-service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.frontend.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: warehouse13-frontend - labels: - {{- include "warehouse13.labels" . | nindent 4 }} - app.kubernetes.io/component: frontend -spec: - type: {{ .Values.frontend.service.type }} - ports: - - port: {{ .Values.frontend.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "warehouse13.selectorLabels" . | nindent 4 }} - app.kubernetes.io/component: frontend -{{- end }} diff --git a/helm/warehouse13/templates/ingress.yaml b/helm/warehouse13/templates/ingress.yaml index 36ba3c6..50818bd 100644 --- a/helm/warehouse13/templates/ingress.yaml +++ b/helm/warehouse13/templates/ingress.yaml @@ -35,7 +35,7 @@ spec: service: name: {{ printf "warehouse13-%s" .backend }} port: - number: {{ if eq .backend "frontend" }}{{ $.Values.frontend.service.port }}{{ else }}{{ $.Values.api.service.port }}{{ end }} + number: {{ $.Values.app.service.port }} {{- end }} {{- end }} {{- end }} diff --git a/helm/warehouse13/values-airgapped.yaml b/helm/warehouse13/values-airgapped.yaml index 7bea087..41ae305 100644 --- a/helm/warehouse13/values-airgapped.yaml +++ b/helm/warehouse13/values-airgapped.yaml @@ -50,37 +50,21 @@ minio: memory: "2Gi" cpu: "1000m" -# API with custom registry -api: +# Application with custom registry (unified API + Frontend) +app: enabled: true image: - repository: harbor.internal.example.com/warehouse13/api + repository: harbor.internal.example.com/warehouse13/app tag: v1.0.0 pullPolicy: IfNotPresent replicas: 2 resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "768Mi" + cpu: "750m" limits: - memory: "1Gi" - cpu: "1000m" - -# Frontend with custom registry -frontend: - enabled: true - image: - repository: harbor.internal.example.com/warehouse13/frontend - tag: v1.0.0 - pullPolicy: IfNotPresent - replicas: 2 - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "512Mi" - cpu: "500m" + memory: "1536Mi" + cpu: "1500m" # Ingress disabled for air-gapped - use NodePort or port-forward ingress: diff --git a/helm/warehouse13/values-dev.yaml b/helm/warehouse13/values-dev.yaml index 0baf692..26a74fa 100644 --- a/helm/warehouse13/values-dev.yaml +++ b/helm/warehouse13/values-dev.yaml @@ -44,37 +44,20 @@ minio: memory: "512Mi" cpu: "250m" -api: +app: enabled: true image: - repository: warehouse13/api + repository: warehouse13/app tag: dev pullPolicy: Always # Always pull latest dev image replicas: 1 resources: requests: - memory: "256Mi" - cpu: "250m" + memory: "384Mi" + cpu: "350m" limits: - memory: "512Mi" - cpu: "500m" - healthCheck: - enabled: true - -frontend: - enabled: true - image: - repository: warehouse13/frontend - tag: dev - pullPolicy: Always # Always pull latest dev image - replicas: 1 - resources: - requests: - memory: "128Mi" - cpu: "100m" - limits: - memory: "256Mi" - cpu: "250m" + memory: "768Mi" + cpu: "750m" healthCheck: enabled: true diff --git a/helm/warehouse13/values-production.yaml b/helm/warehouse13/values-production.yaml index d7ab719..5f7a480 100644 --- a/helm/warehouse13/values-production.yaml +++ b/helm/warehouse13/values-production.yaml @@ -48,37 +48,20 @@ minio: memory: "4Gi" cpu: "2000m" -api: +app: enabled: true image: - repository: warehouse13/api + repository: warehouse13/app tag: v1.0.0 pullPolicy: IfNotPresent replicas: 3 resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "768Mi" + cpu: "750m" limits: - memory: "1Gi" - cpu: "1000m" - healthCheck: - enabled: true - -frontend: - enabled: true - image: - repository: warehouse13/frontend - tag: v1.0.0 - pullPolicy: IfNotPresent - replicas: 3 - resources: - requests: - memory: "256Mi" - cpu: "250m" - limits: - memory: "512Mi" - cpu: "500m" + memory: "1536Mi" + cpu: "1500m" healthCheck: enabled: true @@ -94,10 +77,7 @@ ingress: paths: - path: / pathType: Prefix - backend: frontend - - path: /api - pathType: Prefix - backend: api + backend: app tls: - secretName: warehouse13-tls hosts: diff --git a/helm/warehouse13/values.yaml b/helm/warehouse13/values.yaml index 7cd4ce6..6473597 100644 --- a/helm/warehouse13/values.yaml +++ b/helm/warehouse13/values.yaml @@ -58,11 +58,14 @@ minio: apiPort: 9000 consolePort: 9001 -# API Backend -api: +# Application (Unified API + Frontend) +# The application uses a multi-stage Docker build: +# - Stage 1: Builds Angular frontend +# - Stage 2: Python FastAPI backend that serves the frontend from /static +app: enabled: true image: - repository: warehouse13/api + repository: warehouse13/app tag: latest pullPolicy: IfNotPresent replicas: 2 @@ -72,11 +75,11 @@ api: minioEndpoint: "warehouse13-minio:9000" resources: requests: - memory: "256Mi" - cpu: "250m" + memory: "384Mi" + cpu: "350m" limits: - memory: "512Mi" - cpu: "500m" + memory: "768Mi" + cpu: "750m" service: type: ClusterIP port: 8000 @@ -87,39 +90,10 @@ api: initialDelaySeconds: 30 periodSeconds: 10 readiness: - path: /ready + path: /health initialDelaySeconds: 10 periodSeconds: 5 -# Frontend -frontend: - enabled: true - image: - repository: warehouse13/frontend - tag: latest - pullPolicy: IfNotPresent - replicas: 2 - resources: - requests: - memory: "128Mi" - cpu: "100m" - limits: - memory: "256Mi" - cpu: "250m" - service: - type: ClusterIP - port: 80 - healthCheck: - enabled: true - liveness: - path: / - initialDelaySeconds: 10 - periodSeconds: 10 - readiness: - path: / - initialDelaySeconds: 5 - periodSeconds: 5 - # Ingress ingress: enabled: false @@ -131,10 +105,7 @@ ingress: paths: - path: / pathType: Prefix - backend: frontend - - path: /api - pathType: Prefix - backend: api + backend: app # All traffic goes to unified app (serves both API and frontend) tls: [] # - secretName: warehouse13-tls # hosts: