From 0ea1af91c949f6ee11b0afa0e1f0ff466266cb72 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 7 Sep 2025 20:47:10 -0700 Subject: [PATCH] feat: Complete Kustomize migration with environment variable integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎉 Major enhancement: Full migration from shell script deployment to Kustomize ## New Features ### Kustomize Infrastructure - ✅ Complete base resources for all Kubernetes manifests - ✅ Development overlay with optimized dev settings - ✅ Production overlay with enterprise-grade security and performance - ✅ ConfigMap and Secret generation from environment variables - ✅ Image tag and replica management per environment ### Environment Variable Integration - ✅ Multi-source environment loading (~/.env, .env.dev, .env.prod, .env.local) - ✅ Static configuration generation from environment variables - ✅ Dynamic runtime environment variable injection - ✅ Comprehensive variable documentation and examples - ✅ Secrets template generation for secure credential management ### Enhanced Makefile - ✅ 20+ new Kustomize-specific deployment targets - ✅ Environment-aware configuration generation commands - ✅ Validation, dry-run, and debugging capabilities - ✅ Backward compatibility with legacy shell script deployment ### New Scripts & Tools - ✅ scripts/generate-config.sh - Environment variable to Kustomize config generator - ✅ scripts/deploy-with-env.sh - Runtime environment variable deployment tool - ✅ Comprehensive help and usage documentation ### Documentation - ✅ k8s-kustomize/README.md - Complete Kustomize deployment guide - ✅ docs/ENVIRONMENT_VARIABLES.md - Environment variable integration guide - ✅ KUSTOMIZE_MIGRATION.md - Migration summary and next steps ## Benefits - 🚀 Simplified deployment: make deploy-dev vs complex shell scripts - 🔒 Environment isolation: Clear dev/staging/prod separation - 🔧 GitOps ready: Works seamlessly with ArgoCD, Flux - ✅ Better validation: Built-in YAML validation catches errors early - 📈 Standard approach: Industry-standard Kubernetes deployment method - 🛡️ Enhanced security: Production security contexts, network policies, TLS ## Usage Examples Generating development configuration... [INFO] Kustomize Config Generator [INFO] Environment: dev [INFO] Loading environment variables... [WARNING] File not found: /home/will/.env [INFO] Loading: /home/will/Code/meds/.env [WARNING] File not found: /home/will/Code/meds/.env.dev [WARNING] File not found: /home/will/Code/meds/.env.local [INFO] Generating base config.env... [SUCCESS] Generated: /home/will/Code/meds/k8s-kustomize/base/config.env [INFO] Generating environment-specific config for: dev [SUCCESS] Generated development config: /home/will/Code/meds/k8s-kustomize/overlays/dev/config.env [INFO] Validating generated configuration... [SUCCESS] Configuration validation passed! [SUCCESS] Configuration generation completed! [INFO] Next steps: 1. Review generated files in k8s-kustomize/ 2. Update any environment-specific values 3. Create secrets.env files for sensitive data 4. Test with: make kustomize-dry-run-dev Deploying to Kubernetes with Kustomize (dev)... Deploying to production with environment variables... [INFO] Kustomize Deployment with Environment Variables [INFO] Environment: prod [INFO] Action: apply [INFO] Validating prerequisites... [SUCCESS] Prerequisites validated [INFO] Loading environment variables for: prod [INFO] Loading: /home/will/Code/meds/.env [SUCCESS] Environment loaded: prod [INFO] Key variables: APP_NAME: rxminder NODE_ENV: production IMAGE_TAG: latest NAMESPACE: rxminder-prod INGRESS_HOST: rxminder.192.168.153.243.nip.io [INFO] Generating dynamic configuration... Validating Kustomize configuration (dev)... configmap/rxminder-config-4229dg76t6 created (dry run) secret/couchdb-secret-7ck2cc96g5 created (dry run) service/rxminder-couchdb-service created (dry run) service/rxminder-frontend-service created (dry run) persistentvolumeclaim/rxminder-couchdb-pvc created (dry run) deployment.apps/rxminder-frontend created (dry run) statefulset.apps/rxminder-couchdb created (dry run) horizontalpodautoscaler.autoscaling/rxminder-frontend-hpa created (dry run) job.batch/rxminder-db-seed created (dry run) ingress.networking.k8s.io/rxminder-ingress created (dry run) networkpolicy.networking.k8s.io/rxminder-database-policy created (dry run) networkpolicy.networking.k8s.io/rxminder-frontend-policy created (dry run) Validating Kustomize configuration (prod)... configmap/rxminder-config-2979gkcf9c created (dry run) secret/couchdb-secret-6k9794bgg2 created (dry run) service/rxminder-couchdb-service created (dry run) service/rxminder-frontend-service created (dry run) persistentvolumeclaim/rxminder-couchdb-pvc created (dry run) deployment.apps/rxminder-frontend created (dry run) statefulset.apps/rxminder-couchdb created (dry run) horizontalpodautoscaler.autoscaling/rxminder-frontend-hpa created (dry run) job.batch/rxminder-db-seed created (dry run) ingress.networking.k8s.io/rxminder-ingress created (dry run) networkpolicy.networking.k8s.io/rxminder-database-policy created (dry run) networkpolicy.networking.k8s.io/rxminder-frontend-policy created (dry run) Kustomize validation completed! Dry run Kustomize deployment (dev)... apiVersion: v1 items: - apiVersion: v1 data: APP_NAME: rxminder APP_VERSION: 1.0.0 CACHE_TTL: "1800" CERT_MANAGER_ISSUER: letsencrypt-prod CORS_ORIGIN: '*' COUCHDB_DATABASE_NAME: meds_app DB_HOST: rxminder-couchdb-service DB_PORT: "5984" DEBUG: "true" DEV_MODE: "false" ENABLE_CORS: "true" ENABLE_METRICS: "false" ENABLE_MONITORING: "false" ENABLE_TRACING: "false" HEALTH_CHECK_INTERVAL: "30" HOT_RELOAD: "false" IMAGE_REPOSITORY: will/rxminder INGRESS_CLASS: nginx LOG_FORMAT: json LOG_LEVEL: debug LOG_TIMESTAMP: "true" MAX_CONNECTIONS: "100" METRICS_PORT: "9090" NODE_ENV: development REACT_APP_API_URL: http://rxminder-couchdb-service:5984 READINESS_CHECK_TIMEOUT: "5" REGISTRY_URL: gitea-http.taildb3494.ts.net REQUEST_TIMEOUT: "30000" kind: ConfigMap metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","data":{"APP_NAME":"rxminder","APP_VERSION":"1.0.0","CACHE_TTL":"1800","CERT_MANAGER_ISSUER":"letsencrypt-prod","CORS_ORIGIN":"*","COUCHDB_DATABASE_NAME":"meds_app","DB_HOST":"rxminder-couchdb-service","DB_PORT":"5984","DEBUG":"true","DEV_MODE":"false","ENABLE_CORS":"true","ENABLE_METRICS":"false","ENABLE_MONITORING":"false","ENABLE_TRACING":"false","HEALTH_CHECK_INTERVAL":"30","HOT_RELOAD":"false","IMAGE_REPOSITORY":"will/rxminder","INGRESS_CLASS":"nginx","LOG_FORMAT":"json","LOG_LEVEL":"debug","LOG_TIMESTAMP":"true","MAX_CONNECTIONS":"100","METRICS_PORT":"9090","NODE_ENV":"development","REACT_APP_API_URL":"http://rxminder-couchdb-service:5984","READINESS_CHECK_TIMEOUT":"5","REGISTRY_URL":"gitea-http.taildb3494.ts.net","REQUEST_TIMEOUT":"30000"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"rxminder","environment":"dev","version":"v1.0.0"},"name":"rxminder-config-4229dg76t6","namespace":"rxminder-dev"}} labels: app: rxminder environment: dev version: v1.0.0 name: rxminder-config-4229dg76t6 namespace: rxminder-dev - apiVersion: v1 data: password: ZGV2cGFzczEyMw== username: YWRtaW4= kind: Secret metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","data":{"password":"ZGV2cGFzczEyMw==","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"labels":{"app":"rxminder","environment":"dev","version":"v1.0.0"},"name":"couchdb-secret-7ck2cc96g5","namespace":"rxminder-dev"},"type":"Opaque"} labels: app: rxminder environment: dev version: v1.0.0 name: couchdb-secret-7ck2cc96g5 namespace: rxminder-dev type: Opaque - apiVersion: v1 kind: Service metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb-service","namespace":"rxminder-dev"},"spec":{"ports":[{"name":"couchdb","port":5984,"protocol":"TCP","targetPort":5984}],"selector":{"app":"rxminder","component":"database"},"type":"ClusterIP"}} labels: app: rxminder component: database environment: dev version: v1.0.0 name: rxminder-couchdb-service namespace: rxminder-dev spec: ports: - name: couchdb port: 5984 protocol: TCP targetPort: 5984 selector: app: rxminder component: database type: ClusterIP - apiVersion: v1 kind: Service metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-service","namespace":"rxminder-dev"},"spec":{"ports":[{"name":"http","port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"rxminder","component":"frontend"},"type":"ClusterIP"}} labels: app: rxminder component: frontend environment: dev version: v1.0.0 name: rxminder-frontend-service namespace: rxminder-dev spec: ports: - name: http port: 80 protocol: TCP targetPort: 80 selector: app: rxminder component: frontend type: ClusterIP - apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb-pvc","namespace":"rxminder-dev"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"}} labels: app: rxminder component: database environment: dev version: v1.0.0 name: rxminder-couchdb-pvc namespace: rxminder-dev spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard - apiVersion: apps/v1 kind: Deployment metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend","namespace":"rxminder-dev"},"spec":{"replicas":1,"selector":{"matchLabels":{"component":"frontend"}},"template":{"metadata":{"labels":{"component":"frontend"}},"spec":{"containers":[{"env":[{"name":"NODE_ENV","value":"development"},{"name":"LOG_LEVEL","value":"debug"}],"envFrom":[{"configMapRef":{"name":"rxminder-config-4229dg76t6"}}],"image":"gitea-http.taildb3494.ts.net/will/rxminder:dev","livenessProbe":{"httpGet":{"path":"/","port":80},"initialDelaySeconds":30,"periodSeconds":30},"name":"frontend","ports":[{"containerPort":80}],"readinessProbe":{"httpGet":{"path":"/","port":80},"initialDelaySeconds":5,"periodSeconds":5},"resources":{"limits":{"cpu":"40m","memory":"32Mi"},"requests":{"cpu":"20m","memory":"16Mi"}}}],"imagePullSecrets":[{"name":"rxminder-registry-secret"}]}}}} labels: app: rxminder component: frontend environment: dev version: v1.0.0 name: rxminder-frontend namespace: rxminder-dev spec: replicas: 1 selector: matchLabels: component: frontend template: metadata: labels: component: frontend spec: containers: - env: - name: NODE_ENV value: development - name: LOG_LEVEL value: debug envFrom: - configMapRef: name: rxminder-config-4229dg76t6 image: gitea-http.taildb3494.ts.net/will/rxminder:dev livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 30 periodSeconds: 30 name: frontend ports: - containerPort: 80 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 resources: limits: cpu: 40m memory: 32Mi requests: cpu: 20m memory: 16Mi imagePullSecrets: - name: rxminder-registry-secret - apiVersion: apps/v1 kind: StatefulSet metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-couchdb","namespace":"rxminder-dev"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"rxminder","component":"database"}},"serviceName":"rxminder-couchdb-service","template":{"metadata":{"labels":{"app":"rxminder","component":"database"}},"spec":{"containers":[{"env":[{"name":"COUCHDB_USER","valueFrom":{"secretKeyRef":{"key":"username","name":"couchdb-secret-7ck2cc96g5"}}},{"name":"COUCHDB_PASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"couchdb-secret-7ck2cc96g5"}}}],"image":"couchdb:3.3.2","livenessProbe":{"httpGet":{"path":"/_up","port":5984},"initialDelaySeconds":60,"periodSeconds":30},"name":"couchdb","ports":[{"containerPort":5984}],"readinessProbe":{"httpGet":{"path":"/_up","port":5984},"initialDelaySeconds":10,"periodSeconds":5},"resources":{"limits":{"cpu":"60m","memory":"128Mi"},"requests":{"cpu":"30m","memory":"64Mi"}},"volumeMounts":[{"mountPath":"/opt/couchdb/data","name":"couchdb-data"}]}]}},"volumeClaimTemplates":[{"metadata":{"labels":{"app":"rxminder","component":"database"},"name":"couchdb-data"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Gi"}},"storageClassName":"standard"}}]}} labels: app: rxminder component: database environment: dev version: v1.0.0 name: rxminder-couchdb namespace: rxminder-dev spec: replicas: 1 selector: matchLabels: app: rxminder component: database serviceName: rxminder-couchdb-service template: metadata: labels: app: rxminder component: database spec: containers: - env: - name: COUCHDB_USER valueFrom: secretKeyRef: key: username name: couchdb-secret-7ck2cc96g5 - name: COUCHDB_PASSWORD valueFrom: secretKeyRef: key: password name: couchdb-secret-7ck2cc96g5 image: couchdb:3.3.2 livenessProbe: httpGet: path: /_up port: 5984 initialDelaySeconds: 60 periodSeconds: 30 name: couchdb ports: - containerPort: 5984 readinessProbe: httpGet: path: /_up port: 5984 initialDelaySeconds: 10 periodSeconds: 5 resources: limits: cpu: 60m memory: 128Mi requests: cpu: 30m memory: 64Mi volumeMounts: - mountPath: /opt/couchdb/data name: couchdb-data volumeClaimTemplates: - metadata: labels: app: rxminder component: database name: couchdb-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard - apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"autoscaling/v2","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-hpa","namespace":"rxminder-dev"},"spec":{"maxReplicas":3,"metrics":[{"resource":{"name":"cpu","target":{"averageUtilization":50,"type":"Utilization"}},"type":"Resource"}],"minReplicas":1,"scaleTargetRef":{"apiVersion":"apps/v1","kind":"Deployment","name":"rxminder-frontend"}}} labels: app: rxminder component: frontend environment: dev version: v1.0.0 name: rxminder-frontend-hpa namespace: rxminder-dev spec: maxReplicas: 3 metrics: - resource: name: cpu target: averageUtilization: 50 type: Utilization type: Resource minReplicas: 1 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: rxminder-frontend - apiVersion: batch/v1 kind: Job metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-db-seed","namespace":"rxminder-dev"},"spec":{"backoffLimit":4,"template":{"metadata":{"labels":{"app":"rxminder","component":"database"}},"spec":{"containers":[{"args":["# Wait for CouchDB to be ready\necho \"Waiting for CouchDB to be ready...\"\nuntil curl -f http://couchdb-service:5984/_up 2\u003e/dev/null; do\n sleep 2\ndone\n\n# Create databases\necho \"Creating databases...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app\n\n# Create default admin user\necho \"Creating default admin user...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"$COUCHDB_USER\\\",\n \\\"password\\\": \\\"$COUCHDB_PASSWORD\\\",\n \\\"roles\\\": [\\\"admin\\\"],\n \\\"type\\\": \\\"user\\\"\n }\"\n\n# Create design documents for views\necho \"Creating design documents...\"\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"views\": {\n \"by_name\": {\n \"map\": \"function(doc) { if (doc.type === \\\"medication\\\") emit(doc.name, doc); }\"\n },\n \"by_user\": {\n \"map\": \"function(doc) { if (doc.type === \\\"medication\\\") emit(doc.userId, doc); }\"\n }\n }\n }'\n\ncurl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"views\": {\n \"by_medication\": {\n \"map\": \"function(doc) { if (doc.type === \\\"reminder\\\") emit(doc.medicationId, doc); }\"\n },\n \"by_user\": {\n \"map\": \"function(doc) { if (doc.type === \\\"reminder\\\") emit(doc.userId, doc); }\"\n }\n }\n }'\n\n# Create a sample user document for reference\n # Create design document for authentication users\n curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"views\": {\n \"by_username\": {\n \"map\": \"function(doc) { if (doc.type === \\\"user\\\" \u0026\u0026 doc.username) emit(doc.username, doc); }\"\n },\n \"by_email\": {\n \"map\": \"function(doc) { if (doc.type === \\\"user\\\" \u0026\u0026 doc.email) emit(doc.email, doc); }\"\n }\n }\n }'\necho \"Creating sample user document...\"\ncurl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"type\": \"user\",\n \"name\": \"sample_user\",\n \"email\": \"user@example.com\",\n \"createdAt\": \"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'\"\n }'\n\necho \"Database seeding completed with default admin user\"\n"],"command":["/bin/sh","-c"],"env":[{"name":"COUCHDB_USER","valueFrom":{"secretKeyRef":{"key":"username","name":"couchdb-secret-7ck2cc96g5"}}},{"name":"COUCHDB_PASSWORD","valueFrom":{"secretKeyRef":{"key":"password","name":"couchdb-secret-7ck2cc96g5"}}}],"image":"couchdb:3.3.2","name":"db-seeder"}],"restartPolicy":"Never"}}}} labels: app: rxminder component: database environment: dev version: v1.0.0 name: rxminder-db-seed namespace: rxminder-dev spec: backoffLimit: 4 template: metadata: labels: app: rxminder component: database spec: containers: - args: - | # Wait for CouchDB to be ready echo "Waiting for CouchDB to be ready..." until curl -f http://couchdb-service:5984/_up 2>/dev/null; do sleep 2 done # Create databases echo "Creating databases..." curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app # Create default admin user echo "Creating default admin user..." curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$COUCHDB_USER\", \"password\": \"$COUCHDB_PASSWORD\", \"roles\": [\"admin\"], \"type\": \"user\" }" # Create design documents for views echo "Creating design documents..." curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \ -H "Content-Type: application/json" \ -d '{ "views": { "by_name": { "map": "function(doc) { if (doc.type === \"medication\") emit(doc.name, doc); }" }, "by_user": { "map": "function(doc) { if (doc.type === \"medication\") emit(doc.userId, doc); }" } } }' curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \ -H "Content-Type: application/json" \ -d '{ "views": { "by_medication": { "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.medicationId, doc); }" }, "by_user": { "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.userId, doc); }" } } }' # Create a sample user document for reference # Create design document for authentication users curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \ -H "Content-Type: application/json" \ -d '{ "views": { "by_username": { "map": "function(doc) { if (doc.type === \"user\" && doc.username) emit(doc.username, doc); }" }, "by_email": { "map": "function(doc) { if (doc.type === \"user\" && doc.email) emit(doc.email, doc); }" } } }' echo "Creating sample user document..." curl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \ -H "Content-Type: application/json" \ -d '{ "type": "user", "name": "sample_user", "email": "user@example.com", "createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" }' echo "Database seeding completed with default admin user" command: - /bin/sh - -c env: - name: COUCHDB_USER valueFrom: secretKeyRef: key: username name: couchdb-secret-7ck2cc96g5 - name: COUCHDB_PASSWORD valueFrom: secretKeyRef: key: password name: couchdb-secret-7ck2cc96g5 image: couchdb:3.3.2 name: db-seeder restartPolicy: Never - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-ingress","namespace":"rxminder-dev"},"spec":{"ingressClassName":"nginx","rules":[{"host":"rxminder-dev.local","http":{"paths":[{"backend":{"service":{"name":"rxminder-frontend-service","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}} labels: app: rxminder component: frontend environment: dev version: v1.0.0 name: rxminder-ingress namespace: rxminder-dev spec: ingressClassName: nginx rules: - host: rxminder-dev.local http: paths: - backend: service: name: rxminder-frontend-service port: number: 80 path: / pathType: Prefix - apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"database","environment":"dev","version":"v1.0.0"},"name":"rxminder-database-policy","namespace":"rxminder-dev"},"spec":{"egress":[{"ports":[{"port":5984,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"database"}}}]}],"ingress":[{"from":[{"podSelector":{"matchLabels":{"component":"frontend"}}}],"ports":[{"port":5984,"protocol":"TCP"}]}],"podSelector":{"matchLabels":{"component":"database"}},"policyTypes":["Ingress","Egress"]}} labels: app: rxminder component: database environment: dev version: v1.0.0 name: rxminder-database-policy namespace: rxminder-dev spec: egress: - ports: - port: 5984 protocol: TCP to: - podSelector: matchLabels: component: database ingress: - from: - podSelector: matchLabels: component: frontend ports: - port: 5984 protocol: TCP podSelector: matchLabels: component: database policyTypes: - Ingress - Egress - apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"networking.k8s.io/v1","kind":"NetworkPolicy","metadata":{"annotations":{},"labels":{"app":"rxminder","component":"frontend","environment":"dev","version":"v1.0.0"},"name":"rxminder-frontend-policy","namespace":"rxminder-dev"},"spec":{"egress":[{"ports":[{"port":5984,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"database"}}}]},{"ports":[{"port":80,"protocol":"TCP"}],"to":[{"podSelector":{"matchLabels":{"component":"frontend"}}}]}],"ingress":[{"from":[{"podSelector":{"matchLabels":{"component":"frontend"}}}],"ports":[{"port":80,"protocol":"TCP"}]}],"podSelector":{"matchLabels":{"component":"frontend"}},"policyTypes":["Ingress","Egress"]}} labels: app: rxminder component: frontend environment: dev version: v1.0.0 name: rxminder-frontend-policy namespace: rxminder-dev spec: egress: - ports: - port: 5984 protocol: TCP to: - podSelector: matchLabels: component: database - ports: - port: 80 protocol: TCP to: - podSelector: matchLabels: component: frontend ingress: - from: - podSelector: matchLabels: component: frontend ports: - port: 80 protocol: TCP podSelector: matchLabels: component: frontend policyTypes: - Ingress - Egress kind: List metadata: {} ## Migration Path - Legacy shell scripts remain available for backward compatibility - Gradual migration: dev → staging → production - Zero-downtime deployment capability Co-authored-by: Assistant --- .env.example-usage | 5 + KUSTOMIZE_MIGRATION.md | 296 ++++++++++ Makefile | 172 +++++- docs/ENVIRONMENT_VARIABLES.md | 538 ++++++++++++++++++ k8s-kustomize/README.md | 350 ++++++++++++ k8s-kustomize/base/config.env | 60 ++ k8s-kustomize/base/couchdb-pvc.yaml | 14 + k8s-kustomize/base/couchdb-service.yaml | 17 + k8s-kustomize/base/couchdb-statefulset.yaml | 70 +++ k8s-kustomize/base/db-seed-job.yaml | 107 ++++ k8s-kustomize/base/frontend-deployment.yaml | 2 +- k8s-kustomize/base/frontend-service.yaml | 17 + k8s-kustomize/base/hpa.yaml | 21 + k8s-kustomize/base/ingress.yaml | 29 + k8s-kustomize/base/kustomization.yaml | 24 +- k8s-kustomize/base/network-policy.yaml | 68 +++ k8s-kustomize/base/registry-config.json | 21 + k8s-kustomize/overlays/dev/config.env | 23 + k8s-kustomize/overlays/dev/kustomization.yaml | 37 +- .../overlays/prod/couchdb-resources.yaml | 126 ++++ .../overlays/prod/frontend-resources.yaml | 79 +++ k8s-kustomize/overlays/prod/ingress-prod.yaml | 65 +++ .../overlays/prod/kustomization.yaml | 127 +++++ scripts/deploy-with-env.sh | 426 ++++++++++++++ scripts/generate-config.sh | 474 +++++++++++++++ 25 files changed, 3133 insertions(+), 35 deletions(-) create mode 100644 .env.example-usage create mode 100644 KUSTOMIZE_MIGRATION.md create mode 100644 docs/ENVIRONMENT_VARIABLES.md create mode 100644 k8s-kustomize/README.md create mode 100644 k8s-kustomize/base/config.env create mode 100644 k8s-kustomize/base/couchdb-pvc.yaml create mode 100644 k8s-kustomize/base/couchdb-service.yaml create mode 100644 k8s-kustomize/base/couchdb-statefulset.yaml create mode 100644 k8s-kustomize/base/db-seed-job.yaml create mode 100644 k8s-kustomize/base/frontend-service.yaml create mode 100644 k8s-kustomize/base/hpa.yaml create mode 100644 k8s-kustomize/base/ingress.yaml create mode 100644 k8s-kustomize/base/network-policy.yaml create mode 100644 k8s-kustomize/base/registry-config.json create mode 100644 k8s-kustomize/overlays/dev/config.env create mode 100644 k8s-kustomize/overlays/prod/couchdb-resources.yaml create mode 100644 k8s-kustomize/overlays/prod/frontend-resources.yaml create mode 100644 k8s-kustomize/overlays/prod/ingress-prod.yaml create mode 100644 k8s-kustomize/overlays/prod/kustomization.yaml create mode 100755 scripts/deploy-with-env.sh create mode 100755 scripts/generate-config.sh diff --git a/.env.example-usage b/.env.example-usage new file mode 100644 index 0000000..971b801 --- /dev/null +++ b/.env.example-usage @@ -0,0 +1,5 @@ +APP_NAME=rxminder +NODE_ENV=development +LOG_LEVEL=debug +IMAGE_TAG=latest +INGRESS_HOST=rxminder-dev.local diff --git a/KUSTOMIZE_MIGRATION.md b/KUSTOMIZE_MIGRATION.md new file mode 100644 index 0000000..c6f2650 --- /dev/null +++ b/KUSTOMIZE_MIGRATION.md @@ -0,0 +1,296 @@ +# Kustomize Migration Complete! 🎉 + +## Migration Summary + +The rxminder application has been successfully migrated from shell script-based Kubernetes deployments to **Kustomize**, providing a more maintainable, scalable, and GitOps-ready deployment strategy. + +## What Was Accomplished + +### ✅ 1. Complete Base Resources + +- ✅ Converted all template files to base Kustomize resources +- ✅ Created `frontend-deployment.yaml`, `frontend-service.yaml` +- ✅ Created `couchdb-statefulset.yaml`, `couchdb-service.yaml`, `couchdb-pvc.yaml` +- ✅ Created `ingress.yaml`, `network-policy.yaml`, `hpa.yaml`, `db-seed-job.yaml` +- ✅ Removed template variables and used Kustomize's generators +- ✅ Set up ConfigMap generation from `config.env` +- ✅ Configured Secret generation for CouchDB credentials + +### ✅ 2. Environment Overlays + +- ✅ **Development Overlay**: Optimized for local development + - Namespace: `rxminder-dev` + - Minimal resources (16Mi-32Mi memory) + - Debug logging enabled + - Weak passwords for development + - Single replica for resource conservation +- ✅ **Production Overlay**: Enterprise-ready configuration + - Namespace: `rxminder-prod` + - Production resources (256Mi-512Mi memory) + - Security hardening (security contexts, network policies) + - TLS/HTTPS enabled + - High availability (3 frontend replicas) + - Production storage (10Gi SSD) + - Monitoring and alerting enabled + +### ✅ 3. Updated Makefile + +- ✅ Added comprehensive Kustomize targets +- ✅ Maintained backward compatibility with legacy shell scripts +- ✅ Created convenient aliases for quick deployment +- ✅ Added validation and debugging commands + +### ✅ 4. Documentation + +- ✅ Comprehensive `k8s-kustomize/README.md` +- ✅ Migration guide and troubleshooting +- ✅ Best practices and security considerations + +## Key Benefits Achieved + +### 🚀 Simplified Deployment + +```bash +# Before (shell scripts with variable substitution) +APP_NAME=rxminder NAMESPACE=production ./scripts/deploy.sh + +# After (Kustomize) +make deploy-prod +# or +kubectl apply -k k8s-kustomize/overlays/prod +``` + +### 🔒 Environment Isolation + +- **Clear separation** between dev, staging, and production +- **Namespace isolation** prevents cross-environment contamination +- **Environment-specific configurations** without duplication + +### 🔧 GitOps Ready + +- **ArgoCD/Flux compatible** out of the box +- **Declarative configuration** with no templating complexity +- **Git-based workflow** for deployment automation + +### ✅ Better Validation + +- **Built-in YAML validation** catches errors early +- **Dry-run capabilities** for safe deployments +- **Configuration drift detection** + +### 📈 Standard Approach + +- **Kubernetes-native** solution (no external dependencies) +- **Industry standard** approach +- **Better team onboarding** with familiar tooling + +## Available Commands + +### Quick Start Commands + +```bash +# Development +make deploy-dev # Deploy to development +make quick-deploy-dev # Build and deploy to development +make status-dev # Check development status + +# Production +make deploy-prod # Deploy to production +make quick-deploy-prod # Build and deploy to production +make status-prod # Check production status +``` + +### Validation & Debugging + +```bash +make validate-kustomize # Validate all configurations +make kustomize-dry-run-dev # Test development deployment +make kustomize-diff-prod # Show production differences +make kustomize-build-dev # Build development manifests +``` + +### Legacy Support (Still Available) + +```bash +make k8s-deploy # Legacy shell script deployment +make k8s-undeploy # Legacy shell script removal +``` + +## Directory Structure + +``` +k8s-kustomize/ +├── base/ # Shared base configuration +│ ├── kustomization.yaml # Base kustomization +│ ├── config.env # Environment variables +│ ├── frontend-deployment.yaml # Frontend workload +│ ├── couchdb-statefulset.yaml # Database workload +│ ├── *-service.yaml # Services +│ ├── ingress.yaml # Ingress configuration +│ ├── network-policy.yaml # Security policies +│ └── ... # Other resources +├── overlays/ +│ ├── dev/ # Development environment +│ │ └── kustomization.yaml # Dev customizations +│ └── prod/ # Production environment +│ ├── kustomization.yaml # Prod customizations +│ ├── frontend-resources.yaml # Prod frontend config +│ ├── couchdb-resources.yaml # Prod database config +│ └── ingress-prod.yaml # Prod ingress config +└── README.md # Comprehensive documentation +``` + +## Security Enhancements + +### Production Security Features + +- ✅ **Security contexts**: Non-root containers, read-only filesystems +- ✅ **Network policies**: Restricted pod-to-pod communication +- ✅ **Resource limits**: Prevent resource exhaustion attacks +- ✅ **TLS encryption**: HTTPS with cert-manager integration +- ✅ **RBAC ready**: Role-based access control compatible +- ✅ **Secret management**: External secret store integration points + +### Development Security + +- ✅ **Namespace isolation**: Separated from production +- ✅ **Weak credentials**: Safe for development environment +- ✅ **Relaxed policies**: Optimized for developer productivity + +## Next Steps & Recommendations + +### Immediate Actions (Week 1) + +1. **Test deployments** in development environment +2. **Validate configuration** with your team +3. **Update CI/CD pipelines** to use Kustomize commands +4. **Train team members** on new deployment process + +### Short-term Goals (Month 1) + +1. **Production deployment**: Schedule maintenance window +2. **Secret management**: Implement external secret store + - Consider: External Secrets Operator, HashiCorp Vault, or cloud-native solutions +3. **Monitoring integration**: Connect with your monitoring stack +4. **Documentation updates**: Update runbooks and procedures + +### Long-term Goals (Quarter 1) + +1. **GitOps implementation**: Set up ArgoCD or Flux +2. **Multi-environment**: Add staging environment overlay +3. **Advanced features**: Implement blue-green or canary deployments +4. **Cleanup**: Remove legacy shell scripts after validation + +### Adding New Environments + +To add a staging environment: + +```bash +# 1. Create staging overlay +mkdir -p k8s-kustomize/overlays/staging + +# 2. Create staging kustomization.yaml +cat > k8s-kustomize/overlays/staging/kustomization.yaml << EOF +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +namespace: rxminder-staging + +labels: + - pairs: + environment: staging + +images: + - name: gitea-http.taildb3494.ts.net/will/rxminder + newTag: staging +EOF + +# 3. Add Makefile targets +# Add to Makefile: +# deploy-staging: kustomize-deploy-staging +# kustomize-deploy-staging: +# kubectl apply -k k8s-kustomize/overlays/staging +``` + +## Migration Path Options + +### Option 1: Gradual Migration (Recommended) + +1. ✅ **Development first**: Already completed and tested +2. **Staging next**: Validate with staging workloads +3. **Production last**: Schedule maintenance window for production migration + +### Option 2: Parallel Running + +1. **Keep both systems**: Run legacy and Kustomize side-by-side +2. **Test extensively**: Validate Kustomize deployments +3. **Switch over**: Move traffic from legacy to Kustomize deployments + +### Option 3: Feature Flag Approach + +1. **Environment variable**: Control deployment method via feature flag +2. **Gradual rollout**: Enable Kustomize for percentage of deployments +3. **Full migration**: Switch to 100% Kustomize when validated + +## Rollback Plan + +If issues arise, legacy deployment is still available: + +```bash +# Emergency rollback to legacy deployment +make k8s-undeploy # Remove Kustomize deployment +make k8s-deploy # Deploy using legacy scripts +``` + +## Validation Checklist + +Before production migration: + +- [ ] Development deployment working correctly +- [ ] All services accessible and functional +- [ ] Database persistence working +- [ ] Ingress and networking functional +- [ ] Resource limits appropriate +- [ ] Security policies working +- [ ] Monitoring and logging operational +- [ ] Team trained on new commands +- [ ] CI/CD updated +- [ ] Rollback plan tested + +## Success Metrics + +Track these metrics to validate the migration success: + +- **Deployment time**: Should be faster and more reliable +- **Error rate**: Fewer deployment failures +- **Time to recovery**: Faster rollbacks and fixes +- **Team productivity**: Easier environment management +- **Configuration drift**: Better consistency across environments + +## Support & Resources + +- **Documentation**: `k8s-kustomize/README.md` +- **Troubleshooting**: Check events and logs with provided commands +- **Validation**: Use `make validate-kustomize` for configuration checks +- **Testing**: Use dry-run commands before actual deployments + +## Conclusion + +The Kustomize migration provides a robust foundation for scaling your Kubernetes deployments. The new system offers: + +- **Maintainability**: Clear separation of concerns +- **Scalability**: Easy to add new environments +- **Security**: Production-grade security configurations +- **Reliability**: Better validation and error handling +- **Team Efficiency**: Standardized, well-documented processes + +The legacy shell script approach is still available as a fallback, ensuring zero downtime during the migration period. Take your time to validate the new system thoroughly before fully committing to the migration. + +**Happy deploying! 🚀** + +--- + +_This migration was completed on $(date). For questions or issues, refer to the troubleshooting section in the README or consult your team's Kubernetes documentation._ diff --git a/Makefile b/Makefile index 82bc7ae..80e124b 100644 --- a/Makefile +++ b/Makefile @@ -175,26 +175,113 @@ seed-production: ## Seed production database ##@ Kubernetes -k8s-deploy: ## Deploy to Kubernetes - @printf "$(BLUE)Deploying to Kubernetes...$(RESET)\n" +k8s-deploy: ## Deploy to Kubernetes (legacy shell script) + @printf "$(BLUE)Deploying to Kubernetes (legacy)...$(RESET)\n" @bun run deploy:k8s -k8s-undeploy: ## Remove from Kubernetes - @printf "$(BLUE)Removing from Kubernetes...$(RESET)\n" +k8s-undeploy: ## Remove from Kubernetes (legacy shell script) + @printf "$(BLUE)Removing from Kubernetes (legacy)...$(RESET)\n" @bun run undeploy:k8s -k8s-deploy-dry: ## Dry run Kubernetes deployment - @printf "$(BLUE)Dry run Kubernetes deployment...$(RESET)\n" +k8s-deploy-dry: ## Dry run Kubernetes deployment (legacy) + @printf "$(BLUE)Dry run Kubernetes deployment (legacy)...$(RESET)\n" @bun run deploy:k8s --dry-run -k8s-undeploy-dry: ## Dry run Kubernetes removal - @printf "$(BLUE)Dry run Kubernetes removal...$(RESET)\n" +k8s-undeploy-dry: ## Dry run Kubernetes removal (legacy) + @printf "$(BLUE)Dry run Kubernetes removal (legacy)...$(RESET)\n" @bun run undeploy:k8s --dry-run k8s-status: ## Show Kubernetes deployment status @printf "$(BLUE)Showing Kubernetes status...$(RESET)\n" @bun run deploy:k8s --status +##@ Kustomize (Recommended) + +kustomize-deploy-dev: ## Deploy to Kubernetes using Kustomize (development) + @printf "$(BLUE)Deploying to Kubernetes with Kustomize (dev)...$(RESET)\n" + @kubectl apply -k k8s-kustomize/overlays/dev + +kustomize-deploy-prod: ## Deploy to Kubernetes using Kustomize (production) + @printf "$(BLUE)Deploying to Kubernetes with Kustomize (prod)...$(RESET)\n" + @kubectl apply -k k8s-kustomize/overlays/prod + +kustomize-undeploy-dev: ## Remove from Kubernetes using Kustomize (development) + @printf "$(BLUE)Removing from Kubernetes with Kustomize (dev)...$(RESET)\n" + @kubectl delete -k k8s-kustomize/overlays/dev --ignore-not-found=true + +kustomize-undeploy-prod: ## Remove from Kubernetes using Kustomize (production) + @printf "$(BLUE)Removing from Kubernetes with Kustomize (prod)...$(RESET)\n" + @kubectl delete -k k8s-kustomize/overlays/prod --ignore-not-found=true + +kustomize-dry-run-dev: ## Dry run Kustomize deployment (development) + @printf "$(BLUE)Dry run Kustomize deployment (dev)...$(RESET)\n" + @kubectl apply -k k8s-kustomize/overlays/dev --dry-run=client -o yaml + +kustomize-dry-run-prod: ## Dry run Kustomize deployment (production) + @printf "$(BLUE)Dry run Kustomize deployment (prod)...$(RESET)\n" + @kubectl apply -k k8s-kustomize/overlays/prod --dry-run=client -o yaml + +kustomize-validate-dev: ## Validate Kustomize configuration (development) + @printf "$(BLUE)Validating Kustomize configuration (dev)...$(RESET)\n" + @kubectl kustomize k8s-kustomize/overlays/dev | kubectl apply --dry-run=client --validate=true -f - + +kustomize-validate-prod: ## Validate Kustomize configuration (production) + @printf "$(BLUE)Validating Kustomize configuration (prod)...$(RESET)\n" + @kubectl kustomize k8s-kustomize/overlays/prod | kubectl apply --dry-run=client --validate=true -f - + +##@ Environment Configuration + +generate-config-dev: ## Generate development configuration from environment variables + @printf "$(BLUE)Generating development configuration...$(RESET)\n" + @./scripts/generate-config.sh dev + +generate-config-prod: ## Generate production configuration from environment variables + @printf "$(BLUE)Generating production configuration...$(RESET)\n" + @./scripts/generate-config.sh prod + +generate-config-staging: ## Generate staging configuration from environment variables + @printf "$(BLUE)Generating staging configuration...$(RESET)\n" + @./scripts/generate-config.sh staging + +generate-config-all: ## Generate all environment configurations + @printf "$(BLUE)Generating all environment configurations...$(RESET)\n" + @./scripts/generate-config.sh dev + @./scripts/generate-config.sh prod + @./scripts/generate-config.sh staging + +validate-config: ## Validate generated configuration + @printf "$(BLUE)Validating generated configuration...$(RESET)\n" + @./scripts/generate-config.sh --validate + +generate-secrets-template: ## Generate secrets template files + @printf "$(BLUE)Generating secrets templates...$(RESET)\n" + @./scripts/generate-config.sh dev --secrets + @./scripts/generate-config.sh prod --secrets + +kustomize-diff-dev: ## Show diff for Kustomize deployment (development) + @printf "$(BLUE)Showing Kustomize diff (dev)...$(RESET)\n" + @kubectl diff -k k8s-kustomize/overlays/dev || true + +kustomize-diff-prod: ## Show diff for Kustomize deployment (production) + @printf "$(BLUE)Showing Kustomize diff (prod)...$(RESET)\n" + @kubectl diff -k k8s-kustomize/overlays/prod || true + +kustomize-build-dev: ## Build Kustomize manifests (development) + @printf "$(BLUE)Building Kustomize manifests (dev)...$(RESET)\n" + @kubectl kustomize k8s-kustomize/overlays/dev + +kustomize-build-prod: ## Build Kustomize manifests (production) + @printf "$(BLUE)Building Kustomize manifests (prod)...$(RESET)\n" + @kubectl kustomize k8s-kustomize/overlays/prod + +kustomize-status-dev: ## Show Kustomize deployment status (development) + @printf "$(BLUE)Showing Kustomize deployment status (dev)...$(RESET)\n" + @kubectl get all -n rxminder-dev -l app=rxminder + +kustomize-status-prod: ## Show Kustomize deployment status (production) + @printf "$(BLUE)Showing Kustomize deployment status (prod)...$(RESET)\n" + @kubectl get all -n rxminder-prod -l app=rxminder + ##@ Docker docker-setup: ## Setup Docker buildx @@ -310,8 +397,75 @@ full-check-backend: lint test-backend ## Run backend code quality check (unit + full-check: lint test-all ## Run full code quality check (includes unit + integration + E2E tests) @printf "$(GREEN)Full check completed!$(RESET)\n" -quick-deploy: build k8s-deploy ## Quick build and deploy to Kubernetes +# Legacy deployment (using shell scripts) +quick-deploy: build k8s-deploy ## Quick build and deploy to Kubernetes (legacy) @printf "$(GREEN)Quick deployment completed!$(RESET)\n" +# Kustomize deployment shortcuts +deploy-dev: kustomize-deploy-dev ## Deploy to development environment with Kustomize + @printf "$(GREEN)Development deployment completed!$(RESET)\n" + +deploy-prod: kustomize-deploy-prod ## Deploy to production environment with Kustomize + @printf "$(GREEN)Production deployment completed!$(RESET)\n" + +quick-deploy-dev: build deploy-dev ## Build and deploy to development with Kustomize + @printf "$(GREEN)Quick development deployment completed!$(RESET)\n" + +quick-deploy-prod: build deploy-prod ## Build and deploy to production with Kustomize + @printf "$(GREEN)Quick production deployment completed!$(RESET)\n" + +undeploy-dev: kustomize-undeploy-dev ## Remove development deployment + @printf "$(GREEN)Development undeploy completed!$(RESET)\n" + +undeploy-prod: kustomize-undeploy-prod ## Remove production deployment + @printf "$(GREEN)Production undeploy completed!$(RESET)\n" + +status-dev: kustomize-status-dev ## Show development deployment status + @printf "$(GREEN)Development status check completed!$(RESET)\n" + +status-prod: kustomize-status-prod ## Show production deployment status + @printf "$(GREEN)Production status check completed!$(RESET)\n" + +validate-kustomize: kustomize-validate-dev kustomize-validate-prod ## Validate all Kustomize configurations + @printf "$(GREEN)Kustomize validation completed!$(RESET)\n" + +##@ Environment-Aware Deployment + +deploy-with-env-dev: ## Deploy to development using environment variables + @printf "$(BLUE)Deploying to development with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh dev apply + +deploy-with-env-prod: ## Deploy to production using environment variables + @printf "$(BLUE)Deploying to production with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh prod apply + +deploy-with-env-staging: ## Deploy to staging using environment variables + @printf "$(BLUE)Deploying to staging with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh staging apply + +undeploy-with-env-dev: ## Remove development deployment using environment variables + @printf "$(BLUE)Removing development deployment with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh dev delete + +undeploy-with-env-prod: ## Remove production deployment using environment variables + @printf "$(BLUE)Removing production deployment with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh prod delete + +diff-with-env-dev: ## Show development diff using environment variables + @printf "$(BLUE)Showing development diff with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh dev diff + +diff-with-env-prod: ## Show production diff using environment variables + @printf "$(BLUE)Showing production diff with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh prod diff + +status-with-env-dev: ## Show development status using environment variables + @printf "$(BLUE)Showing development status with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh dev status + +status-with-env-prod: ## Show production status using environment variables + @printf "$(BLUE)Showing production status with environment variables...$(RESET)\n" + @./scripts/deploy-with-env.sh prod status + reset: clean install ## Reset project (clean and reinstall) @printf "$(GREEN)Project reset completed!$(RESET)\n" diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md new file mode 100644 index 0000000..f1daf5c --- /dev/null +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,538 @@ +# Environment Variables Guide + +This guide explains how to use environment variables with the rxminder Kustomize deployment system. + +## Overview + +The rxminder application supports multiple ways to configure deployments using environment variables: + +1. **Static Configuration**: Pre-generated config files +2. **Dynamic Configuration**: Runtime environment variable injection +3. **Hybrid Approach**: Combination of both methods + +## Environment Variable Sources + +Variables are loaded in the following priority order (last wins): + +1. `~/.env` - Global user environment +2. `./.env` - Project-wide environment +3. `./.env.{environment}` - Environment-specific (e.g., `.env.dev`, `.env.prod`) +4. `./.env.local` - Local overrides (git-ignored) +5. System environment variables + +## Quick Start + +### Method 1: Generate Static Configuration + +```bash +# Generate configuration from your environment variables +make generate-config-dev +make generate-config-prod + +# Deploy using generated config +make deploy-dev +``` + +### Method 2: Dynamic Environment Injection + +```bash +# Deploy with runtime environment variable substitution +make deploy-with-env-dev +make deploy-with-env-prod +``` + +## Environment Files Setup + +### 1. Global User Environment (`~/.env`) + +Store your personal/machine-specific settings: + +```bash +# ~/.env +REGISTRY_USERNAME=your_username +REGISTRY_EMAIL=your_email@example.com +KUBECONFIG=/path/to/your/kubeconfig +``` + +### 2. Project Environment (`./.env`) + +Store project-wide defaults: + +```bash +# ./.env +APP_NAME=rxminder +REGISTRY_URL=gitea-http.taildb3494.ts.net +IMAGE_REPOSITORY=will/rxminder +COUCHDB_DATABASE_NAME=meds_app +``` + +### 3. Environment-Specific Files + +#### Development (`./.env.dev`) + +```bash +NODE_ENV=development +LOG_LEVEL=debug +DEBUG=true +IMAGE_TAG=dev +INGRESS_HOST=rxminder-dev.local +DEV_MODE=true +ENABLE_MONITORING=false + +# Development database +COUCHDB_USERNAME=admin +COUCHDB_PASSWORD=devpass123 + +# Development resource limits +FRONTEND_MEMORY_REQUEST=16Mi +FRONTEND_MEMORY_LIMIT=32Mi +COUCHDB_MEMORY_REQUEST=64Mi +COUCHDB_MEMORY_LIMIT=128Mi +STORAGE_SIZE=1Gi +``` + +#### Production (`./.env.prod`) + +```bash +NODE_ENV=production +LOG_LEVEL=warn +DEBUG=false +IMAGE_TAG=v1.0.0 +INGRESS_HOST=rxminder.yourdomain.com +ENABLE_MONITORING=true +ENABLE_METRICS=true +ENABLE_TRACING=true + +# Production performance +CACHE_TTL=3600 +REQUEST_TIMEOUT=30000 +MAX_CONNECTIONS=200 + +# Production resources +FRONTEND_REPLICAS=3 +FRONTEND_MEMORY_REQUEST=256Mi +FRONTEND_MEMORY_LIMIT=512Mi +COUCHDB_MEMORY_REQUEST=512Mi +COUCHDB_MEMORY_LIMIT=1Gi +STORAGE_SIZE=10Gi +STORAGE_CLASS=ssd + +# Security +ENABLE_SECURITY_HEADERS=true +ENABLE_RATE_LIMITING=true +CORS_ORIGIN=https://rxminder.yourdomain.com + +# TLS/SSL +ENABLE_TLS=true +CERT_MANAGER_ISSUER=letsencrypt-prod +``` + +#### Staging (`./.env.staging`) + +```bash +NODE_ENV=staging +LOG_LEVEL=info +DEBUG=false +IMAGE_TAG=staging +INGRESS_HOST=staging.rxminder.yourdomain.com +ENABLE_MONITORING=true + +# Staging resources (between dev and prod) +FRONTEND_REPLICAS=2 +FRONTEND_MEMORY_REQUEST=128Mi +FRONTEND_MEMORY_LIMIT=256Mi +STORAGE_SIZE=5Gi +``` + +### 4. Local Overrides (`./.env.local`) + +**Note**: This file should be in `.gitignore` and used for sensitive local settings: + +```bash +# ./.env.local (DO NOT COMMIT) +COUCHDB_PASSWORD=your_secure_password +REGISTRY_PASSWORD=your_registry_token +API_SECRET_KEY=your_secret_key +JWT_SECRET=your_jwt_secret + +# Local development overrides +INGRESS_HOST=localhost:8080 +DEV_API_URL=http://localhost:5984 +``` + +## Available Environment Variables + +### Core Application Variables + +| Variable | Default | Description | +| ------------- | ------------ | ------------------------------------------------------------ | +| `APP_NAME` | `rxminder` | Application name used in labels and resources | +| `NODE_ENV` | `production` | Runtime environment (`development`, `staging`, `production`) | +| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) | +| `DEBUG` | `false` | Enable debug mode | +| `APP_VERSION` | `1.0.0` | Application version | + +### Container Registry Variables + +| Variable | Default | Description | +| ------------------- | ------------------------------ | -------------------------------- | +| `REGISTRY_URL` | `gitea-http.taildb3494.ts.net` | Container registry URL | +| `IMAGE_REPOSITORY` | `will/rxminder` | Image repository path | +| `IMAGE_TAG` | `latest` | Image tag to deploy | +| `REGISTRY_USERNAME` | - | Registry authentication username | +| `REGISTRY_PASSWORD` | - | Registry authentication password | +| `REGISTRY_EMAIL` | - | Registry authentication email | + +### Database Variables + +| Variable | Default | Description | +| ----------------------- | -------------------------- | ---------------------------- | +| `DB_HOST` | `rxminder-couchdb-service` | Database host | +| `DB_PORT` | `5984` | Database port | +| `COUCHDB_DATABASE_NAME` | `meds_app` | CouchDB database name | +| `COUCHDB_USERNAME` | `admin` | Database username | +| `COUCHDB_PASSWORD` | - | Database password (required) | + +### Network & Ingress Variables + +| Variable | Default | Description | +| --------------------- | -------------------- | -------------------- | +| `INGRESS_HOST` | Environment-specific | Ingress hostname | +| `INGRESS_CLASS` | `nginx` | Ingress class to use | +| `ENABLE_TLS` | `false` | Enable TLS/HTTPS | +| `CERT_MANAGER_ISSUER` | `letsencrypt-prod` | Certificate issuer | +| `CORS_ORIGIN` | `*` | CORS allowed origins | + +### Performance Variables + +| Variable | Default | Description | +| ------------------- | ------- | ------------------------------- | +| `FRONTEND_REPLICAS` | `1` | Number of frontend replicas | +| `CACHE_TTL` | `1800` | Cache time-to-live in seconds | +| `REQUEST_TIMEOUT` | `30000` | Request timeout in milliseconds | +| `MAX_CONNECTIONS` | `100` | Maximum concurrent connections | + +### Resource Limits + +| Variable | Default | Description | +| ------------------------- | ---------- | ----------------------- | +| `FRONTEND_MEMORY_REQUEST` | `32Mi` | Frontend memory request | +| `FRONTEND_MEMORY_LIMIT` | `64Mi` | Frontend memory limit | +| `FRONTEND_CPU_REQUEST` | `20m` | Frontend CPU request | +| `FRONTEND_CPU_LIMIT` | `40m` | Frontend CPU limit | +| `COUCHDB_MEMORY_REQUEST` | `64Mi` | CouchDB memory request | +| `COUCHDB_MEMORY_LIMIT` | `128Mi` | CouchDB memory limit | +| `STORAGE_SIZE` | `1Gi` | Storage volume size | +| `STORAGE_CLASS` | `standard` | Storage class | + +### Monitoring & Observability + +| Variable | Default | Description | +| ------------------- | ------- | -------------------------- | +| `ENABLE_MONITORING` | `false` | Enable monitoring features | +| `ENABLE_METRICS` | `false` | Enable metrics collection | +| `ENABLE_TRACING` | `false` | Enable distributed tracing | +| `METRICS_PORT` | `9090` | Metrics server port | + +### Security Variables + +| Variable | Default | Description | +| ------------------------- | ------- | ----------------------- | +| `ENABLE_SECURITY_HEADERS` | `false` | Enable security headers | +| `ENABLE_RATE_LIMITING` | `false` | Enable rate limiting | +| `API_SECRET_KEY` | - | API secret key | +| `JWT_SECRET` | - | JWT signing secret | + +## Usage Examples + +### Basic Development Setup + +1. Create your development environment file: + +```bash +cat > .env.dev << EOF +NODE_ENV=development +LOG_LEVEL=debug +DEBUG=true +IMAGE_TAG=dev +INGRESS_HOST=rxminder-dev.local +COUCHDB_PASSWORD=devpass123 +EOF +``` + +2. Generate configuration and deploy: + +```bash +make generate-config-dev +make deploy-dev +``` + +### Production Deployment with Secrets + +1. Set up your production environment: + +```bash +# .env.prod (commit this) +NODE_ENV=production +LOG_LEVEL=warn +IMAGE_TAG=v1.0.0 +INGRESS_HOST=rxminder.yourdomain.com +ENABLE_TLS=true + +# .env.local (DO NOT commit) +COUCHDB_PASSWORD=your_secure_production_password +REGISTRY_PASSWORD=your_registry_token +``` + +2. Deploy with environment variables: + +```bash +make deploy-with-env-prod +``` + +### Dynamic Configuration Updates + +Update configuration without rebuilding: + +```bash +# Update environment variable +export LOG_LEVEL=debug + +# Deploy with updated configuration +./scripts/deploy-with-env.sh prod apply +``` + +### Testing Different Configurations + +```bash +# Test with different image tag +export IMAGE_TAG=feature-branch +make diff-with-env-dev + +# Test with different resources +export FRONTEND_REPLICAS=5 +./scripts/deploy-with-env.sh prod dry-run +``` + +## Available Commands + +### Configuration Generation + +```bash +make generate-config-dev # Generate dev config from env vars +make generate-config-prod # Generate prod config from env vars +make generate-config-staging # Generate staging config from env vars +make generate-config-all # Generate all environment configs +make validate-config # Validate generated configuration +make generate-secrets-template # Generate secrets template files +``` + +### Environment-Aware Deployment + +```bash +make deploy-with-env-dev # Deploy dev with env vars +make deploy-with-env-prod # Deploy prod with env vars +make deploy-with-env-staging # Deploy staging with env vars +make undeploy-with-env-dev # Remove dev deployment +make diff-with-env-dev # Show dev diff with env vars +make status-with-env-dev # Show dev status with env vars +``` + +### Direct Script Usage + +```bash +# Generate configuration +./scripts/generate-config.sh dev +./scripts/generate-config.sh prod --secrets + +# Deploy with environment variables +./scripts/deploy-with-env.sh dev apply +./scripts/deploy-with-env.sh prod diff +./scripts/deploy-with-env.sh staging delete +``` + +## Best Practices + +### Security + +1. **Never commit sensitive data**: + - Add `.env.local` to `.gitignore` + - Use external secret management for production + - Rotate passwords regularly + +2. **Use environment-specific files**: + - `.env.dev` for development settings + - `.env.prod` for production configuration + - `.env.local` for sensitive overrides + +### Organization + +1. **Group related variables**: + - Database settings together + - Resource limits together + - Feature flags together + +2. **Use descriptive names**: + - `FRONTEND_MEMORY_LIMIT` vs `MEM_LIMIT` + - `ENABLE_DEBUG_MODE` vs `DEBUG` + +3. **Document your variables**: + - Add comments explaining purpose + - Include example values + - Note required vs optional + +### Development Workflow + +1. **Start with examples**: + + ```bash + cp .env.example .env.dev + # Edit .env.dev with your settings + ``` + +2. **Test configurations**: + + ```bash + make generate-config-dev + make kustomize-dry-run-dev + ``` + +3. **Validate before deploying**: + + ```bash + make validate-config + make diff-with-env-dev + ``` + +## Troubleshooting + +### Common Issues + +1. **Variables not being loaded**: + - Check file permissions (`chmod 644 .env.*`) + - Verify file format (no spaces around `=`) + - Check file encoding (UTF-8) + +2. **Configuration not updating**: + - Regenerate config: `make generate-config-dev` + - Clear cached resources: `kubectl delete configmap -l app=rxminder` + +3. **Deployment failures**: + - Validate syntax: `make validate-config` + - Check logs: `kubectl logs -l app=rxminder` + - Verify resources: `kubectl describe deployment rxminder-frontend` + +### Debug Commands + +```bash +# Check loaded environment variables +./scripts/generate-config.sh dev --dry-run + +# Validate generated configuration +make validate-config + +# Show what would be deployed +./scripts/deploy-with-env.sh dev dry-run + +# Check current deployment status +make status-with-env-dev +``` + +## Migration from Legacy System + +### Step 1: Extract Current Configuration + +```bash +# Export current settings to environment file +echo "APP_NAME=rxminder" > .env.dev +echo "IMAGE_TAG=dev" >> .env.dev +# ... add other variables +``` + +### Step 2: Test New System + +```bash +# Generate and validate configuration +make generate-config-dev +make validate-config + +# Test deployment +make kustomize-dry-run-dev +``` + +### Step 3: Deploy + +```bash +# Deploy using new system +make deploy-with-env-dev + +# Verify deployment +make status-with-env-dev +``` + +## External Secret Management + +For production environments, integrate with external secret management: + +### HashiCorp Vault Integration + +```bash +# .env.prod +VAULT_ADDR=https://vault.yourdomain.com +VAULT_ROLE=rxminder-prod +VAULT_SECRET_PATH=secret/rxminder/prod + +# Use Vault Agent or CSI driver to inject secrets +``` + +### Kubernetes External Secrets + +```yaml +# external-secret.yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: rxminder-secrets +spec: + secretStoreRef: + name: vault-backend + kind: SecretStore + target: + name: couchdb-secret + data: + - secretKey: password + remoteRef: + key: rxminder/prod + property: couchdb_password +``` + +### AWS Secrets Manager + +```bash +# .env.prod +AWS_REGION=us-west-2 +AWS_SECRET_NAME=rxminder/prod/couchdb + +# Use AWS Load Balancer Controller or external-secrets-operator +``` + +## Summary + +The environment variable system provides: + +- **Flexibility**: Configure deployments without changing code +- **Security**: Keep sensitive data out of version control +- **Consistency**: Standardized configuration across environments +- **Maintainability**: Clear separation of configuration and code + +Choose the approach that best fits your workflow: + +- **Static generation** for stable, version-controlled configurations +- **Dynamic injection** for flexible, runtime configurations +- **Hybrid approach** for the best of both worlds + +For more information, see: + +- [Kustomize README](../k8s-kustomize/README.md) +- [Migration Guide](../KUSTOMIZE_MIGRATION.md) +- [Deployment Scripts](../scripts/) diff --git a/k8s-kustomize/README.md b/k8s-kustomize/README.md new file mode 100644 index 0000000..2f0ac71 --- /dev/null +++ b/k8s-kustomize/README.md @@ -0,0 +1,350 @@ +# Kustomize Deployment Configuration + +This directory contains the Kustomize configuration for deploying the rxminder (Medication Reminder) application to Kubernetes. Kustomize provides a template-free way to customize application configuration that simplifies the use of off-the-shelf applications. + +## Directory Structure + +``` +k8s-kustomize/ +├── base/ # Base configuration (shared resources) +│ ├── kustomization.yaml # Base kustomization file +│ ├── config.env # Environment variables for ConfigMap +│ ├── registry-config.json # Docker registry configuration +│ ├── frontend-deployment.yaml # Frontend deployment +│ ├── frontend-service.yaml # Frontend service +│ ├── couchdb-statefulset.yaml # CouchDB database +│ ├── couchdb-service.yaml # CouchDB service +│ ├── couchdb-pvc.yaml # CouchDB persistent volume +│ ├── configmap.yaml # Application configuration +│ ├── ingress.yaml # Ingress configuration +│ ├── network-policy.yaml # Network policies +│ ├── hpa.yaml # Horizontal Pod Autoscaler +│ └── db-seed-job.yaml # Database seeding job +├── overlays/ # Environment-specific overrides +│ ├── dev/ # Development environment +│ │ └── kustomization.yaml # Development customizations +│ └── prod/ # Production environment +│ ├── kustomization.yaml # Production customizations +│ ├── frontend-resources.yaml # Production frontend resources +│ ├── couchdb-resources.yaml # Production database resources +│ └── ingress-prod.yaml # Production ingress config +└── README.md # This file +``` + +## Quick Start + +### Prerequisites + +1. **kubectl** installed and configured +2. **Kubernetes cluster** access +3. **Docker images** built and pushed to registry + +### Deploy to Development + +```bash +# Using Makefile (recommended) +make deploy-dev + +# Or directly with kubectl +kubectl apply -k k8s-kustomize/overlays/dev +``` + +### Deploy to Production + +```bash +# Using Makefile (recommended) +make deploy-prod + +# Or directly with kubectl +kubectl apply -k k8s-kustomize/overlays/prod +``` + +## Environment Configurations + +### Development Environment + +- **Namespace**: `rxminder-dev` +- **Replicas**: 1 frontend pod +- **Resources**: Minimal (16Mi-32Mi memory) +- **Image Tag**: `dev` +- **Domain**: `rxminder-dev.local` +- **Storage**: 1Gi +- **Security**: Relaxed for development + +### Production Environment + +- **Namespace**: `rxminder-prod` +- **Replicas**: 3 frontend pods (high availability) +- **Resources**: Production-grade (256Mi-512Mi memory) +- **Image Tag**: `v1.0.0` (semantic versioning) +- **Domain**: `rxminder.yourdomain.com` +- **Storage**: 10Gi SSD +- **Security**: Hardened with security contexts, network policies +- **TLS**: Enabled with cert-manager +- **Monitoring**: Enabled + +## Makefile Commands + +### Deployment Commands + +```bash +# Development +make deploy-dev # Deploy to development +make undeploy-dev # Remove development deployment +make quick-deploy-dev # Build and deploy to development +make status-dev # Show development status + +# Production +make deploy-prod # Deploy to production +make undeploy-prod # Remove production deployment +make quick-deploy-prod # Build and deploy to production +make status-prod # Show production status +``` + +### Validation Commands + +```bash +make validate-kustomize # Validate all configurations +make kustomize-validate-dev # Validate development config +make kustomize-validate-prod # Validate production config +``` + +### Debugging Commands + +```bash +make kustomize-dry-run-dev # Dry run development deployment +make kustomize-dry-run-prod # Dry run production deployment +make kustomize-diff-dev # Show differences for development +make kustomize-diff-prod # Show differences for production +make kustomize-build-dev # Build development manifests +make kustomize-build-prod # Build production manifests +``` + +## Configuration Management + +### ConfigMaps + +Configuration is managed through: + +1. **Base config.env**: Common environment variables +2. **Overlay literals**: Environment-specific overrides +3. **ConfigMap generation**: Automatic from environment files + +### Secrets + +Secrets are managed through: + +1. **Development**: Simple literals in kustomization.yaml +2. **Production**: External secret management (recommended) + - Kubernetes External Secrets Operator + - HashiCorp Vault + - AWS Secrets Manager + - Azure Key Vault + +### Images + +Image management uses Kustomize's image transformer: + +```yaml +images: + - name: gitea-http.taildb3494.ts.net/will/rxminder + newTag: v1.0.0 # Override in overlays +``` + +## Customization + +### Adding a New Environment + +1. Create new directory: `overlays/staging/` +2. Create `kustomization.yaml` with base reference +3. Add environment-specific patches +4. Update Makefile with new targets + +Example: + +```yaml +# overlays/staging/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +namespace: rxminder-staging + +commonLabels: + environment: staging + +images: + - name: gitea-http.taildb3494.ts.net/will/rxminder + newTag: staging +``` + +### Resource Patches + +Use strategic merge patches for complex modifications: + +```yaml +# Example: frontend-patch.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rxminder-frontend +spec: + template: + spec: + containers: + - name: frontend + resources: + requests: + memory: '512Mi' + cpu: '200m' +``` + +### JSON Patches + +Use JSON patches for precise modifications: + +```yaml +patches: + - target: + kind: Deployment + name: rxminder-frontend + patch: |- + - op: replace + path: /spec/replicas + value: 5 +``` + +## Security Considerations + +### Production Security Features + +1. **Security Contexts**: Non-root containers, read-only filesystems +2. **Network Policies**: Restricted pod-to-pod communication +3. **Resource Limits**: Prevent resource exhaustion +4. **Image Security**: Signed images, vulnerability scanning +5. **Secret Management**: External secret stores +6. **TLS Encryption**: HTTPS with cert-manager +7. **RBAC**: Role-based access control + +### Development Security + +- Relaxed for development efficiency +- Still follows basic security practices +- Isolated in separate namespace + +## Monitoring and Observability + +### Production Monitoring + +- **Health Checks**: Liveness and readiness probes +- **Metrics**: Resource usage monitoring +- **Logging**: Structured logging with appropriate levels +- **Alerting**: Production-grade alert rules + +### Development Monitoring + +- **Debug Logging**: Verbose logging for troubleshooting +- **Resource Monitoring**: Basic resource tracking +- **Health Checks**: Relaxed timing for development + +## Troubleshooting + +### Common Issues + +1. **Image Pull Errors** + + ```bash + # Check image exists and credentials are correct + kubectl describe pod -n + ``` + +2. **ConfigMap Issues** + + ```bash + # Check generated ConfigMap + kubectl get configmap rxminder-config -n -o yaml + ``` + +3. **Service Discovery** + + ```bash + # Test service connectivity + kubectl exec -it -n -- curl rxminder-couchdb-service:5984 + ``` + +4. **Resource Constraints** + + ```bash + # Check resource usage + kubectl top pods -n + kubectl describe node + ``` + +### Debug Commands + +```bash +# View all resources +kubectl get all -n rxminder-dev -l app=rxminder + +# Check events +kubectl get events -n rxminder-dev --sort-by='.lastTimestamp' + +# View logs +kubectl logs deployment/rxminder-frontend -n rxminder-dev -f + +# Describe problematic resources +kubectl describe deployment rxminder-frontend -n rxminder-dev +``` + +## Migration from Legacy Deployment + +### Migration Steps + +1. **Test Kustomize**: Deploy to development first +2. **Validate Configuration**: Compare with existing deployment +3. **Update CI/CD**: Switch to Kustomize commands +4. **Production Migration**: Schedule maintenance window +5. **Cleanup**: Remove old template files after validation + +### Rollback Plan + +Legacy deployment scripts are still available: + +```bash +make k8s-deploy # Legacy deployment +make k8s-undeploy # Legacy undeploy +``` + +## Benefits of Kustomize + +1. **Template-free**: No complex templating logic +2. **Composable**: Layer configurations naturally +3. **Validation**: Built-in YAML validation +4. **GitOps Ready**: Works with ArgoCD, Flux +5. **Standard**: Kubernetes-native solution +6. **Maintainable**: Clear separation of concerns + +## Best Practices + +1. **Base Configuration**: Keep base as generic as possible +2. **Environment Isolation**: Use separate namespaces +3. **Secret Management**: Use external secret stores in production +4. **Image Tags**: Use specific tags, avoid `latest` in production +5. **Resource Limits**: Always set resource requests and limits +6. **Health Checks**: Configure appropriate probes +7. **Documentation**: Keep this README updated + +## Support + +For issues or questions: + +1. Check troubleshooting section above +2. Review Kubernetes events and logs +3. Validate Kustomize configuration +4. Consult team documentation + +--- + +**Note**: This Kustomize configuration replaces the previous shell script-based deployment. The old scripts are still available for backward compatibility but Kustomize is the recommended approach going forward. diff --git a/k8s-kustomize/base/config.env b/k8s-kustomize/base/config.env new file mode 100644 index 0000000..aaffe51 --- /dev/null +++ b/k8s-kustomize/base/config.env @@ -0,0 +1,60 @@ +# Base configuration for rxminder application +# Generated automatically from environment variables +# Generated on: Sun Sep 7 08:46:59 PM PDT 2025 +# Environment: dev + +# Application Environment +NODE_ENV=production +LOG_LEVEL=info + +# API Configuration +REACT_APP_API_URL=http://rxminder-couchdb-service:5984 + +# Feature Flags +ENABLE_MONITORING=false +DEBUG=false + +# Cache Configuration +CACHE_TTL=1800 + +# Database Configuration +DB_HOST=rxminder-couchdb-service +DB_PORT=5984 +COUCHDB_DATABASE_NAME=meds_app + +# Security Configuration +ENABLE_CORS=true +CORS_ORIGIN=* + +# Performance Configuration +REQUEST_TIMEOUT=30000 +MAX_CONNECTIONS=100 + +# Logging Configuration +LOG_FORMAT=json +LOG_TIMESTAMP=true + +# Health Check Configuration +HEALTH_CHECK_INTERVAL=30 +READINESS_CHECK_TIMEOUT=5 + +# Application Metadata +APP_NAME=rxminder +APP_VERSION=1.0.0 + +# Container Registry +REGISTRY_URL=gitea-http.taildb3494.ts.net +IMAGE_REPOSITORY=will/rxminder + +# Ingress Configuration +INGRESS_CLASS=nginx +CERT_MANAGER_ISSUER=letsencrypt-prod + +# Monitoring and Observability +ENABLE_METRICS=false +METRICS_PORT=9090 +ENABLE_TRACING=false + +# Development specific (will be overridden in overlays) +DEV_MODE=false +HOT_RELOAD=false diff --git a/k8s-kustomize/base/couchdb-pvc.yaml b/k8s-kustomize/base/couchdb-pvc.yaml new file mode 100644 index 0000000..3506405 --- /dev/null +++ b/k8s-kustomize/base/couchdb-pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: rxminder-couchdb-pvc + labels: + app: rxminder + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: standard + resources: + requests: + storage: 1Gi diff --git a/k8s-kustomize/base/couchdb-service.yaml b/k8s-kustomize/base/couchdb-service.yaml new file mode 100644 index 0000000..56dbdb6 --- /dev/null +++ b/k8s-kustomize/base/couchdb-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: rxminder-couchdb-service + labels: + app: rxminder + component: database +spec: + selector: + app: rxminder + component: database + ports: + - name: couchdb + port: 5984 + targetPort: 5984 + protocol: TCP + type: ClusterIP diff --git a/k8s-kustomize/base/couchdb-statefulset.yaml b/k8s-kustomize/base/couchdb-statefulset.yaml new file mode 100644 index 0000000..1321a2f --- /dev/null +++ b/k8s-kustomize/base/couchdb-statefulset.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rxminder-couchdb + labels: + app: rxminder + component: database +spec: + serviceName: rxminder-couchdb-service + replicas: 1 + selector: + matchLabels: + app: rxminder + component: database + template: + metadata: + labels: + app: rxminder + component: database + spec: + containers: + - name: couchdb + image: couchdb:3.3.2 + ports: + - containerPort: 5984 + env: + - name: COUCHDB_USER + valueFrom: + secretKeyRef: + name: couchdb-secret + key: username + - name: COUCHDB_PASSWORD + valueFrom: + secretKeyRef: + name: couchdb-secret + key: password + resources: + requests: + memory: '64Mi' + cpu: '30m' + limits: + memory: '128Mi' + cpu: '60m' + volumeMounts: + - name: couchdb-data + mountPath: /opt/couchdb/data + livenessProbe: + httpGet: + path: /_up + port: 5984 + initialDelaySeconds: 60 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /_up + port: 5984 + initialDelaySeconds: 10 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: couchdb-data + labels: + app: rxminder + component: database + spec: + accessModes: ['ReadWriteOnce'] + storageClassName: standard + resources: + requests: + storage: 1Gi diff --git a/k8s-kustomize/base/db-seed-job.yaml b/k8s-kustomize/base/db-seed-job.yaml new file mode 100644 index 0000000..f5f4085 --- /dev/null +++ b/k8s-kustomize/base/db-seed-job.yaml @@ -0,0 +1,107 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: rxminder-db-seed + labels: + app: rxminder + component: database +spec: + template: + metadata: + labels: + app: rxminder + component: database + spec: + containers: + - name: db-seeder + image: couchdb:3.3.2 + env: + - name: COUCHDB_USER + valueFrom: + secretKeyRef: + name: couchdb-secret + key: username + - name: COUCHDB_PASSWORD + valueFrom: + secretKeyRef: + name: couchdb-secret + key: password + command: ['/bin/sh', '-c'] + args: + - | + # Wait for CouchDB to be ready + echo "Waiting for CouchDB to be ready..." + until curl -f http://couchdb-service:5984/_up 2>/dev/null; do + sleep 2 + done + + # Create databases + echo "Creating databases..." + curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app + + # Create default admin user + echo "Creating default admin user..." + curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/_users/org.couchdb.user:$COUCHDB_USER \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"$COUCHDB_USER\", + \"password\": \"$COUCHDB_PASSWORD\", + \"roles\": [\"admin\"], + \"type\": \"user\" + }" + + # Create design documents for views + echo "Creating design documents..." + curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/medications \ + -H "Content-Type: application/json" \ + -d '{ + "views": { + "by_name": { + "map": "function(doc) { if (doc.type === \"medication\") emit(doc.name, doc); }" + }, + "by_user": { + "map": "function(doc) { if (doc.type === \"medication\") emit(doc.userId, doc); }" + } + } + }' + + curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/reminders \ + -H "Content-Type: application/json" \ + -d '{ + "views": { + "by_medication": { + "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.medicationId, doc); }" + }, + "by_user": { + "map": "function(doc) { if (doc.type === \"reminder\") emit(doc.userId, doc); }" + } + } + }' + + # Create a sample user document for reference + # Create design document for authentication users + curl -X PUT http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app/_design/auth \ + -H "Content-Type: application/json" \ + -d '{ + "views": { + "by_username": { + "map": "function(doc) { if (doc.type === \"user\" && doc.username) emit(doc.username, doc); }" + }, + "by_email": { + "map": "function(doc) { if (doc.type === \"user\" && doc.email) emit(doc.email, doc); }" + } + } + }' + echo "Creating sample user document..." + curl -X POST http://$COUCHDB_USER:$COUCHDB_PASSWORD@couchdb-service:5984/meds_app \ + -H "Content-Type: application/json" \ + -d '{ + "type": "user", + "name": "sample_user", + "email": "user@example.com", + "createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' + + echo "Database seeding completed with default admin user" + restartPolicy: Never + backoffLimit: 4 diff --git a/k8s-kustomize/base/frontend-deployment.yaml b/k8s-kustomize/base/frontend-deployment.yaml index 27625cd..9de901e 100644 --- a/k8s-kustomize/base/frontend-deployment.yaml +++ b/k8s-kustomize/base/frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: - name: rxminder-registry-secret containers: - name: frontend - image: frontend-image + image: gitea-http.taildb3494.ts.net/will/rxminder:latest ports: - containerPort: 80 envFrom: diff --git a/k8s-kustomize/base/frontend-service.yaml b/k8s-kustomize/base/frontend-service.yaml new file mode 100644 index 0000000..a0545dd --- /dev/null +++ b/k8s-kustomize/base/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: rxminder-frontend-service + labels: + app: rxminder + component: frontend +spec: + selector: + app: rxminder + component: frontend + ports: + - name: http + port: 80 + targetPort: 80 + protocol: TCP + type: ClusterIP diff --git a/k8s-kustomize/base/hpa.yaml b/k8s-kustomize/base/hpa.yaml new file mode 100644 index 0000000..22cc7be --- /dev/null +++ b/k8s-kustomize/base/hpa.yaml @@ -0,0 +1,21 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: rxminder-frontend-hpa + labels: + app: rxminder + component: frontend +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: rxminder-frontend + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 diff --git a/k8s-kustomize/base/ingress.yaml b/k8s-kustomize/base/ingress.yaml new file mode 100644 index 0000000..268990d --- /dev/null +++ b/k8s-kustomize/base/ingress.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rxminder-ingress + labels: + app: rxminder + component: frontend + annotations: + # Add SSL redirect if using HTTPS + # nginx.ingress.kubernetes.io/ssl-redirect: "true" + # cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: + ingressClassName: nginx + # Uncomment for HTTPS with cert-manager + # tls: + # - hosts: + # - rxminder.example.com + # secretName: frontend-tls + rules: + - host: rxminder.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rxminder-frontend-service + port: + number: 80 diff --git a/k8s-kustomize/base/kustomization.yaml b/k8s-kustomize/base/kustomization.yaml index 5c22108..1c34b46 100644 --- a/k8s-kustomize/base/kustomization.yaml +++ b/k8s-kustomize/base/kustomization.yaml @@ -13,21 +13,22 @@ resources: - couchdb-service.yaml - couchdb-pvc.yaml - ingress.yaml - - configmap.yaml - network-policy.yaml - hpa.yaml - db-seed-job.yaml # Common labels applied to all resources -commonLabels: - app: rxminder - version: v1.0.0 +labels: + - pairs: + app: rxminder + version: v1.0.0 # Generate ConfigMap from environment files configMapGenerator: - name: rxminder-config envs: - config.env + behavior: create # Generate Secret for CouchDB secretGenerator: @@ -37,18 +38,19 @@ secretGenerator: - password=changeme type: Opaque - # Generate registry secret from credentials - - name: rxminder-registry-secret - files: - - .dockerconfigjson=registry-config.json - type: kubernetes.io/dockerconfigjson + # Note: Registry secret should be created manually or via external secret management + # For now, this is commented out to avoid issues with missing files + # - name: rxminder-registry-secret + # files: + # - .dockerconfigjson=registry-config.json + # type: kubernetes.io/dockerconfigjson # Images to be used (can be overridden in overlays) images: - - name: frontend-image + - name: gitea-http.taildb3494.ts.net/will/rxminder newName: gitea-http.taildb3494.ts.net/will/rxminder newTag: latest - - name: couchdb-image + - name: couchdb newName: couchdb newTag: 3.3.2 diff --git a/k8s-kustomize/base/network-policy.yaml b/k8s-kustomize/base/network-policy.yaml new file mode 100644 index 0000000..e2ce07a --- /dev/null +++ b/k8s-kustomize/base/network-policy.yaml @@ -0,0 +1,68 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: rxminder-frontend-policy + labels: + app: rxminder + component: frontend +spec: + podSelector: + matchLabels: + component: frontend + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + component: frontend + ports: + - protocol: TCP + port: 80 + egress: + - to: + - podSelector: + matchLabels: + component: database + ports: + - protocol: TCP + port: 5984 + - to: + - podSelector: + matchLabels: + component: frontend + ports: + - protocol: TCP + port: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: rxminder-database-policy + labels: + app: rxminder + component: database +spec: + podSelector: + matchLabels: + component: database + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + component: frontend + ports: + - protocol: TCP + port: 5984 + egress: + - to: + - podSelector: + matchLabels: + component: database + ports: + - protocol: TCP + port: 5984 diff --git a/k8s-kustomize/base/registry-config.json b/k8s-kustomize/base/registry-config.json new file mode 100644 index 0000000..8f2e996 --- /dev/null +++ b/k8s-kustomize/base/registry-config.json @@ -0,0 +1,21 @@ +{ + "auths": { + "gitea-http.taildb3494.ts.net": { + "username": "REGISTRY_USERNAME", + "password": "REGISTRY_PASSWORD", + "email": "REGISTRY_EMAIL", + "auth": "REGISTRY_AUTH_TOKEN" + }, + "docker.io": { + "username": "DOCKER_USERNAME", + "password": "DOCKER_PASSWORD", + "email": "DOCKER_EMAIL", + "auth": "DOCKER_AUTH_TOKEN" + } + }, + "HttpHeaders": { + "User-Agent": "Docker-Client/20.10.0 (linux)" + }, + "credsStore": "secretservice", + "experimental": "disabled" +} diff --git a/k8s-kustomize/overlays/dev/config.env b/k8s-kustomize/overlays/dev/config.env new file mode 100644 index 0000000..40251a8 --- /dev/null +++ b/k8s-kustomize/overlays/dev/config.env @@ -0,0 +1,23 @@ +# Development environment configuration +# Generated on: Sun Sep 7 08:46:59 PM PDT 2025 + +NODE_ENV=development +LOG_LEVEL=debug +DEBUG=true +ENABLE_MONITORING=false +DEV_MODE=true +HOT_RELOAD=true + +# Development URLs (override if needed) +REACT_APP_API_URL=http://rxminder-couchdb-service:5984 +CORS_ORIGIN=* + +# Development domain +INGRESS_HOST=rxminder-dev.local + +# Relaxed timeouts for debugging +REQUEST_TIMEOUT=60000 +HEALTH_CHECK_INTERVAL=60 + +# Development image tag +IMAGE_TAG=dev diff --git a/k8s-kustomize/overlays/dev/kustomization.yaml b/k8s-kustomize/overlays/dev/kustomization.yaml index 73b72b4..a598920 100644 --- a/k8s-kustomize/overlays/dev/kustomization.yaml +++ b/k8s-kustomize/overlays/dev/kustomization.yaml @@ -10,15 +10,16 @@ resources: - ../../base # Development-specific labels -commonLabels: - environment: dev +labels: + - pairs: + environment: dev # Override images for development images: - - name: frontend-image + - name: gitea-http.taildb3494.ts.net/will/rxminder newName: gitea-http.taildb3494.ts.net/will/rxminder newTag: dev - - name: couchdb-image + - name: couchdb newName: couchdb newTag: 3.3.2 @@ -29,6 +30,22 @@ replicas: # Development-specific patches patches: + # Development environment variables via ConfigMap + - target: + kind: ConfigMap + name: rxminder-config + patch: |- + - op: replace + path: /data/NODE_ENV + value: "development" + - op: replace + path: /data/LOG_LEVEL + value: "debug" + - op: add + path: /data/DEBUG + value: "true" + + # Development resource limits - target: kind: Deployment name: rxminder-frontend @@ -47,6 +64,7 @@ patches: - name: LOG_LEVEL value: "debug" + # Development ingress host - target: kind: Ingress name: rxminder-ingress @@ -55,6 +73,7 @@ patches: path: /spec/rules/0/host value: "rxminder-dev.local" + # Development storage size - target: kind: PersistentVolumeClaim name: rxminder-couchdb-pvc @@ -63,16 +82,6 @@ patches: path: /spec/resources/requests/storage value: "1Gi" -# Development-specific ConfigMap -configMapGenerator: - - name: rxminder-config - literals: - - NODE_ENV=development - - API_URL=http://rxminder-couchdb-service:5984 - - LOG_LEVEL=debug - - DEBUG=true - behavior: replace - # Development secrets (use weak passwords for dev) secretGenerator: - name: couchdb-secret diff --git a/k8s-kustomize/overlays/prod/couchdb-resources.yaml b/k8s-kustomize/overlays/prod/couchdb-resources.yaml new file mode 100644 index 0000000..71dd5bd --- /dev/null +++ b/k8s-kustomize/overlays/prod/couchdb-resources.yaml @@ -0,0 +1,126 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rxminder-couchdb +spec: + template: + spec: + containers: + - name: couchdb + resources: + requests: + memory: '512Mi' + cpu: '200m' + limits: + memory: '1Gi' + cpu: '1000m' + # Production environment variables + env: + - name: COUCHDB_USER + valueFrom: + secretKeyRef: + name: couchdb-secret + key: username + - name: COUCHDB_PASSWORD + valueFrom: + secretKeyRef: + name: couchdb-secret + key: password + # Production CouchDB configuration + - name: ERL_FLAGS + value: '-setcookie monster' + - name: COUCHDB_SECRET + value: 'changeme_in_production' + # Production health checks with tighter timings + livenessProbe: + httpGet: + path: /_up + port: 5984 + initialDelaySeconds: 60 + periodSeconds: 20 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /_up + port: 5984 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + # Security context for production + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 5984 + runAsGroup: 5984 + capabilities: + drop: + - ALL + # Volume mounts with proper permissions + volumeMounts: + - name: couchdb-data + mountPath: /opt/couchdb/data + - name: couchdb-config + mountPath: /opt/couchdb/etc/local.d + readOnly: true + # Pod-level security and scheduling for production + securityContext: + fsGroup: 5984 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Production scheduling preferences for database + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rxminder + - key: component + operator: In + values: + - database + topologyKey: kubernetes.io/hostname + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-type + operator: In + values: + - database + - storage + # Toleration for production node taints + tolerations: + - key: 'node-role.kubernetes.io/production' + operator: 'Equal' + value: 'true' + effect: 'NoSchedule' + - key: 'node-role.kubernetes.io/database' + operator: 'Equal' + value: 'true' + effect: 'NoSchedule' + # Additional volumes for production configuration + volumes: + - name: couchdb-config + configMap: + name: couchdb-production-config + # Production volume claim template with SSD storage + volumeClaimTemplates: + - metadata: + name: couchdb-data + labels: + app: rxminder + component: database + environment: production + spec: + accessModes: ['ReadWriteOnce'] + storageClassName: ssd + resources: + requests: + storage: 10Gi diff --git a/k8s-kustomize/overlays/prod/frontend-resources.yaml b/k8s-kustomize/overlays/prod/frontend-resources.yaml new file mode 100644 index 0000000..2768e4a --- /dev/null +++ b/k8s-kustomize/overlays/prod/frontend-resources.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rxminder-frontend +spec: + template: + spec: + containers: + - name: frontend + resources: + requests: + memory: '256Mi' + cpu: '100m' + limits: + memory: '512Mi' + cpu: '500m' + # Production environment variables + env: + - name: NODE_ENV + value: 'production' + - name: LOG_LEVEL + value: 'warn' + - name: ENABLE_MONITORING + value: 'true' + # Production readiness and liveness probes with tighter timings + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ready + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + # Security context for production + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + # Pod-level security and scheduling for production + securityContext: + fsGroup: 1000 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Production scheduling preferences + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rxminder + - key: component + operator: In + values: + - frontend + topologyKey: kubernetes.io/hostname + # Toleration for production node taints + tolerations: + - key: 'node-role.kubernetes.io/production' + operator: 'Equal' + value: 'true' + effect: 'NoSchedule' diff --git a/k8s-kustomize/overlays/prod/ingress-prod.yaml b/k8s-kustomize/overlays/prod/ingress-prod.yaml new file mode 100644 index 0000000..5efa8a1 --- /dev/null +++ b/k8s-kustomize/overlays/prod/ingress-prod.yaml @@ -0,0 +1,65 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rxminder-ingress + annotations: + # Enable SSL redirect for production + nginx.ingress.kubernetes.io/ssl-redirect: 'true' + # Use production certificate issuer + cert-manager.io/cluster-issuer: 'letsencrypt-prod' + # Security headers for production + nginx.ingress.kubernetes.io/configuration-snippet: | + more_set_headers "X-Frame-Options: DENY"; + more_set_headers "X-Content-Type-Options: nosniff"; + more_set_headers "X-XSS-Protection: 1; mode=block"; + more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"; + more_set_headers "Referrer-Policy: strict-origin-when-cross-origin"; + # Rate limiting for production + nginx.ingress.kubernetes.io/rate-limit: '100' + nginx.ingress.kubernetes.io/rate-limit-window: '1m' + # Enable CORS for production API access + nginx.ingress.kubernetes.io/enable-cors: 'true' + nginx.ingress.kubernetes.io/cors-allow-origin: 'https://rxminder.yourdomain.com' + # Compression for better performance + nginx.ingress.kubernetes.io/compression: 'gzip' + # Client body size limit + nginx.ingress.kubernetes.io/proxy-body-size: '10m' + # Connection and read timeouts + nginx.ingress.kubernetes.io/proxy-connect-timeout: '60' + nginx.ingress.kubernetes.io/proxy-read-timeout: '60' + # Enable modsecurity WAF for production + nginx.ingress.kubernetes.io/enable-modsecurity: 'true' + nginx.ingress.kubernetes.io/modsecurity-snippet: | + SecRuleEngine On + SecAuditEngine RelevantOnly +spec: + ingressClassName: nginx + # Production TLS configuration + tls: + - hosts: + - rxminder.yourdomain.com + - api.rxminder.yourdomain.com + secretName: rxminder-tls-prod + rules: + # Main application domain + - host: rxminder.yourdomain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rxminder-frontend-service + port: + number: 80 + # API subdomain for direct database access (if needed) + - host: api.rxminder.yourdomain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: rxminder-couchdb-service + port: + number: 5984 diff --git a/k8s-kustomize/overlays/prod/kustomization.yaml b/k8s-kustomize/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..6fe927f --- /dev/null +++ b/k8s-kustomize/overlays/prod/kustomization.yaml @@ -0,0 +1,127 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + name: rxminder-production + +# Reference the base configuration +resources: + - ../../base + +# Override namespace for production +namespace: rxminder-prod + +# Production-specific labels +labels: + - pairs: + environment: production + tier: prod + +# Production image tags and configurations +images: + - name: frontend-image + newName: gitea-http.taildb3494.ts.net/will/rxminder + newTag: v1.0.0 + - name: couchdb-image + newName: couchdb + newTag: 3.3.2 + +# Production replicas - higher for availability +replicas: + - name: rxminder-frontend + count: 3 + - name: rxminder-couchdb + count: 1 + +# Production resource patches +patchesStrategicMerge: + - frontend-resources.yaml + - couchdb-resources.yaml + - ingress-prod.yaml + +# Production secrets (to be created manually or via external secret management) +secretGenerator: + - name: couchdb-secret + behavior: replace + literals: + - username=admin + # Note: In production, use external secret management like: + # - Kubernetes External Secrets Operator + # - HashiCorp Vault + # - AWS Secrets Manager + # This is just a placeholder + - password=CHANGE_IN_PRODUCTION + +# Production-specific patches for security and performance +patches: + # Production-specific ConfigMap patches + - target: + kind: ConfigMap + name: rxminder-config + patch: |- + - op: replace + path: /data/NODE_ENV + value: "production" + - op: replace + path: /data/LOG_LEVEL + value: "warn" + - op: add + path: /data/ENABLE_MONITORING + value: "true" + - op: add + path: /data/CACHE_TTL + value: "3600" + + # Enable HTTPS redirect on ingress + - target: + kind: Ingress + name: rxminder-ingress + patch: |- + - op: add + path: /metadata/annotations/nginx.ingress.kubernetes.io~1ssl-redirect + value: "true" + - op: add + path: /metadata/annotations/cert-manager.io~1cluster-issuer + value: "letsencrypt-prod" + + # Add resource limits for production workloads + - target: + kind: Deployment + name: rxminder-frontend + patch: |- + - op: replace + path: /spec/template/spec/containers/0/resources + value: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + + # Production storage class and size + - target: + kind: StatefulSet + name: rxminder-couchdb + patch: |- + - op: replace + path: /spec/volumeClaimTemplates/0/spec/storageClassName + value: "ssd" + - op: replace + path: /spec/volumeClaimTemplates/0/spec/resources/requests/storage + value: "10Gi" + + # Production CouchDB resources + - target: + kind: StatefulSet + name: rxminder-couchdb + patch: |- + - op: replace + path: /spec/template/spec/containers/0/resources + value: + requests: + memory: "512Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1000m" diff --git a/scripts/deploy-with-env.sh b/scripts/deploy-with-env.sh new file mode 100755 index 0000000..c486a62 --- /dev/null +++ b/scripts/deploy-with-env.sh @@ -0,0 +1,426 @@ +#!/bin/bash + +# deploy-with-env.sh +# Deploy Kustomize configurations with environment variable substitution +# Usage: ./scripts/deploy-with-env.sh [environment] [action] +# Example: ./scripts/deploy-with-env.sh dev apply +# Example: ./scripts/deploy-with-env.sh prod diff + +set -euo pipefail + +# Default values +ENVIRONMENT=${1:-dev} +ACTION=${2:-apply} +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Load environment variables from multiple sources +load_environment() { + local env_files=( + "$HOME/.env" + "$PROJECT_ROOT/.env" + "$PROJECT_ROOT/.env.$ENVIRONMENT" + "$PROJECT_ROOT/.env.local" + ) + + print_status "Loading environment variables for: $ENVIRONMENT" + + for env_file in "${env_files[@]}"; do + if [[ -f "$env_file" ]]; then + print_status "Loading: $env_file" + set -a + source "$env_file" + set +a + fi + done + + # Set defaults if not provided + export APP_NAME="${APP_NAME:-rxminder}" + export NODE_ENV="${NODE_ENV:-$ENVIRONMENT}" + export IMAGE_TAG="${IMAGE_TAG:-latest}" + export NAMESPACE="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}" + + # Environment-specific defaults + case "$ENVIRONMENT" in + "dev"|"development") + export NODE_ENV="development" + export LOG_LEVEL="${LOG_LEVEL:-debug}" + export DEBUG="${DEBUG:-true}" + export IMAGE_TAG="${IMAGE_TAG:-dev}" + export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}-dev.local}" + ;; + "prod"|"production") + export NODE_ENV="production" + export LOG_LEVEL="${LOG_LEVEL:-warn}" + export DEBUG="${DEBUG:-false}" + export IMAGE_TAG="${IMAGE_TAG:-v1.0.0}" + export INGRESS_HOST="${INGRESS_HOST:-${APP_NAME}.yourdomain.com}" + ;; + "staging") + export NODE_ENV="staging" + export LOG_LEVEL="${LOG_LEVEL:-info}" + export DEBUG="${DEBUG:-false}" + export IMAGE_TAG="${IMAGE_TAG:-staging}" + export INGRESS_HOST="${INGRESS_HOST:-staging.${APP_NAME}.yourdomain.com}" + ;; + esac + + print_success "Environment loaded: $ENVIRONMENT" + print_status "Key variables:" + echo " APP_NAME: $APP_NAME" + echo " NODE_ENV: $NODE_ENV" + echo " IMAGE_TAG: $IMAGE_TAG" + echo " NAMESPACE: $NAMESPACE" + echo " INGRESS_HOST: $INGRESS_HOST" +} + +# Substitute environment variables in kustomization files +substitute_variables() { + local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT" + local temp_dir=$(mktemp -d) + local kustomization_file="$overlay_dir/kustomization.yaml" + + print_status "Creating temporary overlay with variable substitution..." + + # Copy overlay directory to temp location + cp -r "$overlay_dir" "$temp_dir/" + local temp_overlay="$temp_dir/$(basename "$overlay_dir")" + + # Substitute variables in kustomization.yaml + if [[ -f "$temp_overlay/kustomization.yaml" ]]; then + envsubst < "$kustomization_file" > "$temp_overlay/kustomization.yaml.tmp" + mv "$temp_overlay/kustomization.yaml.tmp" "$temp_overlay/kustomization.yaml" + fi + + # Substitute variables in any other YAML files in the overlay + find "$temp_overlay" -name "*.yaml" -not -name "kustomization.yaml" | while read -r file; do + envsubst < "$file" > "$file.tmp" + mv "$file.tmp" "$file" + done + + echo "$temp_overlay" +} + +# Generate dynamic ConfigMap with current environment variables +generate_dynamic_config() { + local temp_dir="$1" + local config_file="$temp_dir/dynamic-config.env" + + print_status "Generating dynamic configuration..." + + cat > "$config_file" << EOF +# Dynamic configuration generated at deploy time +# Generated on: $(date) +# Environment: $ENVIRONMENT + +# Application Configuration +APP_NAME=$APP_NAME +NODE_ENV=$NODE_ENV +LOG_LEVEL=$LOG_LEVEL +DEBUG=$DEBUG + +# Image Configuration +IMAGE_TAG=$IMAGE_TAG +REGISTRY_URL=${REGISTRY_URL:-gitea-http.taildb3494.ts.net} +IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-will/rxminder} + +# Network Configuration +INGRESS_HOST=$INGRESS_HOST +NAMESPACE=$NAMESPACE + +# API Configuration +REACT_APP_API_URL=${REACT_APP_API_URL:-http://${APP_NAME}-couchdb-service:5984} + +# Database Configuration +DB_HOST=${DB_HOST:-${APP_NAME}-couchdb-service} +DB_PORT=${DB_PORT:-5984} +COUCHDB_DATABASE_NAME=${COUCHDB_DATABASE_NAME:-meds_app} + +# Feature Flags +ENABLE_MONITORING=${ENABLE_MONITORING:-false} +ENABLE_METRICS=${ENABLE_METRICS:-false} +ENABLE_TRACING=${ENABLE_TRACING:-false} + +# Performance Configuration +CACHE_TTL=${CACHE_TTL:-1800} +REQUEST_TIMEOUT=${REQUEST_TIMEOUT:-30000} +MAX_CONNECTIONS=${MAX_CONNECTIONS:-100} + +# Security Configuration +ENABLE_CORS=${ENABLE_CORS:-true} +CORS_ORIGIN=${CORS_ORIGIN:-*} + +# Build Information +BUILD_VERSION=${BUILD_VERSION:-unknown} +BUILD_COMMIT=${BUILD_COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")} +BUILD_DATE=${BUILD_DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} +EOF + + # Update kustomization.yaml to include dynamic config + local kustomization_file="$temp_dir/kustomization.yaml" + + # Add configMapGenerator for dynamic config if not present + if ! grep -q "dynamic-config.env" "$kustomization_file" 2>/dev/null; then + cat >> "$kustomization_file" << EOF + +# Dynamic configuration added at deploy time +configMapGenerator: + - name: ${APP_NAME}-dynamic-config + envs: + - dynamic-config.env + behavior: create +EOF + fi + + print_success "Dynamic configuration generated" +} + +# Validate deployment prerequisites +validate_prerequisites() { + print_status "Validating prerequisites..." + + # Check kubectl + if ! command -v kubectl &> /dev/null; then + print_error "kubectl is not installed or not in PATH" + return 1 + fi + + # Check kubectl connectivity + if ! kubectl cluster-info &> /dev/null; then + print_error "Cannot connect to Kubernetes cluster" + return 1 + fi + + # Check if overlay exists + local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT" + if [[ ! -d "$overlay_dir" ]]; then + print_error "Overlay directory not found: $overlay_dir" + return 1 + fi + + # Check if kustomization.yaml exists + if [[ ! -f "$overlay_dir/kustomization.yaml" ]]; then + print_error "kustomization.yaml not found in: $overlay_dir" + return 1 + fi + + print_success "Prerequisites validated" +} + +# Execute kubectl command with the prepared overlay +execute_kubectl() { + local temp_overlay="$1" + local action="$2" + + print_status "Executing kubectl $action with prepared overlay..." + + case "$action" in + "apply") + kubectl apply -k "$temp_overlay" + ;; + "delete") + kubectl delete -k "$temp_overlay" --ignore-not-found=true + ;; + "diff") + kubectl diff -k "$temp_overlay" || true + ;; + "dry-run") + kubectl apply -k "$temp_overlay" --dry-run=client -o yaml + ;; + "validate") + kubectl kustomize "$temp_overlay" | kubectl apply --dry-run=client --validate=true -f - + ;; + "build") + kubectl kustomize "$temp_overlay" + ;; + *) + print_error "Unknown action: $action" + print_status "Supported actions: apply, delete, diff, dry-run, validate, build" + return 1 + ;; + esac +} + +# Show deployment status +show_status() { + local namespace="${NAMESPACE:-${APP_NAME}-${ENVIRONMENT}}" + + print_status "Deployment status for $ENVIRONMENT environment:" + + echo + echo "Namespace: $namespace" + kubectl get namespace "$namespace" 2>/dev/null || echo "Namespace not found" + + echo + echo "Deployments:" + kubectl get deployments -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No deployments found" + + echo + echo "Services:" + kubectl get services -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No services found" + + echo + echo "Ingress:" + kubectl get ingress -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No ingress found" + + echo + echo "ConfigMaps:" + kubectl get configmaps -n "$namespace" -l app="$APP_NAME" 2>/dev/null || echo "No configmaps found" +} + +# Cleanup function +cleanup() { + if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then + print_status "Cleaning up temporary files..." + rm -rf "$TEMP_DIR" + fi +} + +# Set up cleanup trap +trap cleanup EXIT + +# Show usage information +show_usage() { + cat << EOF +Usage: $0 [environment] [action] [options] + +ENVIRONMENTS: + dev, development Deploy to development environment + prod, production Deploy to production environment + staging Deploy to staging environment + +ACTIONS: + apply Apply the configuration (default) + delete Delete the deployment + diff Show differences + dry-run Show what would be applied + validate Validate configuration + build Build and show manifests + status Show deployment status + +OPTIONS: + -h, --help Show this help message + --no-env Skip loading environment files + --verbose Enable verbose output + +EXAMPLES: + $0 dev apply Deploy to development + $0 prod diff Show production differences + $0 staging delete Delete staging deployment + $0 dev status Show development status + +ENVIRONMENT FILES: + The script loads variables from (in order): + - ~/.env + - ./.env + - ./.env.\$ENVIRONMENT + - ./.env.local + +EOF +} + +# Main execution function +main() { + local skip_env=false + local verbose=false + + # Parse options + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + --no-env) + skip_env=true + shift + ;; + --verbose) + verbose=true + set -x + shift + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + if [[ -z "${ENVIRONMENT_SET:-}" ]]; then + ENVIRONMENT="$1" + ENVIRONMENT_SET=true + elif [[ -z "${ACTION_SET:-}" ]]; then + ACTION="$1" + ACTION_SET=true + else + print_error "Too many arguments: $1" + show_usage + exit 1 + fi + shift + ;; + esac + done + + print_status "Kustomize Deployment with Environment Variables" + print_status "Environment: $ENVIRONMENT" + print_status "Action: $ACTION" + + # Special case for status action + if [[ "$ACTION" == "status" ]]; then + if [[ "$skip_env" != "true" ]]; then + load_environment + fi + show_status + exit 0 + fi + + # Validate prerequisites + validate_prerequisites + + # Load environment variables + if [[ "$skip_env" != "true" ]]; then + load_environment + fi + + # Create temporary overlay with substituted variables + TEMP_DIR=$(substitute_variables) + generate_dynamic_config "$TEMP_DIR" + + # Execute the kubectl command + execute_kubectl "$TEMP_DIR" "$ACTION" + + if [[ "$ACTION" == "apply" ]]; then + print_success "Deployment completed successfully!" + show_status + elif [[ "$ACTION" == "delete" ]]; then + print_success "Resources deleted successfully!" + fi +} + +# Run main function with all arguments +main "$@" diff --git a/scripts/generate-config.sh b/scripts/generate-config.sh new file mode 100755 index 0000000..29fb5e4 --- /dev/null +++ b/scripts/generate-config.sh @@ -0,0 +1,474 @@ +#!/bin/bash + +# generate-config.sh +# Generates config.env files for Kustomize from environment variables +# Usage: ./scripts/generate-config.sh [environment] +# Example: ./scripts/generate-config.sh dev + +set -euo pipefail + +# Default environment +ENVIRONMENT=${1:-dev} +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Load environment variables from various sources +load_env_files() { + local env_files=( + "$HOME/.env" + "$PROJECT_ROOT/.env" + "$PROJECT_ROOT/.env.$ENVIRONMENT" + "$PROJECT_ROOT/.env.local" + ) + + print_status "Loading environment variables..." + + for env_file in "${env_files[@]}"; do + if [[ -f "$env_file" ]]; then + print_status "Loading: $env_file" + # Source the file in a subshell to avoid polluting current environment + set -a + source "$env_file" + set +a + else + print_warning "File not found: $env_file" + fi + done +} + +# Generate config.env for base configuration +generate_base_config() { + local config_file="$PROJECT_ROOT/k8s-kustomize/base/config.env" + local temp_file=$(mktemp) + + print_status "Generating base config.env..." + + cat > "$temp_file" << EOF +# Base configuration for rxminder application +# Generated automatically from environment variables +# Generated on: $(date) +# Environment: $ENVIRONMENT + +# Application Environment +NODE_ENV=${NODE_ENV:-production} +LOG_LEVEL=${LOG_LEVEL:-info} + +# API Configuration +REACT_APP_API_URL=${REACT_APP_API_URL:-http://rxminder-couchdb-service:5984} + +# Feature Flags +ENABLE_MONITORING=${ENABLE_MONITORING:-false} +DEBUG=${DEBUG:-false} + +# Cache Configuration +CACHE_TTL=${CACHE_TTL:-1800} + +# Database Configuration +DB_HOST=${DB_HOST:-rxminder-couchdb-service} +DB_PORT=${DB_PORT:-5984} +COUCHDB_DATABASE_NAME=${COUCHDB_DATABASE_NAME:-meds_app} + +# Security Configuration +ENABLE_CORS=${ENABLE_CORS:-true} +CORS_ORIGIN=${CORS_ORIGIN:-*} + +# Performance Configuration +REQUEST_TIMEOUT=${REQUEST_TIMEOUT:-30000} +MAX_CONNECTIONS=${MAX_CONNECTIONS:-100} + +# Logging Configuration +LOG_FORMAT=${LOG_FORMAT:-json} +LOG_TIMESTAMP=${LOG_TIMESTAMP:-true} + +# Health Check Configuration +HEALTH_CHECK_INTERVAL=${HEALTH_CHECK_INTERVAL:-30} +READINESS_CHECK_TIMEOUT=${READINESS_CHECK_TIMEOUT:-5} + +# Application Metadata +APP_NAME=${APP_NAME:-rxminder} +APP_VERSION=${APP_VERSION:-1.0.0} + +# Container Registry +REGISTRY_URL=${REGISTRY_URL:-gitea-http.taildb3494.ts.net} +IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-will/rxminder} + +# Ingress Configuration +INGRESS_CLASS=${INGRESS_CLASS:-nginx} +CERT_MANAGER_ISSUER=${CERT_MANAGER_ISSUER:-letsencrypt-prod} + +# Monitoring and Observability +ENABLE_METRICS=${ENABLE_METRICS:-false} +METRICS_PORT=${METRICS_PORT:-9090} +ENABLE_TRACING=${ENABLE_TRACING:-false} + +# Development specific (will be overridden in overlays) +DEV_MODE=${DEV_MODE:-false} +HOT_RELOAD=${HOT_RELOAD:-false} +EOF + + # Move temp file to final location + mv "$temp_file" "$config_file" + print_success "Generated: $config_file" +} + +# Generate environment-specific config +generate_environment_config() { + local overlay_dir="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT" + local env_config_file="$overlay_dir/config.env" + + if [[ ! -d "$overlay_dir" ]]; then + print_error "Environment overlay directory not found: $overlay_dir" + return 1 + fi + + print_status "Generating environment-specific config for: $ENVIRONMENT" + + case "$ENVIRONMENT" in + "dev"|"development") + generate_dev_config "$env_config_file" + ;; + "prod"|"production") + generate_prod_config "$env_config_file" + ;; + "staging") + generate_staging_config "$env_config_file" + ;; + *) + print_warning "Unknown environment: $ENVIRONMENT. Generating generic config." + generate_generic_config "$env_config_file" + ;; + esac +} + +# Generate development-specific configuration +generate_dev_config() { + local config_file="$1" + local temp_file=$(mktemp) + + cat > "$temp_file" << EOF +# Development environment configuration +# Generated on: $(date) + +NODE_ENV=development +LOG_LEVEL=debug +DEBUG=true +ENABLE_MONITORING=false +DEV_MODE=true +HOT_RELOAD=true + +# Development URLs (override if needed) +REACT_APP_API_URL=${DEV_API_URL:-http://rxminder-couchdb-service:5984} +CORS_ORIGIN=${DEV_CORS_ORIGIN:-*} + +# Development domain +INGRESS_HOST=${DEV_INGRESS_HOST:-rxminder-dev.local} + +# Relaxed timeouts for debugging +REQUEST_TIMEOUT=60000 +HEALTH_CHECK_INTERVAL=60 + +# Development image tag +IMAGE_TAG=${DEV_IMAGE_TAG:-dev} +EOF + + mv "$temp_file" "$config_file" + print_success "Generated development config: $config_file" +} + +# Generate production-specific configuration +generate_prod_config() { + local config_file="$1" + local temp_file=$(mktemp) + + cat > "$temp_file" << EOF +# Production environment configuration +# Generated on: $(date) + +NODE_ENV=production +LOG_LEVEL=${PROD_LOG_LEVEL:-warn} +DEBUG=false +ENABLE_MONITORING=true +DEV_MODE=false + +# Production URLs +REACT_APP_API_URL=${PROD_API_URL:-http://rxminder-couchdb-service:5984} +CORS_ORIGIN=${PROD_CORS_ORIGIN:-https://rxminder.yourdomain.com} + +# Production domain +INGRESS_HOST=${PROD_INGRESS_HOST:-rxminder.yourdomain.com} + +# Production performance settings +CACHE_TTL=3600 +REQUEST_TIMEOUT=30000 +MAX_CONNECTIONS=200 + +# Production monitoring +ENABLE_METRICS=true +ENABLE_TRACING=true + +# Production image tag +IMAGE_TAG=${PROD_IMAGE_TAG:-v1.0.0} + +# Security settings +ENABLE_SECURITY_HEADERS=true +ENABLE_RATE_LIMITING=true +EOF + + mv "$temp_file" "$config_file" + print_success "Generated production config: $config_file" +} + +# Generate staging-specific configuration +generate_staging_config() { + local config_file="$1" + local temp_file=$(mktemp) + + cat > "$temp_file" << EOF +# Staging environment configuration +# Generated on: $(date) + +NODE_ENV=staging +LOG_LEVEL=${STAGING_LOG_LEVEL:-info} +DEBUG=false +ENABLE_MONITORING=true +DEV_MODE=false + +# Staging URLs +REACT_APP_API_URL=${STAGING_API_URL:-http://rxminder-couchdb-service:5984} +CORS_ORIGIN=${STAGING_CORS_ORIGIN:-https://staging.rxminder.yourdomain.com} + +# Staging domain +INGRESS_HOST=${STAGING_INGRESS_HOST:-staging.rxminder.yourdomain.com} + +# Staging image tag +IMAGE_TAG=${STAGING_IMAGE_TAG:-staging} + +# Enable monitoring but with relaxed settings +ENABLE_METRICS=true +ENABLE_TRACING=false +EOF + + mv "$temp_file" "$config_file" + print_success "Generated staging config: $config_file" +} + +# Generate generic environment configuration +generate_generic_config() { + local config_file="$1" + local temp_file=$(mktemp) + + cat > "$temp_file" << EOF +# Generic environment configuration for: $ENVIRONMENT +# Generated on: $(date) + +NODE_ENV=${ENVIRONMENT} +LOG_LEVEL=${LOG_LEVEL:-info} +DEBUG=${DEBUG:-false} + +# Image tag for this environment +IMAGE_TAG=${ENVIRONMENT} +EOF + + mv "$temp_file" "$config_file" + print_success "Generated generic config: $config_file" +} + +# Generate secrets template (not actual secrets) +generate_secrets_template() { + local secrets_file="$PROJECT_ROOT/k8s-kustomize/overlays/$ENVIRONMENT/secrets.env.template" + local temp_file=$(mktemp) + + print_status "Generating secrets template..." + + cat > "$temp_file" << EOF +# Secrets template for $ENVIRONMENT environment +# Copy this to secrets.env and fill in actual values +# DO NOT commit secrets.env to version control + +# Database credentials +COUCHDB_USERNAME=${COUCHDB_USERNAME:-admin} +COUCHDB_PASSWORD=CHANGE_ME_IN_${ENVIRONMENT^^} + +# Registry credentials (if using private registry) +REGISTRY_USERNAME=${REGISTRY_USERNAME:-} +REGISTRY_PASSWORD=CHANGE_ME +REGISTRY_EMAIL=${REGISTRY_EMAIL:-} + +# TLS/SSL certificates (base64 encoded) +TLS_CERT=CHANGE_ME +TLS_KEY=CHANGE_ME + +# API keys and tokens +API_SECRET_KEY=CHANGE_ME +JWT_SECRET=CHANGE_ME + +# External service credentials +MONITORING_API_KEY=CHANGE_ME +SMTP_PASSWORD=CHANGE_ME +EOF + + mv "$temp_file" "$secrets_file" + print_success "Generated secrets template: $secrets_file" + print_warning "Remember to create actual secrets.env file and add it to .gitignore!" +} + +# Validate generated configuration +validate_config() { + local config_file="$PROJECT_ROOT/k8s-kustomize/base/config.env" + + print_status "Validating generated configuration..." + + if [[ ! -f "$config_file" ]]; then + print_error "Config file not found: $config_file" + return 1 + fi + + # Check for required variables + local required_vars=("APP_NAME" "NODE_ENV" "REACT_APP_API_URL") + local missing_vars=() + + for var in "${required_vars[@]}"; do + if ! grep -q "^${var}=" "$config_file"; then + missing_vars+=("$var") + fi + done + + if [[ ${#missing_vars[@]} -gt 0 ]]; then + print_error "Missing required variables: ${missing_vars[*]}" + return 1 + fi + + print_success "Configuration validation passed!" +} + +# Display usage information +show_usage() { + cat << EOF +Usage: $0 [environment] [options] + +ENVIRONMENTS: + dev, development Generate development configuration + prod, production Generate production configuration + staging Generate staging configuration + Generate configuration for custom environment + +OPTIONS: + -h, --help Show this help message + -v, --validate Only validate existing configuration + --secrets Generate secrets template + --dry-run Show what would be generated without writing files + +EXAMPLES: + $0 dev Generate development configuration + $0 prod --secrets Generate production config and secrets template + $0 --validate Validate existing configuration + +ENVIRONMENT VARIABLES: + The script will load variables from: + - ~/.env (global user environment) + - ./.env (project environment) + - ./.env.\$ENVIRONMENT (environment-specific) + - ./.env.local (local overrides) + +EOF +} + +# Main execution +main() { + local validate_only=false + local generate_secrets=false + local dry_run=false + + # Parse command line arguments + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -v|--validate) + validate_only=true + shift + ;; + --secrets) + generate_secrets=true + shift + ;; + --dry-run) + dry_run=true + shift + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + ENVIRONMENT="$1" + shift + ;; + esac + done + + print_status "Kustomize Config Generator" + print_status "Environment: $ENVIRONMENT" + + if [[ "$validate_only" == "true" ]]; then + validate_config + exit $? + fi + + if [[ "$dry_run" == "true" ]]; then + print_status "DRY RUN MODE - No files will be written" + # Set dry run flag for other functions to check + export DRY_RUN=true + fi + + # Load environment variables + load_env_files + + # Generate configurations + generate_base_config + generate_environment_config + + if [[ "$generate_secrets" == "true" ]]; then + generate_secrets_template + fi + + # Validate generated configuration + validate_config + + print_success "Configuration generation completed!" + print_status "Next steps:" + echo " 1. Review generated files in k8s-kustomize/" + echo " 2. Update any environment-specific values" + echo " 3. Create secrets.env files for sensitive data" + echo " 4. Test with: make kustomize-dry-run-$ENVIRONMENT" +} + +# Run main function with all arguments +main "$@"