diff --git a/README.md b/README.md new file mode 100644 index 00000000..3094c5b2 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# 🍞 Bakery IA - Multi-Service Architecture + +Welcome to Bakery IA, an advanced AI-powered platform for bakery management and optimization. This project implements a microservices architecture with multiple interconnected services to provide comprehensive bakery management solutions. + +## 🚀 Quick Start + +### Prerequisites +- Docker Desktop with Kubernetes enabled +- Docker Compose +- Node.js (for frontend development) + +### Running the Application + +1. **Clone the repository:** + ```bash + git clone + cd bakery-ia + ``` + +2. **Set up environment variables:** + ```bash + cp .env.example .env + # Edit .env with your specific configuration + ``` + +3. **Run with Docker Compose:** + ```bash + docker-compose up --build + ``` + +4. **Or run with Kubernetes (Docker Desktop):** + ```bash + # Enable Kubernetes in Docker Desktop + # Run the setup script + ./scripts/setup-kubernetes-dev.sh + ``` + +## 🏗️ Architecture Overview + +The project follows a microservices architecture with the following main components: + +- **Frontend**: React-based dashboard for user interaction +- **Gateway**: API gateway handling authentication and routing +- **Services**: Multiple microservices handling different business domains +- **Infrastructure**: Redis, RabbitMQ, PostgreSQL databases + +## 🐳 Kubernetes Infrastructure + +## 🛠️ Services + +The project includes multiple services: + +- **Auth Service**: Authentication and authorization +- **Tenant Service**: Multi-tenancy management +- **Sales Service**: Sales processing +- **External Service**: Integration with external systems +- **Training Service**: AI model training +- **Forecasting Service**: Demand forecasting +- **Notification Service**: Notifications and alerts +- **Inventory Service**: Inventory management +- **Recipes Service**: Recipe management +- **Suppliers Service**: Supplier management +- **POS Service**: Point of sale +- **Orders Service**: Order management +- **Production Service**: Production planning +- **Alert Processor**: Background alert processing + +## 📊 Monitoring + +The system includes comprehensive monitoring with: +- Prometheus for metrics collection +- Grafana for visualization +- ELK stack for logging (planned) + +## 🚀 Production Deployment + +For production deployment on clouding.io with Kubernetes: + +1. Set up your clouding.io Kubernetes cluster +2. Update image references to your container registry +3. Configure production-specific values +4. Deploy using the production kustomization: + ```bash + kubectl apply -k infrastructure/kubernetes/environments/production/ + ``` + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## 📄 License + +This project is licensed under the MIT License. diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..29619177 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,71 @@ +# Production Dockerfile for Frontend with Nginx +# Multi-stage build for optimal size and performance + +# Stage 1: Build the application +FROM node:18-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies including dev dependencies for building +RUN npm ci --verbose && \ + npm cache clean --force + +# Copy source code +COPY . . + +# Build the application for production +RUN npm run build + +# Stage 2: Production server with Nginx +FROM nginx:1.25-alpine AS production + +# Install curl for health checks +RUN apk add --no-cache curl + +# Remove default nginx configuration +RUN rm /etc/nginx/conf.d/default.conf + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/conf.d/ + +# Copy built application from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Nginx user already exists in the base image, just ensure proper ownership + +# Set proper permissions +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /etc/nginx/conf.d + +# Create nginx PID directory and fix permissions +RUN mkdir -p /var/run/nginx /var/lib/nginx/tmp && \ + chown -R nginx:nginx /var/run/nginx /var/lib/nginx + +# Custom nginx.conf for running as non-root +RUN echo 'pid /var/run/nginx/nginx.pid;' > /etc/nginx/nginx.conf && \ + echo 'events { worker_connections 1024; }' >> /etc/nginx/nginx.conf && \ + echo 'http {' >> /etc/nginx/nginx.conf && \ + echo ' include /etc/nginx/mime.types;' >> /etc/nginx/nginx.conf && \ + echo ' default_type application/octet-stream;' >> /etc/nginx/nginx.conf && \ + echo ' sendfile on;' >> /etc/nginx/nginx.conf && \ + echo ' keepalive_timeout 65;' >> /etc/nginx/nginx.conf && \ + echo ' include /etc/nginx/conf.d/*.conf;' >> /etc/nginx/nginx.conf && \ + echo '}' >> /etc/nginx/nginx.conf + +# Switch to non-root user +USER nginx + +# Expose port 3000 (to match current setup) +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:3000/ || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 149fec06..a4de780e 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,13 +1,20 @@ -# frontend/nginx.conf -events { - worker_connections 1024; -} +# Nginx configuration for Bakery IA Frontend +# This file is used inside the container at /etc/nginx/conf.d/default.conf -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Enable gzip compression +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://js.stripe.com; script-src-elem 'self' 'unsafe-inline' https://js.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://localhost:8000 http://localhost:8006 ws: wss:; frame-src https://js.stripe.com;" always; + + # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; @@ -24,47 +31,83 @@ http { application/atom+xml image/svg+xml; - server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; + # API proxy to gateway service (Kubernetes internal name) + location /api/ { + proxy_pass http://gateway:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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_cache_bypass $http_upgrade; + proxy_read_timeout 86400; - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; + # CORS headers for API requests + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; - # Handle React Router - location / { - try_files $uri $uri/ /index.html; - } - - # API proxy to backend - location /api/ { - proxy_pass http://bakery-gateway:8000/api/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - 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_cache_bypass $http_upgrade; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - # Health check endpoint - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; + # Handle preflight requests + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain; charset=utf-8'; + add_header Content-Length 0; + return 204; } } -} \ No newline at end of file + + # Static assets with aggressive caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header Vary Accept-Encoding; + access_log off; + try_files $uri @fallback; + } + + # Special handling for PWA assets + location ~* \.(webmanifest|manifest\.json)$ { + expires 1d; + add_header Cache-Control "public"; + add_header Content-Type application/manifest+json; + } + + location = /sw.js { + expires 1d; + add_header Cache-Control "public"; + add_header Content-Type application/javascript; + } + + # Main location block for SPA routing + location / { + try_files $uri $uri/ @fallback; + } + + # Fallback for SPA routing - serve index.html + location @fallback { + rewrite ^.*$ /index.html last; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # Logging + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 00000000..30ddfae1 --- /dev/null +++ b/frontend/public/favicon.ico @@ -0,0 +1 @@ +🍞 diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 7d375e6d..379619f9 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -3,8 +3,8 @@ const urlsToCache = [ '/', '/index.html', '/manifest.json', - '/icons/icon-192.png', - '/icons/icon-512.png', + '/manifest.webmanifest', + '/favicon.ico', ]; // Install event - cache assets @@ -58,12 +58,28 @@ self.addEventListener('fetch', (event) => { return; } - // Static assets - cache first, network fallback - event.respondWith( - caches.match(event.request).then((response) => { - return response || fetch(event.request); - }) - ); + // Static assets - network first, cache fallback (for versioned assets) + if (event.request.destination === 'script' || event.request.destination === 'style' || event.request.destination === 'image') { + event.respondWith( + fetch(event.request).then((response) => { + // Clone the response before caching + const responseToCache = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, responseToCache); + }); + return response; + }).catch(() => { + return caches.match(event.request); + }) + ); + } else { + // Other requests - cache first, network fallback + event.respondWith( + caches.match(event.request).then((response) => { + return response || fetch(event.request); + }) + ); + } }); // Background sync for offline actions @@ -120,4 +136,4 @@ async function syncInventoryData() { } catch (error) { console.error('Sync failed:', error); } -} \ No newline at end of file +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index dced84e3..5ca332e9 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -7,19 +7,7 @@ import './styles/animations.css'; import './styles/themes/light.css'; import './styles/themes/dark.css'; -// Register service worker for PWA -if ('serviceWorker' in navigator && import.meta.env.VITE_ENABLE_PWA === 'true') { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js').then( - (registration) => { - console.log('SW registered:', registration); - }, - (error) => { - console.log('SW registration failed:', error); - } - ); - }); -} +// PWA/ServiceWorker functionality removed to avoid conflicts in development ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c91825c4..ff05626e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,52 +1,12 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import { VitePWA } from 'vite-plugin-pwa'; import path from 'path'; export default defineConfig({ plugins: [ react(), - VitePWA({ - registerType: 'prompt', - injectRegister: false, // Disable auto-registration - includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'], - manifest: { - name: 'Bakery AI', - short_name: 'Bakery AI', - theme_color: '#f97316', - icons: [ - { - src: '/icons/icon-192.png', - sizes: '192x192', - type: 'image/png', - }, - { - src: '/icons/icon-512.png', - sizes: '512x512', - type: 'image/png', - }, - ], - }, - workbox: { - globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'], - runtimeCaching: [ - { - urlPattern: /^https:\/\/api\.bakeryai\.com\//, - handler: 'NetworkFirst', - options: { - cacheName: 'api-cache', - expiration: { - maxEntries: 50, - maxAgeSeconds: 5 * 60, // 5 minutes - }, - cacheableResponse: { - statuses: [0, 200], - }, - }, - }, - ], - }, - }), + // PWA plugin temporarily disabled to avoid service worker conflicts + // VitePWA can be re-enabled later for production PWA features ], resolve: { alias: { @@ -68,7 +28,9 @@ export default defineConfig({ }, proxy: { '/api': { - target: 'http://localhost:8000', + target: process.env.NODE_ENV === 'development' + ? 'http://gateway:8000' // Use internal service name in Kubernetes + : 'http://localhost:8000', changeOrigin: true, }, }, diff --git a/infrastructure/kubernetes/README.md b/infrastructure/kubernetes/README.md new file mode 100644 index 00000000..5a2d3bfd --- /dev/null +++ b/infrastructure/kubernetes/README.md @@ -0,0 +1,262 @@ +# Bakery IA Kubernetes Configuration + +This directory contains Kubernetes manifests for deploying the Bakery IA forecasting platform in a local development environment. + +## Prerequisites + +1. **Kubernetes Cluster**: Ensure you have a local Kubernetes cluster running (minikube, kind, Docker Desktop, etc.) +2. **kubectl**: Install and configure kubectl to communicate with your cluster +3. **Kustomize**: Built into kubectl v1.14+, or install separately +4. **NGINX Ingress Controller**: Required for ingress functionality + +### Install NGINX Ingress Controller + +```bash +# For minikube +minikube addons enable ingress + +# For kind +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml + +# For Docker Desktop +kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml +``` + +## Directory Structure + +``` +infrastructure/kubernetes/ +├── base/ # Base Kubernetes resources +│ ├── namespace.yaml # Namespace definition +│ ├── configmap.yaml # Shared configuration +│ ├── secrets.yaml # Secrets (base64 encoded) +│ ├── ingress.yaml # Ingress rules +│ └── kustomization.yaml # Base kustomization +├── components/ # Individual component manifests +│ ├── auth/ # Auth service +│ ├── tenant/ # Tenant service +│ ├── training/ # Training service +│ ├── forecasting/ # Forecasting service +│ ├── sales/ # Sales service +│ ├── external/ # External service +│ ├── notification/ # Notification service +│ ├── inventory/ # Inventory service +│ ├── recipes/ # Recipes service +│ ├── suppliers/ # Suppliers service +│ ├── pos/ # POS service +│ ├── orders/ # Orders service +│ ├── production/ # Production service +│ ├── alert-processor/ # Alert processor +│ ├── frontend/ # Frontend application +│ ├── databases/ # Database deployments +│ └── infrastructure/ # Infrastructure components (gateway, etc.) +└── overlays/ + └── dev/ # Development environment overlay + ├── kustomization.yaml # Dev-specific kustomization + └── dev-patches.yaml # Development patches +``` + +## Quick Start + +### 1. Build and Deploy Images (if needed) + +First, ensure your Docker images are built and available to your Kubernetes cluster: + +```bash +# Build all services +docker-compose build + +# For minikube, use minikube's Docker daemon +eval $(minikube docker-env) +docker-compose build + +# For kind, load images into the cluster +kind load docker-image bakery/auth-service:latest +kind load docker-image bakery/tenant-service:latest +# ... repeat for all services +``` + +### 2. Deploy to Kubernetes + +```bash +# Deploy the development environment +kubectl apply -k infrastructure/kubernetes/overlays/dev/ + +# Check deployment status +kubectl get pods -n bakery-ia +kubectl get services -n bakery-ia +kubectl get ingress -n bakery-ia +``` + +### 3. Access the Application + +Add the following to your `/etc/hosts` file (or Windows equivalent): + +``` +127.0.0.1 bakery-ia.local +127.0.0.1 api.bakery-ia.local +127.0.0.1 monitoring.bakery-ia.local +``` + +For minikube, get the ingress IP: +```bash +minikube ip +# Use this IP instead of 127.0.0.1 in your hosts file +``` + +Access the application: +- Frontend: http://bakery-ia.local or http://localhost:3000 +- API Gateway: http://api.bakery-ia.local or http://localhost:8000/api +- Individual services: Check service NodePorts or use port-forwarding + +## Port Forwarding for Direct Access + +If you prefer to access services directly without ingress: + +```bash +# Frontend +kubectl port-forward -n bakery-ia svc/frontend-service 3000:3000 + +# Gateway +kubectl port-forward -n bakery-ia svc/gateway-service 8000:8000 + +# Auth Service +kubectl port-forward -n bakery-ia svc/auth-service 8001:8000 + +# Redis +kubectl port-forward -n bakery-ia svc/redis-service 6379:6379 + +# Database example (auth-db) +kubectl port-forward -n bakery-ia svc/auth-db-service 5432:5432 +``` + +## Managing the Deployment + +### Check Status + +```bash +# Check all resources +kubectl get all -n bakery-ia + +# Check specific resource types +kubectl get pods -n bakery-ia +kubectl get services -n bakery-ia +kubectl get deployments -n bakery-ia +kubectl get pvc -n bakery-ia + +# Check logs +kubectl logs -n bakery-ia deployment/auth-service +kubectl logs -n bakery-ia deployment/frontend -f # Follow logs +``` + +### Update Deployments + +```bash +# After making changes to manifests +kubectl apply -k infrastructure/kubernetes/overlays/dev/ + +# Force restart a deployment +kubectl rollout restart -n bakery-ia deployment/auth-service + +# Check rollout status +kubectl rollout status -n bakery-ia deployment/auth-service +``` + +### Scaling Services + +```bash +# Scale a service +kubectl scale -n bakery-ia deployment/auth-service --replicas=3 + +# Or edit the kustomization.yaml replicas section and reapply +``` + +### Clean Up + +```bash +# Delete everything +kubectl delete -k infrastructure/kubernetes/overlays/dev/ + +# Or delete just the namespace (removes everything in it) +kubectl delete namespace bakery-ia +``` + +## Configuration + +### Secrets + +The `secrets.yaml` file contains base64-encoded secrets. For production, these should be: +1. Generated securely +2. Managed through external secret management systems +3. Not committed to version control + +To encode/decode secrets: +```bash +# Encode +echo -n "your-secret-value" | base64 + +# Decode +echo "eW91ci1zZWNyZXQtdmFsdWU=" | base64 -d +``` + +### Environment-Specific Configuration + +Modify the `overlays/dev/` files to customize the development environment: +- `kustomization.yaml`: Image tags, replicas, resource references +- `dev-patches.yaml`: Environment-specific configuration overrides + +### Adding New Services + +1. Create a new directory under `components/` +2. Add the service YAML manifest +3. Update `base/kustomization.yaml` to include the new resource +4. Update configuration maps and secrets as needed + +## Troubleshooting + +### Common Issues + +1. **Images not found**: Ensure images are built and available to the cluster +2. **Pending pods**: Check resource requests and cluster capacity +3. **CrashLoopBackOff**: Check logs and environment variables +4. **Service not accessible**: Verify ingress controller is running and hosts file is configured + +### Debugging Commands + +```bash +# Describe resources for detailed information +kubectl describe pod -n bakery-ia +kubectl describe deployment -n bakery-ia + +# Get events +kubectl get events -n bakery-ia --sort-by='.firstTimestamp' + +# Execute commands in pods +kubectl exec -n bakery-ia -it -- bash +kubectl exec -n bakery-ia -it -- env + +# Check resource usage +kubectl top pods -n bakery-ia +kubectl top nodes +``` + +## Production Considerations + +For production deployment, consider: + +1. **Resource Limits**: Set appropriate CPU and memory limits +2. **Persistent Volumes**: Use proper storage classes for databases +3. **Secrets Management**: Use external secret management (HashiCorp Vault, AWS Secrets Manager, etc.) +4. **Monitoring**: Deploy Prometheus and Grafana +5. **Backup**: Implement database backup strategies +6. **High Availability**: Use multiple replicas and anti-affinity rules +7. **Security**: Network policies, RBAC, pod security policies +8. **CI/CD**: Integrate with your deployment pipeline + +## Next Steps + +1. Add monitoring with Prometheus and Grafana +2. Implement proper logging with ELK stack or similar +3. Add health checks and metrics endpoints +4. Implement automated testing +5. Set up CI/CD pipelines for automated deployments \ No newline at end of file diff --git a/infrastructure/kubernetes/base/configmap.yaml b/infrastructure/kubernetes/base/configmap.yaml new file mode 100644 index 00000000..068d3eee --- /dev/null +++ b/infrastructure/kubernetes/base/configmap.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: bakery-config + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: config +data: + # Environment Settings + ENVIRONMENT: "development" + DEBUG: "true" + LOG_LEVEL: "INFO" + AUTO_RELOAD: "true" + PROFILING_ENABLED: "false" + MOCK_EXTERNAL_APIS: "false" + TESTING: "false" + + # Service Discovery + REDIS_HOST: "redis-service" + REDIS_PORT: "6379" + RABBITMQ_HOST: "rabbitmq-service" + RABBITMQ_PORT: "5672" + RABBITMQ_MANAGEMENT_PORT: "15672" + RABBITMQ_VHOST: "/" + + # Database Hosts + AUTH_DB_HOST: "auth-db-service" + TENANT_DB_HOST: "tenant-db-service" + TRAINING_DB_HOST: "training-db-service" + FORECASTING_DB_HOST: "forecasting-db-service" + SALES_DB_HOST: "sales-db-service" + EXTERNAL_DB_HOST: "external-db-service" + NOTIFICATION_DB_HOST: "notification-db-service" + INVENTORY_DB_HOST: "inventory-db-service" + RECIPES_DB_HOST: "recipes-db-service" + SUPPLIERS_DB_HOST: "suppliers-db-service" + POS_DB_HOST: "pos-db-service" + ORDERS_DB_HOST: "orders-db-service" + PRODUCTION_DB_HOST: "production-db-service" + ALERT_PROCESSOR_DB_HOST: "alert-processor-db-service" + + # Database Ports + DB_PORT: "5432" + + # Database Names + AUTH_DB_NAME: "auth_db" + TENANT_DB_NAME: "tenant_db" + TRAINING_DB_NAME: "training_db" + FORECASTING_DB_NAME: "forecasting_db" + SALES_DB_NAME: "sales_db" + EXTERNAL_DB_NAME: "external_db" + NOTIFICATION_DB_NAME: "notification_db" + INVENTORY_DB_NAME: "inventory_db" + RECIPES_DB_NAME: "recipes_db" + SUPPLIERS_DB_NAME: "suppliers_db" + POS_DB_NAME: "pos_db" + ORDERS_DB_NAME: "orders_db" + PRODUCTION_DB_NAME: "production_db" + ALERT_PROCESSOR_DB_NAME: "alert_processor_db" + + # PostgreSQL Settings + POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256" + + # Service URLs (internal cluster communication) + AUTH_SERVICE_URL: "http://auth-service:8000" + TENANT_SERVICE_URL: "http://tenant-service:8000" + TRAINING_SERVICE_URL: "http://training-service:8000" + FORECASTING_SERVICE_URL: "http://forecasting-service:8000" + SALES_SERVICE_URL: "http://sales-service:8000" + EXTERNAL_SERVICE_URL: "http://external-service:8000" + NOTIFICATION_SERVICE_URL: "http://notification-service:8000" + INVENTORY_SERVICE_URL: "http://inventory-service:8000" + RECIPES_SERVICE_URL: "http://recipes-service:8000" + SUPPLIERS_SERVICE_URL: "http://suppliers-service:8000" + POS_SERVICE_URL: "http://pos-service:8000" + ORDERS_SERVICE_URL: "http://orders-service:8000" + PRODUCTION_SERVICE_URL: "http://production-service:8000" + + # Cache Settings + REDIS_MAX_MEMORY: "512mb" + + # Monitoring + PROMETHEUS_RETENTION: "200h" + TIMEZONE: "UTC" \ No newline at end of file diff --git a/infrastructure/kubernetes/base/ingress.yaml b/infrastructure/kubernetes/base/ingress.yaml new file mode 100644 index 00000000..2a5c9dce --- /dev/null +++ b/infrastructure/kubernetes/base/ingress.yaml @@ -0,0 +1,69 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: bakery-ingress + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + nginx.ingress.kubernetes.io/cors-allow-origin: "*" + nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-headers: "Content-Type, Authorization" +spec: + ingressClassName: nginx + rules: + - host: bakery-ia.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend-service + port: + number: 3000 + - path: /api + pathType: Prefix + backend: + service: + name: gateway-service + port: + number: 8000 + - path: /auth + pathType: Prefix + backend: + service: + name: auth-service + port: + number: 8000 + - host: api.bakery-ia.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gateway-service + port: + number: 8000 + - host: monitoring.bakery-ia.local + http: + paths: + - path: /grafana + pathType: Prefix + backend: + service: + name: grafana-service + port: + number: 3000 + - path: /prometheus + pathType: Prefix + backend: + service: + name: prometheus-service + port: + number: 9090 \ No newline at end of file diff --git a/infrastructure/kubernetes/base/kustomization.yaml b/infrastructure/kubernetes/base/kustomization.yaml new file mode 100644 index 00000000..ded5ee89 --- /dev/null +++ b/infrastructure/kubernetes/base/kustomization.yaml @@ -0,0 +1,90 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + name: bakery-ia-base + +resources: + # Base configuration + - namespace.yaml + - configmap.yaml + - secrets.yaml + - ingress.yaml + + # Infrastructure components + - components/databases/redis.yaml + - components/databases/rabbitmq.yaml + - components/infrastructure/gateway-service.yaml + + # Database services + - components/databases/auth-db.yaml + - components/databases/tenant-db.yaml + - components/databases/training-db.yaml + - components/databases/forecasting-db.yaml + - components/databases/sales-db.yaml + - components/databases/external-db.yaml + - components/databases/notification-db.yaml + - components/databases/inventory-db.yaml + - components/databases/recipes-db.yaml + - components/databases/suppliers-db.yaml + - components/databases/pos-db.yaml + - components/databases/orders-db.yaml + - components/databases/production-db.yaml + - components/databases/alert-processor-db.yaml + + # Microservices + - components/auth/auth-service.yaml + - components/tenant/tenant-service.yaml + - components/training/training-service.yaml + - components/forecasting/forecasting-service.yaml + - components/sales/sales-service.yaml + - components/external/external-service.yaml + - components/notification/notification-service.yaml + - components/inventory/inventory-service.yaml + - components/recipes/recipes-service.yaml + - components/suppliers/suppliers-service.yaml + - components/pos/pos-service.yaml + - components/orders/orders-service.yaml + - components/production/production-service.yaml + - components/alert-processor/alert-processor-service.yaml + + # Frontend + - components/frontend/frontend-service.yaml + +commonLabels: + app.kubernetes.io/part-of: bakery-ia + app.kubernetes.io/managed-by: kustomize + +images: + - name: bakery/auth-service + newTag: latest + - name: bakery/tenant-service + newTag: latest + - name: bakery/training-service + newTag: latest + - name: bakery/forecasting-service + newTag: latest + - name: bakery/sales-service + newTag: latest + - name: bakery/external-service + newTag: latest + - name: bakery/notification-service + newTag: latest + - name: bakery/inventory-service + newTag: latest + - name: bakery/recipes-service + newTag: latest + - name: bakery/suppliers-service + newTag: latest + - name: bakery/pos-service + newTag: latest + - name: bakery/orders-service + newTag: latest + - name: bakery/production-service + newTag: latest + - name: bakery/alert-processor + newTag: latest + - name: bakery/gateway + newTag: latest + - name: bakery/dashboard + newTag: latest \ No newline at end of file diff --git a/infrastructure/kubernetes/base/namespace.yaml b/infrastructure/kubernetes/base/namespace.yaml new file mode 100644 index 00000000..ca1f3dce --- /dev/null +++ b/infrastructure/kubernetes/base/namespace.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: bakery-ia + labels: + name: bakery-ia + environment: local + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/part-of: bakery-forecasting-platform \ No newline at end of file diff --git a/infrastructure/kubernetes/base/secrets.yaml b/infrastructure/kubernetes/base/secrets.yaml new file mode 100644 index 00000000..8331dccc --- /dev/null +++ b/infrastructure/kubernetes/base/secrets.yaml @@ -0,0 +1,96 @@ +apiVersion: v1 +kind: Secret +metadata: + name: database-secrets + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: database +type: Opaque +data: + # Database Users (base64 encoded) + AUTH_DB_USER: YmFrZXJ5X2F1dGg= # bakery_auth + TENANT_DB_USER: YmFrZXJ5X3RlbmFudA== # bakery_tenant + TRAINING_DB_USER: YmFrZXJ5X3RyYWluaW5n # bakery_training + FORECASTING_DB_USER: YmFrZXJ5X2ZvcmVjYXN0aW5n # bakery_forecasting + SALES_DB_USER: YmFrZXJ5X3NhbGVz # bakery_sales + EXTERNAL_DB_USER: YmFrZXJ5X2V4dGVybmFs # bakery_external + NOTIFICATION_DB_USER: YmFrZXJ5X25vdGlmaWNhdGlvbg== # bakery_notification + INVENTORY_DB_USER: YmFrZXJ5X2ludmVudG9yeQ== # bakery_inventory + RECIPES_DB_USER: YmFrZXJ5X3JlY2lwZXM= # bakery_recipes + SUPPLIERS_DB_USER: YmFrZXJ5X3N1cHBsaWVycw== # bakery_suppliers + POS_DB_USER: YmFrZXJ5X3Bvcw== # bakery_pos + ORDERS_DB_USER: YmFrZXJ5X29yZGVycw== # bakery_orders + PRODUCTION_DB_USER: YmFrZXJ5X3Byb2R1Y3Rpb24= # bakery_production + ALERT_PROCESSOR_DB_USER: YmFrZXJ5X2FsZXJ0X3Byb2Nlc3Nvcg== # bakery_alert_processor + + # Database Passwords (base64 encoded - change these in production!) + AUTH_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + TENANT_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + TRAINING_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + FORECASTING_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + SALES_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + EXTERNAL_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + NOTIFICATION_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + INVENTORY_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + RECIPES_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + SUPPLIERS_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + POS_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + ORDERS_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + PRODUCTION_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + ALERT_PROCESSOR_DB_PASSWORD: ZGV2X3Bhc3N3b3JkXzEyMw== # dev_password_123 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-secrets + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: redis +type: Opaque +data: + REDIS_PASSWORD: ZGV2X3JlZGlzXzEyMw== # dev_redis_123 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: rabbitmq-secrets + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: rabbitmq +type: Opaque +data: + RABBITMQ_USER: YmFrZXJ5X3VzZXI= # bakery_user + RABBITMQ_PASSWORD: ZGV2X3JhYmJpdF8xMjM= # dev_rabbit_123 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: jwt-secrets + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: auth +type: Opaque +data: + JWT_SECRET_KEY: ZGV2X2p3dF9zZWNyZXRfa2V5XzEyMzQ1Njc4OTA= # dev_jwt_secret_key_1234567890 + JWT_REFRESH_SECRET_KEY: ZGV2X2p3dF9yZWZyZXNoX3NlY3JldF9rZXlfMTIzNDU2Nzg5MA== # dev_jwt_refresh_secret_key_1234567890 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: monitoring-secrets + namespace: bakery-ia + labels: + app.kubernetes.io/name: bakery-ia + app.kubernetes.io/component: monitoring +type: Opaque +data: + GRAFANA_ADMIN_USER: YWRtaW4= # admin + GRAFANA_ADMIN_PASSWORD: ZGV2X2dyYWZhbmFfMTIz # dev_grafana_123 \ No newline at end of file diff --git a/infrastructure/kubernetes/components/alert-processor/alert-processor-service.yaml b/infrastructure/kubernetes/components/alert-processor/alert-processor-service.yaml new file mode 100644 index 00000000..c3f2185a --- /dev/null +++ b/infrastructure/kubernetes/components/alert-processor/alert-processor-service.yaml @@ -0,0 +1,112 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: alert-processor-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: alert-processor-service + app.kubernetes.io/component: worker + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: alert-processor-service + app.kubernetes.io/component: worker + template: + metadata: + labels: + app.kubernetes.io/name: alert-processor-service + app.kubernetes.io/component: worker + spec: + containers: + - name: alert-processor-service + image: bakery/alert-processor:latest + env: + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + name: bakery-config + key: ENVIRONMENT + - name: DEBUG + valueFrom: + configMapKeyRef: + name: bakery-config + key: DEBUG + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + name: bakery-config + key: LOG_LEVEL + - name: ALERT_PROCESSOR_DB_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: ALERT_PROCESSOR_DB_HOST + - name: ALERT_PROCESSOR_DB_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: DB_PORT + - name: ALERT_PROCESSOR_DB_NAME + valueFrom: + configMapKeyRef: + name: bakery-config + key: ALERT_PROCESSOR_DB_NAME + - name: ALERT_PROCESSOR_DB_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: ALERT_PROCESSOR_DB_USER + - name: ALERT_PROCESSOR_DB_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: ALERT_PROCESSOR_DB_PASSWORD + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: REDIS_HOST + - name: REDIS_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: REDIS_PORT + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-secrets + key: REDIS_PASSWORD + - name: RABBITMQ_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: RABBITMQ_HOST + - name: RABBITMQ_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: RABBITMQ_PORT + - name: RABBITMQ_USER + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_USER + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_PASSWORD + - name: NOTIFICATION_SERVICE_URL + valueFrom: + configMapKeyRef: + name: bakery-config + key: NOTIFICATION_SERVICE_URL + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" \ No newline at end of file diff --git a/infrastructure/kubernetes/components/auth/auth-service.yaml b/infrastructure/kubernetes/components/auth/auth-service.yaml new file mode 100644 index 00000000..1fb388c6 --- /dev/null +++ b/infrastructure/kubernetes/components/auth/auth-service.yaml @@ -0,0 +1,156 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: auth-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: auth-service + app.kubernetes.io/component: microservice + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: auth-service + app.kubernetes.io/component: microservice + template: + metadata: + labels: + app.kubernetes.io/name: auth-service + app.kubernetes.io/component: microservice + spec: + containers: + - name: auth-service + image: bakery/auth-service:latest + ports: + - containerPort: 8000 + name: http + env: + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + name: bakery-config + key: ENVIRONMENT + - name: DEBUG + valueFrom: + configMapKeyRef: + name: bakery-config + key: DEBUG + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + name: bakery-config + key: LOG_LEVEL + - name: AUTH_DB_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: AUTH_DB_HOST + - name: AUTH_DB_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: DB_PORT + - name: AUTH_DB_NAME + valueFrom: + configMapKeyRef: + name: bakery-config + key: AUTH_DB_NAME + - name: AUTH_DB_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: AUTH_DB_USER + - name: AUTH_DB_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: AUTH_DB_PASSWORD + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: REDIS_HOST + - name: REDIS_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: REDIS_PORT + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-secrets + key: REDIS_PASSWORD + - name: RABBITMQ_HOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: RABBITMQ_HOST + - name: RABBITMQ_PORT + valueFrom: + configMapKeyRef: + name: bakery-config + key: RABBITMQ_PORT + - name: RABBITMQ_USER + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_USER + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_PASSWORD + - name: JWT_SECRET_KEY + valueFrom: + secretKeyRef: + name: jwt-secrets + key: JWT_SECRET_KEY + - name: JWT_REFRESH_SECRET_KEY + valueFrom: + secretKeyRef: + name: jwt-secrets + key: JWT_REFRESH_SECRET_KEY + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + +--- +apiVersion: v1 +kind: Service +metadata: + name: auth-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: auth-service + app.kubernetes.io/component: microservice +spec: + type: ClusterIP + ports: + - port: 8000 + targetPort: 8000 + protocol: TCP + name: http + selector: + app.kubernetes.io/name: auth-service + app.kubernetes.io/component: microservice \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/alert-processor-db.yaml b/infrastructure/kubernetes/components/databases/alert-processor-db.yaml new file mode 100644 index 00000000..cf5ab3aa --- /dev/null +++ b/infrastructure/kubernetes/components/databases/alert-processor-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: alert-processor-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: ALERT_PROCESSOR_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: ALERT_PROCESSOR_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: ALERT_PROCESSOR_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: alert-processor-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: alert-processor-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: alert-processor-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: alert-processor-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/auth-db.yaml b/infrastructure/kubernetes/components/databases/auth-db.yaml new file mode 100644 index 00000000..3f2d3f24 --- /dev/null +++ b/infrastructure/kubernetes/components/databases/auth-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: auth-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: AUTH_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: AUTH_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: AUTH_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: auth-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: auth-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: auth-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: auth-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/external-db.yaml b/infrastructure/kubernetes/components/databases/external-db.yaml new file mode 100644 index 00000000..641ab1c8 --- /dev/null +++ b/infrastructure/kubernetes/components/databases/external-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: EXTERNAL_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: EXTERNAL_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: EXTERNAL_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: external-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: external-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: external-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: external-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/forecasting-db.yaml b/infrastructure/kubernetes/components/databases/forecasting-db.yaml new file mode 100644 index 00000000..1d002f4f --- /dev/null +++ b/infrastructure/kubernetes/components/databases/forecasting-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forecasting-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: FORECASTING_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: FORECASTING_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: FORECASTING_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: forecasting-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: forecasting-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: forecasting-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: forecasting-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/inventory-db.yaml b/infrastructure/kubernetes/components/databases/inventory-db.yaml new file mode 100644 index 00000000..1054f51b --- /dev/null +++ b/infrastructure/kubernetes/components/databases/inventory-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: inventory-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: INVENTORY_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: INVENTORY_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: INVENTORY_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: inventory-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: inventory-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: inventory-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: inventory-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/notification-db.yaml b/infrastructure/kubernetes/components/databases/notification-db.yaml new file mode 100644 index 00000000..5f4776cf --- /dev/null +++ b/infrastructure/kubernetes/components/databases/notification-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: notification-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: NOTIFICATION_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: NOTIFICATION_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: NOTIFICATION_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: notification-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: notification-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: notification-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: notification-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/orders-db.yaml b/infrastructure/kubernetes/components/databases/orders-db.yaml new file mode 100644 index 00000000..61f65c6c --- /dev/null +++ b/infrastructure/kubernetes/components/databases/orders-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: orders-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: ORDERS_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: ORDERS_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: ORDERS_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: orders-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: orders-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: orders-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: orders-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/pos-db.yaml b/infrastructure/kubernetes/components/databases/pos-db.yaml new file mode 100644 index 00000000..26ab44c2 --- /dev/null +++ b/infrastructure/kubernetes/components/databases/pos-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pos-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: POS_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: POS_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: POS_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: pos-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: pos-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pos-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: pos-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/postgres-template.yaml b/infrastructure/kubernetes/components/databases/postgres-template.yaml new file mode 100644 index 00000000..6001c41d --- /dev/null +++ b/infrastructure/kubernetes/components/databases/postgres-template.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{SERVICE_NAME}}-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: {{SERVICE_NAME_UPPER}}_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: {{SERVICE_NAME_UPPER}}_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: {{SERVICE_NAME_UPPER}}_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: {{SERVICE_NAME}}-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: {{SERVICE_NAME}}-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{SERVICE_NAME}}-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: {{SERVICE_NAME}}-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/production-db.yaml b/infrastructure/kubernetes/components/databases/production-db.yaml new file mode 100644 index 00000000..1434f668 --- /dev/null +++ b/infrastructure/kubernetes/components/databases/production-db.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: production-db + namespace: bakery-ia + labels: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database + template: + metadata: + labels: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_DB + valueFrom: + configMapKeyRef: + name: bakery-config + key: PRODUCTION_DB_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: database-secrets + key: PRODUCTION_DB_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: database-secrets + key: PRODUCTION_DB_PASSWORD + - name: POSTGRES_INITDB_ARGS + valueFrom: + configMapKeyRef: + name: bakery-config + key: POSTGRES_INITDB_ARGS + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 30 + timeoutSeconds: 5 + periodSeconds: 10 + failureThreshold: 3 + readinessProbe: + exec: + command: + - pg_isready + - -U + - $(POSTGRES_USER) + - -d + - $(POSTGRES_DB) + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: production-db-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: production-db-service + namespace: bakery-ia + labels: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgres + selector: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: production-db-pvc + namespace: bakery-ia + labels: + app.kubernetes.io/name: production-db + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/infrastructure/kubernetes/components/databases/rabbitmq.yaml b/infrastructure/kubernetes/components/databases/rabbitmq.yaml new file mode 100644 index 00000000..3e5c1a99 --- /dev/null +++ b/infrastructure/kubernetes/components/databases/rabbitmq.yaml @@ -0,0 +1,123 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq + namespace: bakery-ia + labels: + app.kubernetes.io/name: rabbitmq + app.kubernetes.io/component: message-broker + app.kubernetes.io/part-of: bakery-ia +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: rabbitmq + app.kubernetes.io/component: message-broker + template: + metadata: + labels: + app.kubernetes.io/name: rabbitmq + app.kubernetes.io/component: message-broker + spec: + containers: + - name: rabbitmq + image: rabbitmq:3.12-management-alpine + ports: + - containerPort: 5672 + name: amqp + - containerPort: 15672 + name: management + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_USER + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: rabbitmq-secrets + key: RABBITMQ_PASSWORD + - name: RABBITMQ_DEFAULT_VHOST + valueFrom: + configMapKeyRef: + name: bakery-config + key: RABBITMQ_VHOST + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + resources: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1000m" + livenessProbe: + exec: + command: + - timeout + - "5" + - bash + - -c + - " str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("ALERT_PROCESSOR_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("ALERT_PROCESSOR_DB_USER", "alert_processor_user") + password = os.getenv("ALERT_PROCESSOR_DB_PASSWORD", "alert_processor_pass123") + host = os.getenv("ALERT_PROCESSOR_DB_HOST", "localhost") + port = os.getenv("ALERT_PROCESSOR_DB_PORT", "5432") + name = os.getenv("ALERT_PROCESSOR_DB_NAME", "alert_processor_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Use dedicated Redis DB for alert processing REDIS_DB: int = int(os.getenv("ALERT_PROCESSOR_REDIS_DB", "6")) diff --git a/services/auth/app/core/config.py b/services/auth/app/core/config.py index 1abc862b..70887b1e 100644 --- a/services/auth/app/core/config.py +++ b/services/auth/app/core/config.py @@ -19,9 +19,23 @@ class AuthSettings(BaseServiceSettings): SERVICE_NAME: str = "auth-service" DESCRIPTION: str = "User authentication and authorization service" - # Database Configuration - DATABASE_URL: str = os.getenv("AUTH_DATABASE_URL", - "postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("AUTH_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("AUTH_DB_USER", "auth_user") + password = os.getenv("AUTH_DB_PASSWORD", "auth_pass123") + host = os.getenv("AUTH_DB_HOST", "localhost") + port = os.getenv("AUTH_DB_PORT", "5432") + name = os.getenv("AUTH_DB_NAME", "auth_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (dedicated for auth) REDIS_DB: int = 0 diff --git a/services/external/app/core/config.py b/services/external/app/core/config.py index b317d468..f7c47237 100644 --- a/services/external/app/core/config.py +++ b/services/external/app/core/config.py @@ -16,11 +16,23 @@ class DataSettings(BaseServiceSettings): # API Configuration API_V1_STR: str = "/api/v1" - # Override database URL to use EXTERNAL_DATABASE_URL - DATABASE_URL: str = Field( - default="postgresql+asyncpg://external_user:external_pass123@external-db:5432/external_db", - env="EXTERNAL_DATABASE_URL" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("EXTERNAL_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("EXTERNAL_DB_USER", "external_user") + password = os.getenv("EXTERNAL_DB_PASSWORD", "external_pass123") + host = os.getenv("EXTERNAL_DB_HOST", "localhost") + port = os.getenv("EXTERNAL_DB_PORT", "5432") + name = os.getenv("EXTERNAL_DB_NAME", "external_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # External API Configuration AEMET_API_KEY: str = os.getenv("AEMET_API_KEY", "") diff --git a/services/forecasting/app/core/config.py b/services/forecasting/app/core/config.py index e054b661..61bdf12c 100644 --- a/services/forecasting/app/core/config.py +++ b/services/forecasting/app/core/config.py @@ -19,9 +19,23 @@ class ForecastingSettings(BaseServiceSettings): SERVICE_NAME: str = "forecasting-service" DESCRIPTION: str = "Demand prediction and forecasting service" - # Database Configuration - DATABASE_URL: str = os.getenv("FORECASTING_DATABASE_URL", - "postgresql+asyncpg://forecasting_user:forecasting_pass123@forecasting-db:5432/forecasting_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("FORECASTING_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("FORECASTING_DB_USER", "forecasting_user") + password = os.getenv("FORECASTING_DB_PASSWORD", "forecasting_pass123") + host = os.getenv("FORECASTING_DB_HOST", "localhost") + port = os.getenv("FORECASTING_DB_PORT", "5432") + name = os.getenv("FORECASTING_DB_NAME", "forecasting_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (dedicated for prediction cache) REDIS_DB: int = 2 diff --git a/services/inventory/app/core/config.py b/services/inventory/app/core/config.py index e0d1ad3f..dc456e5b 100644 --- a/services/inventory/app/core/config.py +++ b/services/inventory/app/core/config.py @@ -3,6 +3,7 @@ Inventory Service Configuration """ +import os from typing import List from pydantic import Field from shared.config.base import BaseServiceSettings @@ -20,11 +21,23 @@ class Settings(BaseServiceSettings): # API Configuration API_V1_STR: str = "/api/v1" - # Override database URL to use INVENTORY_DATABASE_URL - DATABASE_URL: str = Field( - default="postgresql+asyncpg://inventory_user:inventory_pass123@inventory-db:5432/inventory_db", - env="INVENTORY_DATABASE_URL" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("INVENTORY_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("INVENTORY_DB_USER", "inventory_user") + password = os.getenv("INVENTORY_DB_PASSWORD", "inventory_pass123") + host = os.getenv("INVENTORY_DB_HOST", "localhost") + port = os.getenv("INVENTORY_DB_PORT", "5432") + name = os.getenv("INVENTORY_DB_NAME", "inventory_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Inventory-specific Redis database REDIS_DB: int = Field(default=3, env="INVENTORY_REDIS_DB") diff --git a/services/notification/app/core/config.py b/services/notification/app/core/config.py index 09332be5..64ee281a 100644 --- a/services/notification/app/core/config.py +++ b/services/notification/app/core/config.py @@ -19,9 +19,23 @@ class NotificationSettings(BaseServiceSettings): SERVICE_NAME: str = "notification-service" DESCRIPTION: str = "Email and WhatsApp notification service" - # Database Configuration - DATABASE_URL: str = os.getenv("NOTIFICATION_DATABASE_URL", - "postgresql+asyncpg://notification_user:notification_pass123@notification-db:5432/notification_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("NOTIFICATION_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("NOTIFICATION_DB_USER", "notification_user") + password = os.getenv("NOTIFICATION_DB_PASSWORD", "notification_pass123") + host = os.getenv("NOTIFICATION_DB_HOST", "localhost") + port = os.getenv("NOTIFICATION_DB_PORT", "5432") + name = os.getenv("NOTIFICATION_DB_NAME", "notification_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (dedicated for notification queue) REDIS_DB: int = 5 diff --git a/services/orders/app/core/config.py b/services/orders/app/core/config.py index dd9e54de..79dc8ce4 100644 --- a/services/orders/app/core/config.py +++ b/services/orders/app/core/config.py @@ -18,9 +18,23 @@ class OrdersSettings(BaseServiceSettings): VERSION: str = "1.0.0" DESCRIPTION: str = "Customer orders and procurement planning" - # Database Configuration - DATABASE_URL: str = os.getenv("ORDERS_DATABASE_URL", - "postgresql+asyncpg://orders_user:orders_pass123@orders-db:5432/orders_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("ORDERS_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("ORDERS_DB_USER", "orders_user") + password = os.getenv("ORDERS_DB_PASSWORD", "orders_pass123") + host = os.getenv("ORDERS_DB_HOST", "localhost") + port = os.getenv("ORDERS_DB_PORT", "5432") + name = os.getenv("ORDERS_DB_NAME", "orders_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Order Processing ORDER_PROCESSING_ENABLED: bool = os.getenv("ORDER_PROCESSING_ENABLED", "true").lower() == "true" diff --git a/services/pos/app/core/config.py b/services/pos/app/core/config.py index 3381f585..623633d7 100644 --- a/services/pos/app/core/config.py +++ b/services/pos/app/core/config.py @@ -3,6 +3,7 @@ POS Integration Service Configuration """ +import os from typing import List, Optional from pydantic import Field from shared.config.base import BaseServiceSettings @@ -20,11 +21,23 @@ class Settings(BaseServiceSettings): # API Configuration API_V1_STR: str = "/api/v1" - # Override database URL to use POS_DATABASE_URL - DATABASE_URL: str = Field( - default="postgresql+asyncpg://pos_user:pos_pass123@pos-db:5432/pos_db", - env="POS_DATABASE_URL" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("POS_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("POS_DB_USER", "pos_user") + password = os.getenv("POS_DB_PASSWORD", "pos_pass123") + host = os.getenv("POS_DB_HOST", "localhost") + port = os.getenv("POS_DB_PORT", "5432") + name = os.getenv("POS_DB_NAME", "pos_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # POS-specific Redis database REDIS_DB: int = Field(default=5, env="POS_REDIS_DB") diff --git a/services/production/app/core/config.py b/services/production/app/core/config.py index 0c0d3be8..898c0e2c 100644 --- a/services/production/app/core/config.py +++ b/services/production/app/core/config.py @@ -20,9 +20,23 @@ class ProductionSettings(BaseServiceSettings): VERSION: str = "1.0.0" DESCRIPTION: str = "Production planning and batch management" - # Database Configuration - DATABASE_URL: str = os.getenv("PRODUCTION_DATABASE_URL", - "postgresql+asyncpg://production_user:production_pass123@production-db:5432/production_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("PRODUCTION_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("PRODUCTION_DB_USER", "production_user") + password = os.getenv("PRODUCTION_DB_PASSWORD", "production_pass123") + host = os.getenv("PRODUCTION_DB_HOST", "localhost") + port = os.getenv("PRODUCTION_DB_PORT", "5432") + name = os.getenv("PRODUCTION_DB_NAME", "production_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (for production queues and caching) REDIS_DB: int = 3 diff --git a/services/recipes/app/core/config.py b/services/recipes/app/core/config.py index e8c570dc..33b0c1dd 100644 --- a/services/recipes/app/core/config.py +++ b/services/recipes/app/core/config.py @@ -17,11 +17,23 @@ class Settings: # API settings API_V1_PREFIX: str = "/api/v1" - # Override DATABASE_URL for recipes service - DATABASE_URL: str = os.getenv( - "RECIPES_DATABASE_URL", - "postgresql://recipes_user:recipes_pass@localhost:5432/recipes_db" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("RECIPES_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("RECIPES_DB_USER", "recipes_user") + password = os.getenv("RECIPES_DB_PASSWORD", "recipes_pass123") + host = os.getenv("RECIPES_DB_HOST", "localhost") + port = os.getenv("RECIPES_DB_PORT", "5432") + name = os.getenv("RECIPES_DB_NAME", "recipes_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis (if needed for caching) REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0") diff --git a/services/sales/app/core/config.py b/services/sales/app/core/config.py index f3ef96a5..e61d1cd7 100644 --- a/services/sales/app/core/config.py +++ b/services/sales/app/core/config.py @@ -3,6 +3,7 @@ Sales Service Configuration """ +import os from typing import List from pydantic import Field from shared.config.base import BaseServiceSettings @@ -20,11 +21,23 @@ class Settings(BaseServiceSettings): # API Configuration API_V1_STR: str = "/api/v1" - # Override database URL to use SALES_DATABASE_URL - DATABASE_URL: str = Field( - default="postgresql+asyncpg://sales_user:sales_pass123@sales-db:5432/sales_db", - env="SALES_DATABASE_URL" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("SALES_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("SALES_DB_USER", "sales_user") + password = os.getenv("SALES_DB_PASSWORD", "sales_pass123") + host = os.getenv("SALES_DB_HOST", "localhost") + port = os.getenv("SALES_DB_PORT", "5432") + name = os.getenv("SALES_DB_NAME", "sales_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Sales-specific Redis database REDIS_DB: int = Field(default=2, env="SALES_REDIS_DB") diff --git a/services/suppliers/app/core/config.py b/services/suppliers/app/core/config.py index 04d580e5..bff183e2 100644 --- a/services/suppliers/app/core/config.py +++ b/services/suppliers/app/core/config.py @@ -3,6 +3,7 @@ Supplier & Procurement Service Configuration """ +import os from typing import List from pydantic import Field from shared.config.base import BaseServiceSettings @@ -20,11 +21,23 @@ class Settings(BaseServiceSettings): # API Configuration API_V1_STR: str = "/api/v1" - # Override database URL to use SUPPLIERS_DATABASE_URL - DATABASE_URL: str = Field( - default="postgresql+asyncpg://suppliers_user:suppliers_pass123@suppliers-db:5432/suppliers_db", - env="SUPPLIERS_DATABASE_URL" - ) + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("SUPPLIERS_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("SUPPLIERS_DB_USER", "suppliers_user") + password = os.getenv("SUPPLIERS_DB_PASSWORD", "suppliers_pass123") + host = os.getenv("SUPPLIERS_DB_HOST", "localhost") + port = os.getenv("SUPPLIERS_DB_PORT", "5432") + name = os.getenv("SUPPLIERS_DB_NAME", "suppliers_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Suppliers-specific Redis database REDIS_DB: int = Field(default=4, env="SUPPLIERS_REDIS_DB") diff --git a/services/tenant/app/core/config.py b/services/tenant/app/core/config.py index c4d8837d..05a21c90 100644 --- a/services/tenant/app/core/config.py +++ b/services/tenant/app/core/config.py @@ -19,9 +19,23 @@ class TenantSettings(BaseServiceSettings): SERVICE_NAME: str = "tenant-service" DESCRIPTION: str = "Multi-tenant management and subscription service" - # Database Configuration - DATABASE_URL: str = os.getenv("TENANT_DATABASE_URL", - "postgresql+asyncpg://tenant_user:tenant_pass123@tenant-db:5432/tenant_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("TENANT_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("TENANT_DB_USER", "tenant_user") + password = os.getenv("TENANT_DB_PASSWORD", "tenant_pass123") + host = os.getenv("TENANT_DB_HOST", "localhost") + port = os.getenv("TENANT_DB_PORT", "5432") + name = os.getenv("TENANT_DB_NAME", "tenant_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (dedicated for tenant data) REDIS_DB: int = 4 diff --git a/services/training/app/core/config.py b/services/training/app/core/config.py index 10112851..52b787b8 100644 --- a/services/training/app/core/config.py +++ b/services/training/app/core/config.py @@ -19,9 +19,23 @@ class TrainingSettings(BaseServiceSettings): SERVICE_NAME: str = "training-service" DESCRIPTION: str = "Machine learning model training service" - # Database Configuration - DATABASE_URL: str = os.getenv("TRAINING_DATABASE_URL", - "postgresql+asyncpg://training_user:training_pass123@training-db:5432/training_db") + # Database configuration (secure approach - build from components) + @property + def DATABASE_URL(self) -> str: + """Build database URL from secure components""" + # Try complete URL first (for backward compatibility) + complete_url = os.getenv("TRAINING_DATABASE_URL") + if complete_url: + return complete_url + + # Build from components (secure approach) + user = os.getenv("TRAINING_DB_USER", "training_user") + password = os.getenv("TRAINING_DB_PASSWORD", "training_pass123") + host = os.getenv("TRAINING_DB_HOST", "localhost") + port = os.getenv("TRAINING_DB_PORT", "5432") + name = os.getenv("TRAINING_DB_NAME", "training_db") + + return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}" # Redis Database (dedicated for training cache) REDIS_DB: int = 1