Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
2e3bcfac2d | |||
fd31ce7eed | |||
f22d7423e8 | |||
3bcf42e94a | |||
83a3d12dbd | |||
4c65ddfb9e | |||
c88d0e2739 |
6 changed files with 261 additions and 14 deletions
18
README_GO.md
18
README_GO.md
|
@ -8,3 +8,21 @@ export HAPROXY_TEMPLATE="./haproxy.tmpl"
|
||||||
|
|
||||||
./haproxy-generator > /etc/haproxy/haproxy.cfg && sudo systemctl restart haproxy
|
./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
30
generate_kubeconfig.sh
Normal 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
42
go_cron_bash.sh
Normal 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
|
9
haproxy-ingress-configmap.yaml
Normal file
9
haproxy-ingress-configmap.yaml
Normal 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
27
haproxy-ingress-rbac.yaml
Normal 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
149
main.go
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/sha1"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -12,19 +13,39 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type K8sList struct {
|
type K8sRawList struct {
|
||||||
Items []json.RawMessage `json:"items"`
|
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 {
|
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 val
|
||||||
}
|
}
|
||||||
return fallback
|
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) {
|
func getTemplate(path string) (*template.Template, error) {
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
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("Authorization", "Bearer "+token)
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "gateway-config")
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
if !verifySSL {
|
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))
|
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 {
|
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -84,29 +106,128 @@ func main() {
|
||||||
log.Fatalf("Invalid KUBERNETES_VERIFYSSL value: %v", err)
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get services: %v", err)
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get endpoints: %v", err)
|
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)
|
tmpl, err := getTemplate(tmplAbsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to parse template: %v", err)
|
log.Fatalf("Failed to parse template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
err = tmpl.Execute(os.Stdout, map[string]interface{}{
|
||||||
err = tmpl.Execute(&buf, map[string]interface{}{
|
"backends": backends,
|
||||||
"services": services,
|
|
||||||
"endpoints": endpoints,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to render template: %v", err)
|
log.Fatalf("Failed to execute template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(buf.String())
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue