diff --git a/charts/my-recipes-chart/Chart.yaml b/charts/my-recipes-chart/Chart.yaml index 022edee..86c815b 100644 --- a/charts/my-recipes-chart/Chart.yaml +++ b/charts/my-recipes-chart/Chart.yaml @@ -1,6 +1,14 @@ apiVersion: v2 -name: recipes-db -description: PostgreSQL (StatefulSet) for Recipes backend +name: my-recipes +description: Complete recipe management application with PostgreSQL, FastAPI backend, and React frontend type: application -version: 0.1.0 -appVersion: "16" +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - recipes + - fastapi + - react + - postgresql +maintainers: + - name: Development Team + diff --git a/charts/my-recipes-chart/templates/backend-deployment.yaml b/charts/my-recipes-chart/templates/backend-deployment.yaml index 15d8b10..3fc4485 100644 --- a/charts/my-recipes-chart/templates/backend-deployment.yaml +++ b/charts/my-recipes-chart/templates/backend-deployment.yaml @@ -1,26 +1,57 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Release.Name }}-backend + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} labels: - app: {{ .Release.Name }}-backend + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend spec: - replicas: {{ .Values.backend.replicas }} + replicas: {{ .Values.backend.replicaCount }} selector: matchLabels: - app: {{ .Release.Name }}-backend + app: {{ .Release.Name }}-{{ .Values.backend.name }} template: metadata: labels: - app: {{ .Release.Name }}-backend + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend spec: containers: - - name: backend + - name: {{ .Values.backend.name }} image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" imagePullPolicy: {{ .Values.backend.image.pullPolicy }} ports: - - containerPort: 8000 + - containerPort: {{ .Values.backend.service.targetPort }} name: http + protocol: TCP + env: + - name: PYTHONUNBUFFERED + value: "1" envFrom: - secretRef: name: {{ .Release.Name }}-db-credentials + livenessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.backend.resources.requests.cpu }} + memory: {{ .Values.backend.resources.requests.memory }} + limits: + cpu: {{ .Values.backend.resources.limits.cpu }} + memory: {{ .Values.backend.resources.limits.memory }} + diff --git a/charts/my-recipes-chart/templates/backend-service.yaml b/charts/my-recipes-chart/templates/backend-service.yaml index e69de29..6608df6 100644 --- a/charts/my-recipes-chart/templates/backend-service.yaml +++ b/charts/my-recipes-chart/templates/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.backend.name }} diff --git a/charts/my-recipes-chart/templates/db-schema-configmap.yaml b/charts/my-recipes-chart/templates/db-schema-configmap.yaml index d0be270..f99151e 100644 --- a/charts/my-recipes-chart/templates/db-schema-configmap.yaml +++ b/charts/my-recipes-chart/templates/db-schema-configmap.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} data: schema.sql: | -- Create recipes table @@ -10,10 +11,10 @@ data: name TEXT NOT NULL, meal_type TEXT NOT NULL, time_minutes INTEGER NOT NULL, - tags JSONB NOT NULL DEFAULT '[]', ingredients JSONB NOT NULL DEFAULT '[]', - steps JSONB NOT NULL DEFAULT '[]' + steps JSONB NOT NULL DEFAULT '[]', + image TEXT ); CREATE INDEX IF NOT EXISTS idx_recipes_meal_type @@ -27,3 +28,4 @@ data: CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb ON recipes USING GIN (ingredients); + diff --git a/charts/my-recipes-chart/templates/db-secret.yaml b/charts/my-recipes-chart/templates/db-secret.yaml index ad2c9eb..f4a35f2 100644 --- a/charts/my-recipes-chart/templates/db-secret.yaml +++ b/charts/my-recipes-chart/templates/db-secret.yaml @@ -2,10 +2,12 @@ apiVersion: v1 kind: Secret metadata: name: {{ .Release.Name }}-db-credentials + namespace: {{ .Values.global.namespace }} type: Opaque stringData: - DB_HOST: {{ printf "%s-db-headless.%s.svc.cluster.local" .Release.Name .Release.Namespace }} + DB_HOST: {{ printf "%s-%s-headless.%s.svc.cluster.local" .Release.Name .Values.postgres.name .Values.global.namespace }} DB_PORT: "{{ .Values.postgres.port }}" DB_NAME: {{ .Values.postgres.database | quote }} DB_USER: {{ .Values.postgres.user | quote }} DB_PASSWORD: {{ .Values.postgres.password | quote }} + diff --git a/charts/my-recipes-chart/templates/db-service.yaml b/charts/my-recipes-chart/templates/db-service.yaml index d598d42..5cae43b 100644 --- a/charts/my-recipes-chart/templates/db-service.yaml +++ b/charts/my-recipes-chart/templates/db-service.yaml @@ -1,14 +1,36 @@ apiVersion: v1 kind: Service metadata: - name: {{ .Release.Name }}-db-headless + name: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + namespace: {{ .Values.global.namespace }} labels: - app: {{ .Release.Name }}-db + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database spec: clusterIP: None selector: - app: {{ .Release.Name }}-db + app: {{ .Release.Name }}-{{ .Values.postgres.name }} ports: - name: postgres port: {{ .Values.postgres.port }} - targetPort: 5432 + targetPort: {{ .Values.postgres.port }} + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + type: {{ .Values.postgres.service.type }} + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP + diff --git a/charts/my-recipes-chart/templates/db-statefulset.yaml b/charts/my-recipes-chart/templates/db-statefulset.yaml index b53d194..b609270 100644 --- a/charts/my-recipes-chart/templates/db-statefulset.yaml +++ b/charts/my-recipes-chart/templates/db-statefulset.yaml @@ -1,26 +1,31 @@ apiVersion: apps/v1 kind: StatefulSet metadata: - name: {{ .Release.Name }}-db + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} labels: - app: {{ .Release.Name }}-db + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database spec: - serviceName: {{ .Release.Name }}-db-headless + serviceName: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless replicas: 1 selector: matchLabels: - app: {{ .Release.Name }}-db + app: {{ .Release.Name }}-{{ .Values.postgres.name }} template: metadata: labels: - app: {{ .Release.Name }}-db + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database spec: containers: - name: postgres - image: {{ .Values.postgres.image }} + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} ports: - - containerPort: 5432 + - containerPort: {{ .Values.postgres.port }} name: postgres + protocol: TCP env: - name: POSTGRES_USER value: {{ .Values.postgres.user | quote }} @@ -28,11 +33,40 @@ spec: value: {{ .Values.postgres.password | quote }} - name: POSTGRES_DB value: {{ .Values.postgres.database | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata volumeMounts: - name: data mountPath: /var/lib/postgresql/data - name: init-sql mountPath: /docker-entrypoint-initdb.d + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.postgres.resources.requests.cpu }} + memory: {{ .Values.postgres.resources.requests.memory }} + limits: + cpu: {{ .Values.postgres.resources.limits.cpu }} + memory: {{ .Values.postgres.resources.limits.memory }} volumes: - name: init-sql configMap: @@ -49,3 +83,4 @@ spec: {{- if .Values.postgres.persistence.storageClass }} storageClassName: {{ .Values.postgres.persistence.storageClass | quote }} {{- end }} + diff --git a/charts/my-recipes-chart/templates/frontend-deployment.yaml b/charts/my-recipes-chart/templates/frontend-deployment.yaml index e69de29..1555238 100644 --- a/charts/my-recipes-chart/templates/frontend-deployment.yaml +++ b/charts/my-recipes-chart/templates/frontend-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend + spec: + containers: + - name: {{ .Values.frontend.name }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.service.targetPort }} + name: http + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.frontend.resources.requests.cpu }} + memory: {{ .Values.frontend.resources.requests.memory }} + limits: + cpu: {{ .Values.frontend.resources.limits.cpu }} + memory: {{ .Values.frontend.resources.limits.memory }} diff --git a/charts/my-recipes-chart/templates/frontend-service.yaml b/charts/my-recipes-chart/templates/frontend-service.yaml index e69de29..9427830 100644 --- a/charts/my-recipes-chart/templates/frontend-service.yaml +++ b/charts/my-recipes-chart/templates/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} diff --git a/charts/my-recipes-chart/templates/ingress.yaml b/charts/my-recipes-chart/templates/ingress.yaml index e69de29..a234d51 100644 --- a/charts/my-recipes-chart/templates/ingress.yaml +++ b/charts/my-recipes-chart/templates/ingress.yaml @@ -0,0 +1,48 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + {{- if eq .backend "frontend" }} + name: {{ .Release.Name }}-{{ $.Values.frontend.name }} + port: + number: {{ $.Values.frontend.service.port }} + {{- else if eq .backend "backend" }} + name: {{ .Release.Name }}-{{ $.Values.backend.name }} + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/my-recipes-chart/values.yaml b/charts/my-recipes-chart/values.yaml index 8130208..0fc9812 100644 --- a/charts/my-recipes-chart/values.yaml +++ b/charts/my-recipes-chart/values.yaml @@ -1,28 +1,103 @@ -# Backend image +global: + namespace: default + imagePullSecrets: [] + +# Backend configuration backend: + name: backend + replicaCount: 2 image: repository: harbor.dvirlabs.com/my-apps/my-recipes-backend - tag: v1.0.0 pullPolicy: IfNotPresent - replicas: 1 + tag: "latest" + + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + env: + PYTHONUNBUFFERED: "1" -# Frontend image +# Frontend configuration frontend: + name: frontend + replicaCount: 2 image: - repository: harbor.dvirlabs.com/my-apps/my-recipes/frontend - tag: v1.0.0 + repository: harbor.dvirlabs.com/my-apps/my-recipes-frontend pullPolicy: IfNotPresent - replicas: 1 + tag: "latest" + + service: + type: ClusterIP + port: 3000 + targetPort: 3000 + + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi -# Postgres DB +# PostgreSQL configuration postgres: - image: postgres:16 + name: db + image: + repository: postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + user: recipes_user - password: recipes_password # POC only – later use Secret/ExternalSecret + password: recipes_password database: recipes_db port: 5432 + + service: + type: ClusterIP + port: 5432 + targetPort: 5432 + persistence: enabled: true accessMode: ReadWriteOnce storageClass: "nfs-client" - size: 8Gi + size: 10Gi + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# Ingress configuration +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: recipes.local + paths: + - path: / + pathType: Prefix + backend: frontend + - path: /api + pathType: Prefix + backend: backend + tls: + - secretName: recipes-tls + hosts: + - recipes.local + diff --git a/manifests/my-recipes/values.yaml b/manifests/my-recipes/values.yaml index 8a8b3fe..8ac28af 100644 --- a/manifests/my-recipes/values.yaml +++ b/manifests/my-recipes/values.yaml @@ -1,28 +1,93 @@ -# Backend image +global: + namespace: default + imagePullSecrets: [] + +# Backend configuration backend: + name: backend + replicaCount: 1 image: repository: harbor.dvirlabs.com/my-apps/my-recipes-backend - tag: master-35e1584 pullPolicy: IfNotPresent - replicas: 1 - tag: master-3afc7d2 -# Frontend image + tag: "master-35e1584" + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + env: + PYTHONUNBUFFERED: "1" + +# Frontend configuration frontend: + name: frontend + replicaCount: 1 image: repository: registry.dvirlabs.com/my-apps/my-recipes-frontend - tag: v1.0.0 pullPolicy: IfNotPresent - replicas: 1 - tag: master-56d00c2 -# Postgres DB + tag: "v1.0.0" + service: + type: ClusterIP + port: 3000 + targetPort: 3000 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + +# PostgreSQL configuration postgres: - image: postgres:16 + name: db + image: + repository: postgres + tag: "16" + pullPolicy: IfNotPresent user: recipes_user password: recipes_password # POC only – later use Secret/ExternalSecret database: recipes_db port: 5432 + service: + type: ClusterIP + port: 5432 + targetPort: 5432 persistence: enabled: true accessMode: ReadWriteOnce storageClass: "nfs-client" size: 8Gi + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# Ingress (keep defaults in chart if you prefer not to expose) +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: recipes.local + paths: + - path: / + pathType: Prefix + backend: frontend + - path: /api + pathType: Prefix + backend: backend + tls: + - secretName: recipes-tls + hosts: + - recipes.local