From 9f1ff0bbbe55527225c07b4944196f611f6f61e1 Mon Sep 17 00:00:00 2001 From: gitmuhammedalbayrak Date: Mon, 24 Nov 2025 03:07:56 +0300 Subject: [PATCH] feat: Implement Helm Chart and update CI/CD pipeline --- .gitea/workflows/deploy.yaml | 39 ++++++------ deploy/charts/evrak/Chart.yaml | 6 ++ deploy/charts/evrak/templates/_helpers.tpl | 43 +++++++++++++ deploy/charts/evrak/templates/backend.yaml | 54 ++++++++++++++++ deploy/charts/evrak/templates/frontend.yaml | 40 ++++++++++++ deploy/charts/evrak/templates/ingress.yaml | 32 ++++++++++ deploy/charts/evrak/templates/postgres.yaml | 70 +++++++++++++++++++++ deploy/charts/evrak/templates/secrets.yaml | 13 ++++ deploy/charts/evrak/values.yaml | 43 +++++++++++++ 9 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 deploy/charts/evrak/Chart.yaml create mode 100644 deploy/charts/evrak/templates/_helpers.tpl create mode 100644 deploy/charts/evrak/templates/backend.yaml create mode 100644 deploy/charts/evrak/templates/frontend.yaml create mode 100644 deploy/charts/evrak/templates/ingress.yaml create mode 100644 deploy/charts/evrak/templates/postgres.yaml create mode 100644 deploy/charts/evrak/templates/secrets.yaml create mode 100644 deploy/charts/evrak/values.yaml diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index e6f281f..d33f180 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -50,33 +50,34 @@ jobs: cache-to: type=gha,mode=max # ----------------------------------------------------------------- - # CD PART + # CD PART (HELM) # ----------------------------------------------------------------- - - name: Install Kubectl Binary + - name: Install Kubectl & Helm run: | + # Install kubectl (ARM64) curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/ + + # Install Helm + curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - - name: Deploy to Kubernetes and Update + - name: Deploy with Helm run: | # 1. Write Kubeconfig content to file echo "${{ secrets.KUBE_CONFIG }}" > /tmp/kubeconfig.yaml - # 2. Get the new image Digests - BACKEND_DIGEST="${{ vars.GITEA_REGISTRY_URL }}/${{ github.repository }}/backend@${{ steps.docker_build_backend.outputs.digest }}" - FRONTEND_DIGEST="${{ vars.GITEA_REGISTRY_URL }}/${{ github.repository }}/frontend@${{ steps.docker_build_frontend.outputs.digest }}" - - # 3. Update Deployment - kubectl set image deployment/evrak-backend backend=${BACKEND_DIGEST} \ - --kubeconfig=/tmp/kubeconfig.yaml -n default + # 2. Deploy using Helm Upgrade + # We pass the image repository and tag explicitly to ensure we use the Gitea registry + helm upgrade --install evrak ./deploy/charts/evrak \ + --kubeconfig /tmp/kubeconfig.yaml \ + --namespace default \ + --set backend.image.repository=${{ vars.GITEA_REGISTRY_URL }}/${{ github.repository }}/backend \ + --set backend.image.tag=latest \ + --set frontend.image.repository=${{ vars.GITEA_REGISTRY_URL }}/${{ github.repository }}/frontend \ + --set frontend.image.tag=latest \ + --set postgres.auth.password=${{ secrets.DB_PASSWORD }} - kubectl set image deployment/evrak-frontend frontend=${FRONTEND_DIGEST} \ - --kubeconfig=/tmp/kubeconfig.yaml -n default - - # 4. Rollout Restart to ensure fresh pods - kubectl rollout restart deployment/evrak-backend \ - --kubeconfig=/tmp/kubeconfig.yaml -n default - - kubectl rollout restart deployment/evrak-frontend \ - --kubeconfig=/tmp/kubeconfig.yaml -n default + # 3. Force restart to pick up latest image if tag is 'latest' (Helm doesn't always redeploy if values didn't change) + kubectl rollout restart deployment/evrak-backend --kubeconfig /tmp/kubeconfig.yaml -n default + kubectl rollout restart deployment/evrak-frontend --kubeconfig /tmp/kubeconfig.yaml -n default diff --git a/deploy/charts/evrak/Chart.yaml b/deploy/charts/evrak/Chart.yaml new file mode 100644 index 0000000..ee4ea5e --- /dev/null +++ b/deploy/charts/evrak/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: evrak +description: A Helm chart for Evrak Project Management System +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/deploy/charts/evrak/templates/_helpers.tpl b/deploy/charts/evrak/templates/_helpers.tpl new file mode 100644 index 0000000..1a4856b --- /dev/null +++ b/deploy/charts/evrak/templates/_helpers.tpl @@ -0,0 +1,43 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "evrak.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "evrak.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 }} + +{{/* +Common labels +*/}} +{{- define "evrak.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{ include "evrak.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "evrak.selectorLabels" -}} +app.kubernetes.io/name: {{ include "evrak.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/deploy/charts/evrak/templates/backend.yaml b/deploy/charts/evrak/templates/backend.yaml new file mode 100644 index 0000000..c1ead4a --- /dev/null +++ b/deploy/charts/evrak/templates/backend.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "evrak.fullname" . }}-backend + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + replicas: {{ .Values.backend.replicas }} + selector: + matchLabels: + {{- include "evrak.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: backend + template: + metadata: + labels: + {{- include "evrak.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + spec: + containers: + - name: backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.service.port }} + env: + - name: DB_HOST + value: {{ include "evrak.fullname" . }}-postgres + - name: DB_PORT + value: {{ .Values.backend.env.dbPort | quote }} + - name: DB_USERNAME + value: {{ .Values.postgres.auth.username | quote }} + - name: DB_DATABASE + value: {{ .Values.postgres.auth.database | quote }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "evrak.fullname" . }}-db-secret + key: password +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "evrak.fullname" . }}-backend + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.port }} + selector: + {{- include "evrak.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend diff --git a/deploy/charts/evrak/templates/frontend.yaml b/deploy/charts/evrak/templates/frontend.yaml new file mode 100644 index 0000000..32db19c --- /dev/null +++ b/deploy/charts/evrak/templates/frontend.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "evrak.fullname" . }}-frontend + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + replicas: {{ .Values.frontend.replicas }} + selector: + matchLabels: + {{- include "evrak.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: frontend + template: + metadata: + labels: + {{- include "evrak.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: frontend + spec: + containers: + - name: frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.service.port }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "evrak.fullname" . }}-frontend + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.port }} + selector: + {{- include "evrak.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: frontend diff --git a/deploy/charts/evrak/templates/ingress.yaml b/deploy/charts/evrak/templates/ingress.yaml new file mode 100644 index 0000000..87b5385 --- /dev/null +++ b/deploy/charts/evrak/templates/ingress.yaml @@ -0,0 +1,32 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "evrak.fullname" . }} + labels: + {{- include "evrak.labels" . | nindent 4 }} + annotations: + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + - host: {{ .Values.global.domain }} + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: {{ include "evrak.fullname" . }}-backend + port: + number: {{ .Values.backend.service.port }} + - path: / + pathType: Prefix + backend: + service: + name: {{ include "evrak.fullname" . }}-frontend + port: + number: {{ .Values.frontend.service.port }} +{{- end }} diff --git a/deploy/charts/evrak/templates/postgres.yaml b/deploy/charts/evrak/templates/postgres.yaml new file mode 100644 index 0000000..9881d6d --- /dev/null +++ b/deploy/charts/evrak/templates/postgres.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "evrak.fullname" . }}-postgres + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: postgres +spec: + replicas: 1 + selector: + matchLabels: + {{- include "evrak.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: postgres + template: + metadata: + labels: + {{- include "evrak.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: postgres + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + env: + - name: POSTGRES_USER + value: {{ .Values.postgres.auth.username | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgres.auth.database | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "evrak.fullname" . }}-db-secret + key: password + ports: + - containerPort: 5432 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ include "evrak.fullname" . }}-postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "evrak.fullname" . }}-postgres + labels: + {{- include "evrak.labels" . | nindent 4 }} + app.kubernetes.io/component: postgres +spec: + ports: + - port: 5432 + targetPort: 5432 + selector: + {{- include "evrak.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: postgres +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "evrak.fullname" . }}-postgres-pvc + labels: + {{- include "evrak.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.postgres.storage.className }} + resources: + requests: + storage: {{ .Values.postgres.storage.size }} diff --git a/deploy/charts/evrak/templates/secrets.yaml b/deploy/charts/evrak/templates/secrets.yaml new file mode 100644 index 0000000..9d0c8ad --- /dev/null +++ b/deploy/charts/evrak/templates/secrets.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "evrak.fullname" . }}-db-secret + labels: + {{- include "evrak.labels" . | nindent 4 }} +type: Opaque +data: + # In a real scenario, you'd want to generate this or pass it in. + # For now, we'll use the value from values.yaml (base64 encoded automatically by Helm if we used stringData, but here we do it manually or let user provide it) + # Better approach: use stringData +stringData: + password: {{ .Values.postgres.auth.password | default "postgres" | quote }} diff --git a/deploy/charts/evrak/values.yaml b/deploy/charts/evrak/values.yaml new file mode 100644 index 0000000..7a96417 --- /dev/null +++ b/deploy/charts/evrak/values.yaml @@ -0,0 +1,43 @@ +global: + domain: evrak.konstantiniyye.studio + +backend: + image: + repository: git.konstantiniyye.studio/muhammed/evrak/backend + tag: latest + pullPolicy: Always + replicas: 1 + service: + port: 3000 + env: + dbPort: 5432 + dbDatabase: evrak + # dbHost, dbUsername, dbPassword will be injected via secrets/templates + +frontend: + image: + repository: git.konstantiniyye.studio/muhammed/evrak/frontend + tag: latest + pullPolicy: Always + replicas: 1 + service: + port: 80 + +postgres: + image: + repository: postgres + tag: "14-alpine" + storage: + size: 5Gi + className: "local-path" # Based on your cluster info + auth: + username: evrak_user + database: evrak + # Password should be managed via secrets, but for simplicity in values we can set a default or require it to be passed + # In production, use --set postgres.auth.password=... + +ingress: + enabled: true + className: traefik + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod # Assuming you have this, or remove if not