Compare commits

...

7 commits

Author SHA1 Message Date
2e3bcfac2d Update haproxy-ingress-rbac.yaml
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-06-29 17:30:27 +02:00
fd31ce7eed Add generate_kubeconfig.sh
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-06-29 15:21:42 +02:00
f22d7423e8 Add haproxy-ingress-rbac.yaml
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-06-29 15:17:26 +02:00
3bcf42e94a Add haproxy-ingress-configmap.yaml
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-06-29 15:17:10 +02:00
83a3d12dbd Add go_cron_bash.sh
All checks were successful
Build Go Binary / build (push) Successful in 31s
2025-06-29 10:12:03 +02:00
4c65ddfb9e Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 31s
2025-06-29 07:42:26 +02:00
c88d0e2739 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 33s
2025-06-29 07:39:38 +02:00
6 changed files with 261 additions and 14 deletions

View file

@ -8,3 +8,21 @@ export HAPROXY_TEMPLATE="./haproxy.tmpl"
./haproxy-generator > /etc/haproxy/haproxy.cfg && sudo systemctl restart haproxy
```
# HaProxy Template
```
{{- range .backends }}
backend {{ .Name }}
mode tcp
balance leastconn
cookie {{ .Name }} insert indirect nocache
{{- range .Servers }}
server {{ .Name }} {{ .Address }}:{{ .Port }} check cookie {{ .Cookie }}
{{- end }}
{{- end }}
```

30
generate_kubeconfig.sh Normal file
View file

@ -0,0 +1,30 @@
#!/bin/bash
# Erstellt ein Token
TOKEN=$(kubectl -n kube-system create token haproxy-sa)
# Get Cluster Info
CLUSTER_NAME=$(kubectl config view -o jsonpath='{.clusters[0].name}')
CLUSTER_SERVER=$(kubectl config view -o jsonpath="{.clusters[0].cluster.server}")
CA_DATA=$(kubectl config view --raw -o jsonpath="{.clusters[0].cluster.certificate-authority-data}")
# Erstelle kubeconfig
cat <<EOF > haproxy-kubeconfig.yaml
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: ${CA_DATA}
server: ${CLUSTER_SERVER}
name: ${CLUSTER_NAME}
contexts:
- context:
cluster: ${CLUSTER_NAME}
user: haproxy-sa
name: haproxy-context
current-context: haproxy-context
users:
- name: haproxy-sa
user:
token: ${TOKEN}
EOF

42
go_cron_bash.sh Normal file
View file

@ -0,0 +1,42 @@
#!/bin/bash
set -euo pipefail
BIN_PATH="/usr/local/bin/haproxy_generator"
CONFIG_PATH="/etc/haproxy/haproxy.cfg"
NEW_CONFIG="${CONFIG_PATH}.new"
TEMPLATE_PATH="/etc/haproxy/haproxy.tmpl"
# Setze notwendige Umgebungsvariablen
export HAPROXY_TEMPLATE="$TEMPLATE_PATH"
export KUBERNETES_HOST="https://10.0.20.7:6443"
export KUBERNETES_TOKEN="eyJhbGciOi..." # <<< hier echten Token einfügen
export KUBERNETES_VERIFYSSL="false"
# Prüfe, ob Template vorhanden ist
if [ ! -f "$TEMPLATE_PATH" ]; then
echo "[ERROR] Template-Datei nicht gefunden: $TEMPLATE_PATH" >&2
exit 1
fi
# Neue Konfiguration generieren
if ! "$BIN_PATH" > "$NEW_CONFIG"; then
echo "[ERROR] Fehler beim Ausführen von haproxy_generator" >&2
exit 1
fi
# Syntaxprüfung der neuen Konfiguration
if ! /sbin/haproxy -f "$NEW_CONFIG" -c; then
echo "[ERROR] Neue Konfiguration ist syntaktisch ungültig (haproxy -c)" >&2
exit 1
fi
# Unterschiede prüfen und ggf. übernehmen
if ! diff -q "$CONFIG_PATH" "$NEW_CONFIG" > /dev/null; then
echo "[INFO] Änderungen erkannt - Konfiguration wird übernommen"
mv "$NEW_CONFIG" "$CONFIG_PATH"
/usr/sbin/service haproxy reload
else
echo "[INFO] Keine Änderungen - kein Reload notwendig"
rm -f "$NEW_CONFIG"
fi

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: haproxy-config
namespace: kube-system
data:
ssl-redirect: "true"
timeout-http-request: "5s"
use-forwarded-headers: "true"

27
haproxy-ingress-rbac.yaml Normal file
View file

@ -0,0 +1,27 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: gateway
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gateway
rules:
- apiGroups: [""]
resources: ["endpoints", "services", "namespaces", "nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gateway
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gateway
subjects:
- kind: ServiceAccount
name: gateway
namespace: kube-system

149
main.go
View file

@ -1,8 +1,9 @@
package main
import (
"bytes"
"crypto/sha1"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
@ -12,19 +13,39 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
)
type K8sList struct {
type K8sRawList struct {
Items []json.RawMessage `json:"items"`
}
type BackendServer struct {
Name string
Address string
Port int
Cookie string
}
type Backend struct {
Name string
Balance string
Servers []BackendServer
}
func getEnv(key, fallback string) string {
if val, exists := os.LookupEnv(key); exists && val != "" {
if val, ok := os.LookupEnv(key); ok && val != "" {
return val
}
return fallback
}
func hashString(input string) string {
h := sha1.New()
h.Write([]byte(input))
return hex.EncodeToString(h.Sum(nil))[:8]
}
func getTemplate(path string) (*template.Template, error) {
content, err := os.ReadFile(path)
if err != nil {
@ -42,6 +63,7 @@ func getK8sResources(k8sHost, token string, verifySSL bool, resource string) ([]
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "gateway-config")
client := &http.Client{}
if !verifySSL {
@ -62,7 +84,7 @@ func getK8sResources(k8sHost, token string, verifySSL bool, resource string) ([]
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
var list K8sList
var list K8sRawList
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
return nil, err
}
@ -84,29 +106,128 @@ func main() {
log.Fatalf("Invalid KUBERNETES_VERIFYSSL value: %v", err)
}
services, err := getK8sResources(k8sHost, k8sToken, verifySSL, "services")
servicesRaw, err := getK8sResources(k8sHost, k8sToken, verifySSL, "services")
if err != nil {
log.Fatalf("Failed to get services: %v", err)
}
endpoints, err := getK8sResources(k8sHost, k8sToken, verifySSL, "endpoints")
endpointsRaw, err := getK8sResources(k8sHost, k8sToken, verifySSL, "endpoints")
if err != nil {
log.Fatalf("Failed to get endpoints: %v", err)
}
tmplAbsPath, _ := filepath.Abs(templatePath)
type Service struct {
Metadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
} `json:"metadata"`
Spec struct {
Type string `json:"type"`
Ports []struct {
Port int `json:"port"`
} `json:"ports"`
} `json:"spec"`
}
type EndpointSubsetAddress struct {
IP string `json:"ip"`
}
type EndpointSubsetPort struct {
Port int `json:"port"`
}
type EndpointSubset struct {
Addresses []EndpointSubsetAddress `json:"addresses"`
Ports []EndpointSubsetPort `json:"ports"`
}
type Endpoint struct {
Metadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
} `json:"metadata"`
Subsets []EndpointSubset `json:"subsets"`
}
services := make([]Service, 0, len(servicesRaw))
for _, raw := range servicesRaw {
var svc Service
if err := json.Unmarshal(raw, &svc); err != nil {
log.Printf("Warn: failed to unmarshal service: %v", err)
continue
}
services = append(services, svc)
}
endpoints := make([]Endpoint, 0, len(endpointsRaw))
for _, raw := range endpointsRaw {
var ep Endpoint
if err := json.Unmarshal(raw, &ep); err != nil {
log.Printf("Warn: failed to unmarshal endpoint: %v", err)
continue
}
endpoints = append(endpoints, ep)
}
endpointMap := make(map[string]Endpoint)
for _, ep := range endpoints {
key := ep.Metadata.Namespace + "/" + ep.Metadata.Name
endpointMap[key] = ep
}
backends := []Backend{}
for _, svc := range services {
if svc.Spec.Type != "LoadBalancer" {
continue
}
key := svc.Metadata.Namespace + "/" + svc.Metadata.Name
ep, found := endpointMap[key]
if !found || len(ep.Subsets) == 0 {
continue
}
b := Backend{
Name: "SRV_" + strings.ReplaceAll(svc.Metadata.Name, " ", "-"),
Balance: "leastconn",
}
servers := []BackendServer{}
serverIndex := 1
for _, subset := range ep.Subsets {
for _, addr := range subset.Addresses {
for _, port := range subset.Ports {
cookie := hashString(fmt.Sprintf("%s-%s-%d", svc.Metadata.Name, addr.IP, port.Port))
serverName := fmt.Sprintf("%s_%d", svc.Metadata.Name, serverIndex)
serverIndex++
servers = append(servers, BackendServer{
Name: serverName,
Address: addr.IP,
Port: port.Port,
Cookie: cookie,
})
}
}
}
b.Servers = servers
backends = append(backends, b)
}
tmplAbsPath, err := filepath.Abs(templatePath)
if err != nil {
log.Fatalf("Failed to get absolute path: %v", err)
}
tmpl, err := getTemplate(tmplAbsPath)
if err != nil {
log.Fatalf("Failed to parse template: %v", err)
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, map[string]interface{}{
"services": services,
"endpoints": endpoints,
err = tmpl.Execute(os.Stdout, map[string]interface{}{
"backends": backends,
})
if err != nil {
log.Fatalf("Failed to render template: %v", err)
log.Fatalf("Failed to execute template: %v", err)
}
fmt.Println(buf.String())
}