talos deploy
Some checks failed
Django Helm GitOps Deploy / build-and-test (push) Failing after 11s
Django Helm GitOps Deploy / deploy-prod (push) Has been skipped

This commit is contained in:
root 2026-11-08 02:32:47 +01:00
parent 82c06a5963
commit e9220a6a69
14 changed files with 212 additions and 64 deletions

View file

@ -4,9 +4,17 @@ on:
push: push:
branches: [ "main" ] branches: [ "main" ]
workflow_dispatch: workflow_dispatch:
inputs:
deploy_prod:
description: "Nach erfolgreichem Test auch auf Produktion (Talos) deployen?"
type: boolean
default: true
jobs: jobs:
build-and-deploy: # ---------------------------------------------------------------------------
# 1. Build + Test (k3s) – wie bisher
# ---------------------------------------------------------------------------
build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest container: catthehacker/ubuntu:act-latest
steps: 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" cp nginx_ssl/nginx.crt /usr/local/share/ca-certificates/nginx-sandbox.crt 2>/dev/null || echo "Nutze Workspace-Zertifikat"
update-ca-certificates update-ca-certificates
# Nutzt dein Personal Access Token (PAT) für den schnellen Push über den Socket-Port
- name: Log in to Local Registry - name: Log in to Local Registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login localhost:3005 -u ${{ github.actor }} --password-stdin 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 - name: Build Django Image
run: | run: |
docker build -t localhost:3005/${{ github.repository }}:${{ github.sha }} . docker build \
docker build -t localhost:3005/${{ github.repository }}:latest . -t localhost:3005/${{ github.repository }}:${{ github.sha }} \
-t localhost:3005/${{ github.repository }}:latest .
- name: Push Django Image - name: Push Django Image
run: | run: |
docker push localhost:3005/${{ github.repository }}:${{ github.sha }} docker push localhost:3005/${{ github.repository }}:${{ github.sha }}
docker push localhost:3005/${{ github.repository }}:latest docker push localhost:3005/${{ github.repository }}:latest
# Holt die echten K3s-Schlüssel aus dem automatischen Host-Synchronisations-Secret - name: Prepare Kubernetes Access (k3s)
- name: Prepare Kubernetes Access
run: | run: |
echo "${{ secrets.KUBECONFIG_DATA }}" > ./kind_kubeconfig echo "${{ secrets.KUBECONFIG_DATA }}" > ./kind_kubeconfig
chmod 600 ./kind_kubeconfig chmod 600 ./kind_kubeconfig
# Führt direkt das installierte, performante Zentral-Binary aus - name: Terraform Deploy (test / k3s)
- name: Terraform Deploy
run: | run: |
terraform --version terraform --version
terraform init terraform init -reconfigure -backend-config=envs/test.backend.hcl
terraform apply -auto-approve -var="app_version=${{ github.sha }}" 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 (test)
- name: Verify App Health
run: | run: |
echo "Warte 15 Sekunden auf die Django-Zertifikate und Migrationen..."
echo "DEBUG: Sende Request an local-k3s-platform..."
sleep 15 sleep 15
curl --fail -L --connect-timeout 5 -H "Host: boday-it.ch" http://local-k3s-platform:80/ || exit 1 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 (test)
- name: Publish Map to cloudmon Dashboard
run: | run: |
terraform show -json > plan.json terraform show -json > plan.json
curl -X POST \ curl -X POST \
-H "Authorization: Bearer boday-cloudmon-super-token-2026" \ -H "Authorization: Bearer ${{ secrets.CLOUDMON_TOKEN }}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Host: cloudmon.local" \ -H "Host: cloudmon.local" \
--data-binary @plan.json \ --data-binary @plan.json \
"http://local-k3s-platform:80/api/v1/push/?repo=${{ github.repository }}" "http://local-k3s-platform:80/api/v1/push/?repo=${{ github.repository }}&env=test"
echo "Infrastruktur-Map erfolgreich an cloudmon übermittelt!" # ---------------------------------------------------------------------------
# 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"

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ davenv
.DS_Store .DS_Store
notes.txt notes.txt
runserver.sh runserver.sh
talos_kubeconfig

View file

@ -26,6 +26,11 @@ spec:
secretKeyRef: secretKeyRef:
name: {{ .Release.Name }}-secrets name: {{ .Release.Name }}-secrets
key: postgres-password 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: ports:
- containerPort: 5432 - containerPort: 5432
name: dbport name: dbport
@ -37,6 +42,9 @@ spec:
name: db-data name: db-data
spec: spec:
accessModes: [ "ReadWriteOnce" ] accessModes: [ "ReadWriteOnce" ]
{{- with .Values.postgres.storageClassName }}
storageClassName: {{ . | quote }}
{{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.postgres.storageSize }} storage: {{ .Values.postgres.storageSize }}

View file

@ -1,43 +1,32 @@
# KORRIGIERT: Der offizielle, moderne API-Pfad für Ingress in K3s
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ .Release.Name }}-ingress name: {{ .Release.Name }}-ingress
{{- with .Values.ingress.annotations }}
annotations: annotations:
kubernetes.io/ingress.class: "nginx" {{- toYaml . | nindent 4 }}
{{- end }}
spec: spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . | quote }}
{{- end }}
rules: rules:
- host: boday-it.ch {{- range .Values.ingress.hosts }}
- host: {{ . | quote }}
http: http:
paths: paths:
- path: /static - path: /static
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ .Release.Name }}-web name: {{ $.Release.Name }}-web
port: port:
name: static name: static
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ .Release.Name }}-web 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
port: port:
name: django name: django
{{- end }}

View file

@ -17,7 +17,7 @@ spec:
- name: collectstatic - name: collectstatic
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
command: command:
- /bin/sh - /bin/sh
- -c - -c
- | - |

View file

@ -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

View file

@ -0,0 +1 @@
# k3s – Testumgebung: rendert identisch zum bisherigen Stand

View file

@ -9,7 +9,7 @@ django:
debug: "0" debug: "0"
allowedHosts: "boday-it.ch,www.boday-it.ch,localhost,127.0.0.1" 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: secrets:
djangoSecretKey: "aldhguroqugaplslljdgzlshfz" djangoSecretKey: "aldhguroqugaplslljdgzlshfz"
postgresPassword: "asafdksdf" postgresPassword: "asafdksdf"
@ -18,3 +18,13 @@ postgres:
database: "djangodb" database: "djangodb"
user: "djangouser" user: "djangouser"
storageSize: 1Gi 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

1
envs/prod.backend.hcl Normal file
View file

@ -0,0 +1 @@
path = "terraform/states/boday_tf_prod"

5
envs/prod.tfvars Normal file
View file

@ -0,0 +1,5 @@
# Talos – Produktion
environment = "prod"
kubeconfig_path = "./talos_kubeconfig"
kube_insecure = false
namespace = "boday-it"

1
envs/test.backend.hcl Normal file
View file

@ -0,0 +1 @@
path = "terraform/states/boday_tf"

6
envs/test.tfvars Normal file
View file

@ -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"

99
main.tf
View file

@ -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" { backend "consul" {
address = "consul:8500" address = "consul:8500"
path = "terraform/states/boday_tf"
} }
} }
# Variable für den dynamischen Git-SHA-Tag deiner Pipeline # --- Variablen ---------------------------------------------------------------
variable "app_version" { variable "app_version" {
type = string description = "Image-Tag (Git-SHA aus der Pipeline)"
default = "latest" 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" { provider "kubernetes" {
config_path = "./kind_kubeconfig" config_path = var.kubeconfig_path
host = "https://local-k3s-platform:6443" host = var.kube_host
insecure = true insecure = var.kube_insecure
} }
provider "helm" { provider "helm" {
kubernetes { kubernetes {
config_path = "./kind_kubeconfig" config_path = var.kubeconfig_path
host = "https://local-k3s-platform:6443" host = var.kube_host
insecure = true insecure = var.kube_insecure
} }
} }
# DAS FINALE DJANGO HELM-CHART DEPLOYMENT # --- Deployment --------------------------------------------------------------
resource "helm_release" "boday_website" {
name = "boday-it" locals {
chart = "./charts/boday-it" # Zeigt direkt auf den lokalen Chart-Ordner im Repo secret_overrides = {
namespace = "default" "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 { set {
name = "image.tag" name = "image.tag"
value = var.app_version 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
}
}
}