From e9220a6a6960dc62b351d4227afa0f746c8159a5 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Nov 2026 02:32:47 +0100 Subject: [PATCH] talos deploy --- .github/workflows/deploy.yml | 97 +++++++++++++----- .gitignore | 1 + charts/boday-it/templates/db-statefulset.yaml | 8 ++ charts/boday-it/templates/ingress.yaml | 33 +++---- .../templates/{secrets.yaml => secret.yaml} | 0 charts/boday-it/templates/web-deployment.yaml | 2 +- charts/boday-it/values-prod.yaml | 10 ++ charts/boday-it/values-test.yaml | 1 + charts/boday-it/values.yaml | 12 ++- envs/prod.backend.hcl | 1 + envs/prod.tfvars | 5 + envs/test.backend.hcl | 1 + envs/test.tfvars | 6 ++ main.tf | 99 +++++++++++++++---- 14 files changed, 212 insertions(+), 64 deletions(-) rename charts/boday-it/templates/{secrets.yaml => secret.yaml} (100%) create mode 100644 charts/boday-it/values-prod.yaml create mode 100644 charts/boday-it/values-test.yaml create mode 100644 envs/prod.backend.hcl create mode 100644 envs/prod.tfvars create mode 100644 envs/test.backend.hcl create mode 100644 envs/test.tfvars diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index aef3455..351bf38 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,9 +4,17 @@ on: push: branches: [ "main" ] workflow_dispatch: + inputs: + deploy_prod: + description: "Nach erfolgreichem Test auch auf Produktion (Talos) deployen?" + type: boolean + default: true jobs: - build-and-deploy: + # --------------------------------------------------------------------------- + # 1. Build + Test (k3s) – wie bisher + # --------------------------------------------------------------------------- + build-and-test: runs-on: ubuntu-latest container: catthehacker/ubuntu:act-latest steps: @@ -22,53 +30,96 @@ jobs: cp nginx_ssl/nginx.crt /usr/local/share/ca-certificates/nginx-sandbox.crt 2>/dev/null || echo "Nutze Workspace-Zertifikat" update-ca-certificates - # Nutzt dein Personal Access Token (PAT) für den schnellen Push über den Socket-Port - name: Log in to Local Registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login localhost:3005 -u ${{ github.actor }} --password-stdin - # Baut das Django-Image und taggt es mit dem eindeutigen Git-SHA + # Ein Build, zwei Tags - name: Build Django Image run: | - docker build -t localhost:3005/${{ github.repository }}:${{ github.sha }} . - docker build -t localhost:3005/${{ github.repository }}:latest . + docker build \ + -t localhost:3005/${{ github.repository }}:${{ github.sha }} \ + -t localhost:3005/${{ github.repository }}:latest . - name: Push Django Image run: | docker push localhost:3005/${{ github.repository }}:${{ github.sha }} docker push localhost:3005/${{ github.repository }}:latest - # Holt die echten K3s-Schlüssel aus dem automatischen Host-Synchronisations-Secret - - name: Prepare Kubernetes Access + - name: Prepare Kubernetes Access (k3s) run: | echo "${{ secrets.KUBECONFIG_DATA }}" > ./kind_kubeconfig chmod 600 ./kind_kubeconfig - # Führt direkt das installierte, performante Zentral-Binary aus - - name: Terraform Deploy + - name: Terraform Deploy (test / k3s) run: | terraform --version - terraform init - terraform apply -auto-approve -var="app_version=${{ github.sha }}" + terraform init -reconfigure -backend-config=envs/test.backend.hcl + terraform apply -auto-approve \ + -var-file=envs/test.tfvars \ + -var="app_version=${{ github.sha }}" - # Überprüft das Deployment im internen Container-Netzwerk und folgt dem Django-Redirect (-L) - - name: Verify App Health + - name: Verify App Health (test) run: | - echo "Warte 15 Sekunden auf die Django-Zertifikate und Migrationen..." - echo "DEBUG: Sende Request an local-k3s-platform..." sleep 15 curl --fail -L --connect-timeout 5 -H "Host: boday-it.ch" http://local-k3s-platform:80/ || exit 1 - echo "Erfolg: Die Boday-IT Django-Webseite läuft perfekt via Helm im GitOps-Standard!" + echo "Test (k3s) OK" - # DAS FINALE DEINES PRODUKTS: Generiert das anonyme JSON und pusht es mit dem cloudmon-Host-Header - - name: Publish Map to cloudmon Dashboard + - name: Publish Map to cloudmon Dashboard (test) run: | terraform show -json > plan.json - curl -X POST \ - -H "Authorization: Bearer boday-cloudmon-super-token-2026" \ + -H "Authorization: Bearer ${{ secrets.CLOUDMON_TOKEN }}" \ -H "Content-Type: application/json" \ -H "Host: cloudmon.local" \ --data-binary @plan.json \ - "http://local-k3s-platform:80/api/v1/push/?repo=${{ github.repository }}" - - echo "Infrastruktur-Map erfolgreich an cloudmon übermittelt!" + "http://local-k3s-platform:80/api/v1/push/?repo=${{ github.repository }}&env=test" + + # --------------------------------------------------------------------------- + # 2. Produktion (Talos) – nur wenn Test grün ist; gleiches Image (Git-SHA) + # --------------------------------------------------------------------------- + deploy-prod: + needs: build-and-test + if: github.event_name == 'push' || inputs.deploy_prod + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Prepare Kubernetes Access (Talos) + run: | + echo "${{ secrets.KUBECONFIG_TALOS }}" > ./talos_kubeconfig + chmod 600 ./talos_kubeconfig + + - name: Terraform Deploy (prod / Talos) + env: + TF_VAR_django_secret_key: ${{ secrets.PROD_DJANGO_SECRET_KEY }} + TF_VAR_postgres_password: ${{ secrets.PROD_POSTGRES_PASSWORD }} + run: | + terraform init -reconfigure -backend-config=envs/prod.backend.hcl + terraform apply -auto-approve \ + -var-file=envs/prod.tfvars \ + -var="app_version=${{ github.sha }}" + + # Erster Lauf: Longhorn-Volume + initdb dauern länger -> mit Retries + - name: Verify App Health (prod) + run: | + for i in $(seq 1 12); do + if curl --fail -sL --connect-timeout 5 -o /dev/null -H "Host: boday-it.ch" http://${{ vars.TALOS_INGRESS_IP }}/; then + echo "Prod (Talos) OK"; exit 0 + fi + echo "Noch nicht erreichbar, Versuch $i/12"; sleep 10 + done + exit 1 + + - name: Publish Map to cloudmon Dashboard (prod) + run: | + # show -json enthält sensitive Werte im Klartext -> Prod-Secrets vor dem Versand entfernen + terraform show -json | jq 'del(.. | .set_sensitive?)' > plan.json + ! grep -qF "${{ secrets.PROD_POSTGRES_PASSWORD }}" plan.json || { echo "Secret im JSON gefunden, Abbruch"; exit 1; } + curl -X POST \ + -H "Authorization: Bearer ${{ secrets.CLOUDMON_TOKEN }}" \ + -H "Content-Type: application/json" \ + -H "Host: cloudmon.local" \ + --data-binary @plan.json \ + "http://local-k3s-platform:80/api/v1/push/?repo=${{ github.repository }}&env=prod" diff --git a/.gitignore b/.gitignore index 1819f9e..7041805 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ davenv .DS_Store notes.txt runserver.sh +talos_kubeconfig diff --git a/charts/boday-it/templates/db-statefulset.yaml b/charts/boday-it/templates/db-statefulset.yaml index 60d7bd9..b395959 100644 --- a/charts/boday-it/templates/db-statefulset.yaml +++ b/charts/boday-it/templates/db-statefulset.yaml @@ -26,6 +26,11 @@ spec: secretKeyRef: name: {{ .Release.Name }}-secrets key: postgres-password + {{- if .Values.postgres.pgdataSubdir }} + # Longhorn-Volumes haben ein lost+found im Root -> initdb braucht ein Unterverzeichnis + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + {{- end }} ports: - containerPort: 5432 name: dbport @@ -37,6 +42,9 @@ spec: name: db-data spec: accessModes: [ "ReadWriteOnce" ] + {{- with .Values.postgres.storageClassName }} + storageClassName: {{ . | quote }} + {{- end }} resources: requests: storage: {{ .Values.postgres.storageSize }} diff --git a/charts/boday-it/templates/ingress.yaml b/charts/boday-it/templates/ingress.yaml index 6342e22..571b472 100644 --- a/charts/boday-it/templates/ingress.yaml +++ b/charts/boday-it/templates/ingress.yaml @@ -1,43 +1,32 @@ -# KORRIGIERT: Der offizielle, moderne API-Pfad für Ingress in K3s apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }}-ingress + {{- with .Values.ingress.annotations }} annotations: - kubernetes.io/ingress.class: "nginx" + {{- toYaml . | nindent 4 }} + {{- end }} spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . | quote }} + {{- end }} rules: - - host: boday-it.ch + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} http: paths: - path: /static pathType: Prefix backend: service: - name: {{ .Release.Name }}-web + name: {{ $.Release.Name }}-web port: name: static - path: / pathType: Prefix backend: service: - name: {{ .Release.Name }}-web - port: - name: django - - host: www.boday-it.ch - http: - paths: - - path: /static - pathType: Prefix - backend: - service: - name: {{ .Release.Name }}-web - port: - name: static - - path: / - pathType: Prefix - backend: - service: - name: {{ .Release.Name }}-web + name: {{ $.Release.Name }}-web port: name: django + {{- end }} diff --git a/charts/boday-it/templates/secrets.yaml b/charts/boday-it/templates/secret.yaml similarity index 100% rename from charts/boday-it/templates/secrets.yaml rename to charts/boday-it/templates/secret.yaml diff --git a/charts/boday-it/templates/web-deployment.yaml b/charts/boday-it/templates/web-deployment.yaml index 4713ed2..5254e9d 100644 --- a/charts/boday-it/templates/web-deployment.yaml +++ b/charts/boday-it/templates/web-deployment.yaml @@ -17,7 +17,7 @@ spec: - name: collectstatic image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} - command: + command: - /bin/sh - -c - | diff --git a/charts/boday-it/values-prod.yaml b/charts/boday-it/values-prod.yaml new file mode 100644 index 0000000..5614ab5 --- /dev/null +++ b/charts/boday-it/values-prod.yaml @@ -0,0 +1,10 @@ +# Talos – Produktion +postgres: + storageSize: 5Gi + storageClassName: longhorn + pgdataSubdir: true + +ingress: + className: traefik + annotations: + kubernetes.io/ingress.class: null # alte Annotation entfernen diff --git a/charts/boday-it/values-test.yaml b/charts/boday-it/values-test.yaml new file mode 100644 index 0000000..89b7805 --- /dev/null +++ b/charts/boday-it/values-test.yaml @@ -0,0 +1 @@ +# k3s – Testumgebung: rendert identisch zum bisherigen Stand diff --git a/charts/boday-it/values.yaml b/charts/boday-it/values.yaml index e5d82f0..3f08186 100644 --- a/charts/boday-it/values.yaml +++ b/charts/boday-it/values.yaml @@ -9,7 +9,7 @@ django: debug: "0" allowedHosts: "boday-it.ch,www.boday-it.ch,localhost,127.0.0.1" -# Diese Werte werden im Secret verschlüsselt +# Defaults für den Test. In Prod setzt Terraform echte Werte aus Forgejo-Secrets. secrets: djangoSecretKey: "aldhguroqugaplslljdgzlshfz" postgresPassword: "asafdksdf" @@ -18,3 +18,13 @@ postgres: database: "djangodb" user: "djangouser" storageSize: 1Gi + storageClassName: "" # leer = Default-StorageClass des Clusters + pgdataSubdir: false + +ingress: + className: "" + annotations: + kubernetes.io/ingress.class: "nginx" + hosts: + - boday-it.ch + - www.boday-it.ch diff --git a/envs/prod.backend.hcl b/envs/prod.backend.hcl new file mode 100644 index 0000000..e48d16e --- /dev/null +++ b/envs/prod.backend.hcl @@ -0,0 +1 @@ +path = "terraform/states/boday_tf_prod" diff --git a/envs/prod.tfvars b/envs/prod.tfvars new file mode 100644 index 0000000..94da71d --- /dev/null +++ b/envs/prod.tfvars @@ -0,0 +1,5 @@ +# Talos – Produktion +environment = "prod" +kubeconfig_path = "./talos_kubeconfig" +kube_insecure = false +namespace = "boday-it" diff --git a/envs/test.backend.hcl b/envs/test.backend.hcl new file mode 100644 index 0000000..94d45bf --- /dev/null +++ b/envs/test.backend.hcl @@ -0,0 +1 @@ +path = "terraform/states/boday_tf" diff --git a/envs/test.tfvars b/envs/test.tfvars new file mode 100644 index 0000000..fb85ac7 --- /dev/null +++ b/envs/test.tfvars @@ -0,0 +1,6 @@ +# k3s – Testumgebung (Verhalten wie bisher) +environment = "test" +kubeconfig_path = "./kind_kubeconfig" +kube_host = "https://local-k3s-platform:6443" +kube_insecure = true +namespace = "default" diff --git a/main.tf b/main.tf index e93d210..7557870 100644 --- a/main.tf +++ b/main.tf @@ -10,42 +10,107 @@ terraform { } } + # Der State-Pfad pro Umgebung wird beim init gesetzt: + # terraform init -backend-config=envs/test.backend.hcl (k3s, terraform/states/boday_tf – unverändert) + # terraform init -backend-config=envs/prod.backend.hcl (Talos, terraform/states/boday_tf_prod) backend "consul" { address = "consul:8500" - path = "terraform/states/boday_tf" } } -# Variable für den dynamischen Git-SHA-Tag deiner Pipeline +# --- Variablen --------------------------------------------------------------- + variable "app_version" { - type = string - default = "latest" + description = "Image-Tag (Git-SHA aus der Pipeline)" + type = string + default = "latest" } +variable "environment" { + description = "test (k3s) oder prod (Talos)" + type = string +} + +variable "kubeconfig_path" { + type = string + default = "./kind_kubeconfig" +} + +# Nur für k3s nötig (überschreibt den Server aus der Kubeconfig). Bei Talos null lassen. +variable "kube_host" { + type = string + default = null +} + +# k3s-Test: true. Talos: false – die Kubeconfig von talosctl enthält die CA. +variable "kube_insecure" { + type = bool + default = false +} + +variable "namespace" { + type = string + default = "default" +} + +# Leer = Wert aus values.yaml bleibt (k3s-Test). Für Prod als Forgejo-Secret übergeben. +variable "django_secret_key" { + type = string + default = "" + sensitive = true +} + +variable "postgres_password" { + type = string + default = "" + sensitive = true +} + +# --- Provider ---------------------------------------------------------------- + provider "kubernetes" { - config_path = "./kind_kubeconfig" - host = "https://local-k3s-platform:6443" - insecure = true + config_path = var.kubeconfig_path + host = var.kube_host + insecure = var.kube_insecure } provider "helm" { kubernetes { - config_path = "./kind_kubeconfig" - host = "https://local-k3s-platform:6443" - insecure = true + config_path = var.kubeconfig_path + host = var.kube_host + insecure = var.kube_insecure } } -# DAS FINALE DJANGO HELM-CHART DEPLOYMENT -resource "helm_release" "boday_website" { - name = "boday-it" - chart = "./charts/boday-it" # Zeigt direkt auf den lokalen Chart-Ordner im Repo - namespace = "default" +# --- Deployment -------------------------------------------------------------- + +locals { + secret_overrides = { + "secrets.djangoSecretKey" = var.django_secret_key + "secrets.postgresPassword" = var.postgres_password + } +} + +resource "helm_release" "boday_website" { + name = "boday-it" + chart = "./charts/boday-it" + namespace = var.namespace + create_namespace = var.namespace != "default" + + # Umgebungsspezifische Werte (Ingress-Klasse, StorageClass, ...) + values = [file("${path.module}/charts/boday-it/values-${var.environment}.yaml")] - # Überschreibt das Image-Tag dynamisch mit der aktuellen Pipeline-Version set { name = "image.tag" value = var.app_version } -} + # Secrets nur setzen, wenn sie übergeben wurden + dynamic "set_sensitive" { + for_each = { for k, v in local.secret_overrides : k => v if v != "" } + content { + name = set_sensitive.key + value = set_sensitive.value + } + } +}