talos deploy
This commit is contained in:
parent
82c06a5963
commit
e9220a6a69
14 changed files with 212 additions and 64 deletions
97
.github/workflows/deploy.yml
vendored
97
.github/workflows/deploy.yml
vendored
|
|
@ -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
1
.gitignore
vendored
|
|
@ -8,3 +8,4 @@ davenv
|
||||||
.DS_Store
|
.DS_Store
|
||||||
notes.txt
|
notes.txt
|
||||||
runserver.sh
|
runserver.sh
|
||||||
|
talos_kubeconfig
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
- |
|
- |
|
||||||
|
|
|
||||||
10
charts/boday-it/values-prod.yaml
Normal file
10
charts/boday-it/values-prod.yaml
Normal 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
|
||||||
1
charts/boday-it/values-test.yaml
Normal file
1
charts/boday-it/values-test.yaml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# k3s – Testumgebung: rendert identisch zum bisherigen Stand
|
||||||
|
|
@ -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
1
envs/prod.backend.hcl
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
path = "terraform/states/boday_tf_prod"
|
||||||
5
envs/prod.tfvars
Normal file
5
envs/prod.tfvars
Normal 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
1
envs/test.backend.hcl
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
path = "terraform/states/boday_tf"
|
||||||
6
envs/test.tfvars
Normal file
6
envs/test.tfvars
Normal 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
99
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" {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue