diff --git a/argocd-apps/brand-master.yaml b/argocd-apps/brand-master.yaml new file mode 100644 index 0000000..790660d --- /dev/null +++ b/argocd-apps/brand-master.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: brand-master + namespace: argocd +spec: + project: my-apps + source: + repoURL: ssh://git@gitea-ssh.dev-tools.svc.cluster.local:2222/dvirlabs/my-apps.git + targetRevision: HEAD + path: charts/brand-master-chart + helm: + valueFiles: + - ../../manifests/brand-master/values.yaml + destination: + server: https://kubernetes.default.svc + namespace: my-apps + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/charts/brand-master-chart/Chart.yaml b/charts/brand-master-chart/Chart.yaml new file mode 100644 index 0000000..ee30f38 --- /dev/null +++ b/charts/brand-master-chart/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: brand-master +description: A Helm chart for Brand Master - E-commerce Fashion & Shoe Store +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - brand-master + - ecommerce + - fashion + - shoes +maintainers: + - name: dvir diff --git a/charts/brand-master-chart/README.md b/charts/brand-master-chart/README.md new file mode 100644 index 0000000..dca234e --- /dev/null +++ b/charts/brand-master-chart/README.md @@ -0,0 +1,158 @@ +# Brand Master Helm Chart + +This Helm chart deploys the Brand Master e-commerce application on Kubernetes. + +## Components + +- **Frontend**: React-based UI served by Nginx +- **Backend**: FastAPI application +- **Database**: PostgreSQL 16 +- **Storage**: 15GB PVC for product images + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- cert-manager (for TLS certificates) +- Storage class configured (nfs-client by default) + +## Installation + +### 1. Update values.yaml + +Edit `values.yaml` and configure: + +- Image repositories (if using private registry) +- Domain names for ingress +- JWT secret key (IMPORTANT!) +- Database credentials +- Storage class name + +### 2. Install the chart + +```bash +# Install in the my-apps namespace +helm install brand-master ./brand-master-chart -n my-apps --create-namespace + +# Or with custom values +helm install brand-master ./brand-master-chart -n my-apps \ + --set backend.jwtSecretKey=your-super-secret-key \ + --set postgres.password=secure-password +``` + +### 3. Upgrade the chart + +```bash +helm upgrade brand-master ./brand-master-chart -n my-apps +``` + +### 4. Uninstall + +```bash +helm uninstall brand-master -n my-apps +``` + +## Configuration + +### Key Configuration Options + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `backend.image.repository` | Backend Docker image | `harbor.dvirlabs.com/my-apps/brand-master-backend` | +| `backend.image.tag` | Backend image tag | `latest` | +| `backend.jwtSecretKey` | JWT secret for authentication | `your-secret-key-change-this-in-production` | +| `backend.persistence.enabled` | Enable persistent storage for images | `true` | +| `backend.persistence.size` | Size of uploads PVC | `15Gi` | +| `frontend.image.repository` | Frontend Docker image | `harbor.dvirlabs.com/my-apps/brand-master-frontend` | +| `frontend.image.tag` | Frontend image tag | `latest` | +| `postgres.user` | PostgreSQL username | `brand_master_user` | +| `postgres.password` | PostgreSQL password | `brand_master_password` | +| `postgres.database` | PostgreSQL database name | `brand_master_db` | +| `postgres.persistence.size` | Size of database PVC | `10Gi` | + +## Building Docker Images + +### Backend + +```bash +cd backend +docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest . +docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest +``` + +### Frontend + +```bash +cd frontend +docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest \ + --build-arg VITE_API_URL=https://api-brand-master.dvirlabs.com . +docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest +``` + +## Storage + +The chart creates two PVCs: + +1. **Database PVC**: 10GB for PostgreSQL data +2. **Uploads PVC**: 15GB for product images at `/app/uploads` + +Both use the `nfs-client` storage class by default. Update this in `values.yaml` if needed. + +## Ingress + +The chart creates two ingress resources: + +- **Frontend**: `brand-master.dvirlabs.com` +- **Backend API**: `api-brand-master.dvirlabs.com` + +TLS is enabled by default using Let's Encrypt via cert-manager. + +## Troubleshooting + +### Check pod status +```bash +kubectl get pods -n my-apps +``` + +### View logs +```bash +# Backend logs +kubectl logs -n my-apps -l app.kubernetes.io/component=backend + +# Frontend logs +kubectl logs -n my-apps -l app.kubernetes.io/component=frontend + +# Database logs +kubectl logs -n my-apps -l app.kubernetes.io/component=database +``` + +### Access services locally +```bash +# Frontend +kubectl port-forward -n my-apps svc/brand-master-frontend 8080:80 + +# Backend +kubectl port-forward -n my-apps svc/brand-master-backend 8000:8000 + +# Database +kubectl port-forward -n my-apps svc/brand-master-db 5432:5432 +``` + +### Check PVC status +```bash +kubectl get pvc -n my-apps +``` + +## Security Notes + +1. **Change the JWT secret** in production +2. **Update database credentials** +3. **Use strong passwords** +4. **Configure proper CORS settings** +5. **Review and adjust resource limits** +6. **Enable network policies** if needed +7. **Use image pull secrets** for private registries + +## Support + +For issues or questions, refer to the main repository documentation. diff --git a/charts/brand-master-chart/templates/NOTES.txt b/charts/brand-master-chart/templates/NOTES.txt new file mode 100644 index 0000000..bb083f4 --- /dev/null +++ b/charts/brand-master-chart/templates/NOTES.txt @@ -0,0 +1,48 @@ +1. Get the application URL by running these commands: +{{- if .Values.frontend.ingress.enabled }} +{{- range $host := .Values.frontend.ingress.hosts }} + {{- range .paths }} + Frontend: http{{ if $.Values.frontend.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- end }} + +{{- if .Values.backend.ingress.enabled }} +{{- range $host := .Values.backend.ingress.hosts }} + {{- range .paths }} + Backend API: http{{ if $.Values.backend.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- end }} + +2. Database Connection: + Host: {{ include "brand-master.fullname" . }}-db + Port: {{ .Values.postgres.port }} + Database: {{ .Values.postgres.database }} + User: {{ .Values.postgres.user }} + +3. Product Images Storage: + {{- if .Values.backend.persistence.enabled }} + PVC: {{ include "brand-master.fullname" . }}-uploads-pvc + Size: {{ .Values.backend.persistence.size }} + Mount Path: {{ .Values.backend.persistence.mountPath }} + {{- else }} + Warning: Persistence is disabled. Product images will be lost on pod restart! + {{- end }} + +4. IMPORTANT Security Notes: + - Change the JWT secret key in values.yaml before deploying to production + - Update the database password in values.yaml + - Configure your domain names in the ingress sections + - Ensure cert-manager is installed for TLS certificates + +5. To access the application locally without ingress: + kubectl port-forward svc/{{ include "brand-master.fullname" . }}-frontend 8080:80 + kubectl port-forward svc/{{ include "brand-master.fullname" . }}-backend 8000:8000 + +6. To check pod status: + kubectl get pods -l app.kubernetes.io/instance={{ .Release.Name }} + +7. To view logs: + kubectl logs -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=backend + kubectl logs -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=frontend diff --git a/charts/brand-master-chart/templates/_helpers.tpl b/charts/brand-master-chart/templates/_helpers.tpl new file mode 100644 index 0000000..09c6169 --- /dev/null +++ b/charts/brand-master-chart/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "brand-master.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "brand-master.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "brand-master.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "brand-master.labels" -}} +helm.sh/chart: {{ include "brand-master.chart" . }} +{{ include "brand-master.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "brand-master.selectorLabels" -}} +app.kubernetes.io/name: {{ include "brand-master.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "brand-master.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "brand-master.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/brand-master-chart/templates/backend-deployment.yaml b/charts/brand-master-chart/templates/backend-deployment.yaml new file mode 100644 index 0000000..079a02c --- /dev/null +++ b/charts/brand-master-chart/templates/backend-deployment.yaml @@ -0,0 +1,106 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "brand-master.fullname" . }}-backend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + {{- include "brand-master.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: backend + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "brand-master.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "brand-master.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: wait-for-postgres + image: harbor.dvirlabs.com/dockerhub/busybox:1.35 + command: ['sh', '-c', 'until nc -z {{ include "brand-master.fullname" . }}-db-headless {{ .Values.postgres.port | default 5432 }}; do echo waiting for postgres; sleep 2; done;'] + containers: + - name: backend + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: database-url + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: jwt-secret-key + - name: ALGORITHM + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: jwt-algorithm + - name: ACCESS_TOKEN_EXPIRE_MINUTES + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: jwt-expire-minutes + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + volumeMounts: + {{- if .Values.backend.persistence.enabled }} + - name: uploads + mountPath: {{ .Values.backend.persistence.mountPath }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} + volumes: + {{- if .Values.backend.persistence.enabled }} + - name: uploads + persistentVolumeClaim: + claimName: {{ include "brand-master.fullname" . }}-uploads-pvc + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/brand-master-chart/templates/backend-ingress.yaml b/charts/brand-master-chart/templates/backend-ingress.yaml new file mode 100644 index 0000000..9e37256 --- /dev/null +++ b/charts/brand-master-chart/templates/backend-ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.backend.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "brand-master.fullname" . }}-backend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: backend + {{- with .Values.backend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.backend.ingress.className }} + ingressClassName: {{ .Values.backend.ingress.className }} + {{- end }} + {{- if .Values.backend.ingress.tls }} + tls: + {{- range .Values.backend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.backend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "brand-master.fullname" $ }}-backend + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/brand-master-chart/templates/backend-pvc.yaml b/charts/brand-master-chart/templates/backend-pvc.yaml new file mode 100644 index 0000000..fa8eb3f --- /dev/null +++ b/charts/brand-master-chart/templates/backend-pvc.yaml @@ -0,0 +1,18 @@ +{{- if .Values.backend.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "brand-master.fullname" . }}-uploads-pvc + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + accessModes: + - {{ .Values.backend.persistence.accessMode }} + {{- if .Values.backend.persistence.storageClass }} + storageClassName: {{ .Values.backend.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.backend.persistence.size }} +{{- end }} diff --git a/charts/brand-master-chart/templates/backend-service.yaml b/charts/brand-master-chart/templates/backend-service.yaml new file mode 100644 index 0000000..04a75ec --- /dev/null +++ b/charts/brand-master-chart/templates/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "brand-master.fullname" . }}-backend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "brand-master.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend diff --git a/charts/brand-master-chart/templates/db-service.yaml b/charts/brand-master-chart/templates/db-service.yaml new file mode 100644 index 0000000..d3ff669 --- /dev/null +++ b/charts/brand-master-chart/templates/db-service.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "brand-master.fullname" . }}-db + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + type: {{ .Values.postgres.service.type }} + ports: + - port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.service.targetPort }} + protocol: TCP + name: postgres + selector: + {{- include "brand-master.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "brand-master.fullname" . }}-db-headless + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + type: ClusterIP + clusterIP: None + ports: + - port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.service.targetPort }} + protocol: TCP + name: postgres + selector: + {{- include "brand-master.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database diff --git a/charts/brand-master-chart/templates/db-statefulset.yaml b/charts/brand-master-chart/templates/db-statefulset.yaml new file mode 100644 index 0000000..77be2a8 --- /dev/null +++ b/charts/brand-master-chart/templates/db-statefulset.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "brand-master.fullname" . }}-db + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + serviceName: {{ include "brand-master.fullname" . }}-db-headless + replicas: 1 + selector: + matchLabels: + {{- include "brand-master.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: database + template: + metadata: + labels: + {{- include "brand-master.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + securityContext: + fsGroup: 999 + initContainers: + - name: fix-permissions + image: harbor.dvirlabs.com/dockerhub/busybox:latest + command: + - sh + - -c + - | + chown -R 999:999 /var/lib/postgresql/data || true + chmod 700 /var/lib/postgresql/data || true + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + securityContext: + runAsUser: 0 + containers: + - name: postgres + securityContext: + runAsUser: 999 + runAsNonRoot: true + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} + ports: + - name: postgres + containerPort: {{ .Values.postgres.port }} + protocol: TCP + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: postgres-user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: postgres-password + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: {{ include "brand-master.fullname" . }}-secrets + key: postgres-database + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + - name: postgres-run + mountPath: /var/run/postgresql + resources: + {{- toYaml .Values.postgres.resources | nindent 12 }} + startupProbe: + exec: + command: + - sh + - -c + - pg_isready -h 127.0.0.1 -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 30 + livenessProbe: + exec: + command: + - sh + - -c + - pg_isready -h 127.0.0.1 -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - sh + - -c + - pg_isready -h 127.0.0.1 -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: postgres-run + emptyDir: {} + {{- if .Values.postgres.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: postgres-data + labels: + {{- include "brand-master.labels" . | nindent 8 }} + spec: + accessModes: + - {{ .Values.postgres.persistence.accessMode }} + {{- if .Values.postgres.persistence.storageClass }} + storageClassName: {{ .Values.postgres.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} + {{- else }} + - name: postgres-data + emptyDir: {} + {{- end }} diff --git a/charts/brand-master-chart/templates/frontend-deployment.yaml b/charts/brand-master-chart/templates/frontend-deployment.yaml new file mode 100644 index 0000000..274a172 --- /dev/null +++ b/charts/brand-master-chart/templates/frontend-deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "brand-master.fullname" . }}-frontend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + {{- include "brand-master.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: frontend + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "brand-master.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: frontend + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "brand-master.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: frontend + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + {{- if .Values.frontend.env }} + env: + {{- range $key, $value := .Values.frontend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/brand-master-chart/templates/frontend-ingress.yaml b/charts/brand-master-chart/templates/frontend-ingress.yaml new file mode 100644 index 0000000..d8d2de4 --- /dev/null +++ b/charts/brand-master-chart/templates/frontend-ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.frontend.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "brand-master.fullname" . }}-frontend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend + {{- with .Values.frontend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.frontend.ingress.className }} + ingressClassName: {{ .Values.frontend.ingress.className }} + {{- end }} + {{- if .Values.frontend.ingress.tls }} + tls: + {{- range .Values.frontend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.frontend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "brand-master.fullname" $ }}-frontend + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/brand-master-chart/templates/frontend-service.yaml b/charts/brand-master-chart/templates/frontend-service.yaml new file mode 100644 index 0000000..a2326c7 --- /dev/null +++ b/charts/brand-master-chart/templates/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "brand-master.fullname" . }}-frontend + labels: + {{- include "brand-master.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "brand-master.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: frontend diff --git a/charts/brand-master-chart/templates/secret.yaml b/charts/brand-master-chart/templates/secret.yaml new file mode 100644 index 0000000..e69908a --- /dev/null +++ b/charts/brand-master-chart/templates/secret.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "brand-master.fullname" . }}-secrets + labels: + {{- include "brand-master.labels" . | nindent 4 }} +type: Opaque +stringData: + postgres-user: {{ .Values.postgres.user | quote }} + postgres-password: {{ .Values.postgres.password | quote }} + postgres-database: {{ .Values.postgres.database | quote }} + database-url: "postgresql://{{ .Values.postgres.user }}:{{ .Values.postgres.password }}@{{ include "brand-master.fullname" . }}-db:{{ .Values.postgres.port }}/{{ .Values.postgres.database }}" + jwt-secret-key: {{ .Values.backend.jwtSecretKey | quote }} + jwt-algorithm: {{ .Values.backend.jwtAlgorithm | quote }} + jwt-expire-minutes: {{ .Values.backend.jwtExpireMinutes | quote }} diff --git a/charts/brand-master-chart/templates/serviceaccount.yaml b/charts/brand-master-chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..de9c270 --- /dev/null +++ b/charts/brand-master-chart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "brand-master.serviceAccountName" . }} + labels: + {{- include "brand-master.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/brand-master-chart/values.yaml b/charts/brand-master-chart/values.yaml new file mode 100644 index 0000000..7525c6e --- /dev/null +++ b/charts/brand-master-chart/values.yaml @@ -0,0 +1,165 @@ +global: + namespace: my-apps + imagePullSecrets: [] + +# Backend configuration +backend: + name: backend + replicaCount: 1 + image: + repository: harbor.dvirlabs.com/my-apps/brand-master-backend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + + env: + PYTHONUNBUFFERED: "1" + BACKEND_URL: "https://api-brand-master.dvirlabs.com" + FRONTEND_URL: "https://brand-master.dvirlabs.com" + + # JWT Secret Key (IMPORTANT: Change this in production!) + jwtSecretKey: "your-secret-key-change-this-in-production" + jwtAlgorithm: "HS256" + jwtExpireMinutes: "30" + + # Persistent storage for product images + persistence: + enabled: true + storageClass: "nfs-client" + accessMode: ReadWriteOnce + size: 15Gi + mountPath: /app/uploads + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: api-brand-master.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-brand-master-tls + hosts: + - api-brand-master.dvirlabs.com + +# Frontend configuration +frontend: + name: frontend + replicaCount: 1 + image: + repository: harbor.dvirlabs.com/my-apps/brand-master-frontend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 80 + targetPort: 80 + + env: + VITE_API_URL: "https://api-brand-master.dvirlabs.com" + + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: brand-master.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: brand-master-tls + hosts: + - brand-master.dvirlabs.com + +# PostgreSQL configuration +postgres: + name: db + image: + repository: harbor.dvirlabs.com/dockerhub/postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + + user: brand_master_user + password: brand_master_password + database: brand_master_db + port: 5432 + + service: + type: ClusterIP + port: 5432 + targetPort: 5432 + + persistence: + enabled: true + accessMode: ReadWriteOnce + storageClass: "nfs-client" + size: 10Gi + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# Service Account +serviceAccount: + create: true + annotations: {} + name: "" + +# Pod annotations +podAnnotations: {} + +# Pod security context +podSecurityContext: {} + # fsGroup: 2000 + +# Container security context +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# Node selector +nodeSelector: {} + +# Tolerations +tolerations: [] + +# Affinity +affinity: {} diff --git a/manifests/brand-master/values.yaml b/manifests/brand-master/values.yaml new file mode 100644 index 0000000..9576afc --- /dev/null +++ b/manifests/brand-master/values.yaml @@ -0,0 +1,101 @@ +nameOverride: "" +fullnameOverride: "" +commonLabels: {} +commonAnnotations: {} +imagePullSecrets: + - name: harbor-regcred + +backend: + image: + repository: harbor.dvirlabs.com/my-apps/brand-master-backend + tag: latest + pullPolicy: IfNotPresent + replicas: 1 + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 200m + memory: 256Mi + env: + - name: PYTHONUNBUFFERED + value: "1" + - name: BACKEND_URL + value: "https://api-brand-master.dvirlabs.com" + - name: FRONTEND_URL + value: "https://brand-master.dvirlabs.com" + - name: JWT_SECRET_KEY + value: "your-secret-key-change-this-in-production" + - name: JWT_ALGORITHM + value: "HS256" + - name: JWT_EXPIRE_MINUTES + value: "30" + persistence: + enabled: true + storageClass: "nfs-client" + size: 15Gi + mountPath: /app/uploads + service: + type: ClusterIP + port: 8000 + healthCheck: + path: /health + initialDelaySeconds: 10 + periodSeconds: 30 + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: api-brand-master.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-brand-master-tls + hosts: + - api-brand-master.dvirlabs.com + +frontend: + image: + repository: harbor.dvirlabs.com/my-apps/brand-master-frontend + tag: latest + pullPolicy: IfNotPresent + replicas: 1 + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + env: + - name: VITE_API_URL + value: "https://api-brand-master.dvirlabs.com" + service: + type: ClusterIP + port: 80 + healthCheck: + path: / + initialDelaySeconds: 5 + periodSeconds: 30 + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: brand-master.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: brand-master-tls + hosts: + - brand-master.dvirlabs.com