Compare commits

..

19 commits

Author SHA1 Message Date
65de2adc47 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 32s
2025-08-16 07:53:49 +02:00
150373a819 delete log
All checks were successful
Build Go Binary / build (push) Successful in 38s
2025-08-15 14:31:49 +02:00
91fbebd25e Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 32s
2025-08-08 19:03:08 +02:00
f889527cc7 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 34s
2025-08-08 19:02:06 +02:00
1ba52cb8fd Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 39s
2025-08-08 11:57:49 +02:00
c42d85a1d5 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 30s
2025-08-06 17:35:22 +02:00
923261a527 Update main.go
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-08-06 17:34:55 +02:00
f797d909cf Update README_GO.md
Some checks failed
Build Go Binary / build (push) Failing after 27s
2025-08-06 17:30:19 +02:00
d010200de7 Update main.go
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-08-06 17:29:55 +02:00
5e645198a7 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 30s
2025-08-06 17:27:01 +02:00
27ab196460 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 41s
2025-08-06 17:16:38 +02:00
b90d855736 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 36s
2025-08-06 15:22:39 +02:00
9fef25c49d Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 37s
2025-08-06 15:20:52 +02:00
a331343f71 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 33s
2025-08-06 15:19:16 +02:00
8705a80ca8 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 43s
2025-08-06 15:16:50 +02:00
edef83ffd3 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 37s
2025-08-06 15:09:22 +02:00
be43cb93b4 Update README_GO.md
All checks were successful
Build Go Binary / build (push) Successful in 32s
2025-08-06 12:48:07 +02:00
f0b618c712 Update main.go
Some checks failed
Build Go Binary / build (push) Has been cancelled
2025-08-06 12:47:50 +02:00
b15afb8c06 Update main.go
All checks were successful
Build Go Binary / build (push) Successful in 43s
2025-08-06 12:42:03 +02:00
2 changed files with 101 additions and 45 deletions

View file

@ -15,16 +15,23 @@ export HAPROXY_TEMPLATE="./haproxy.tmpl"
{{- range .backends }} {{- range .backends }}
backend {{ .Name }} backend {{ .Name }}
mode tcp mode {{ .Mode }}
balance leastconn balance {{ .Balance }}
cookie {{ .Name }} insert indirect nocache {{- if .CookieName }}
cookie {{ .CookieName }} {{ .CookieFlags }}
{{- end }}
{{- if .HealthCheck }}
option httpchk GET {{ .HealthCheck }}
http-check expect status 200
{{- end }}
{{- $backend := . }}
{{- range .Servers }} {{- range .Servers }}
server {{ .Name }} {{ .Address }}:{{ .Port }} check cookie {{ .Cookie }} server {{ .Name }} {{ .Address }}:{{ .Port }}{{ if $backend.HealthCheck }} check{{ end }}{{ if $backend.CookieName }} cookie {{ .Cookie }}{{ end }}{{ if $backend.ServerOptions }} {{ $backend.ServerOptions }}{{ end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
``` ```
@ -41,8 +48,7 @@ Environment=KUBERNETES_HOST=https://10.0.20.7:6443
Environment=KUBERNETES_TOKEN=eyJhbGciOi... Environment=KUBERNETES_TOKEN=eyJhbGciOi...
Environment=KUBERNETES_VERIFYSSL=false Environment=KUBERNETES_VERIFYSSL=false
Environment=HAPROXY_TEMPLATE=/etc/haproxy/haproxy.tmpl Environment=HAPROXY_TEMPLATE=/etc/haproxy/haproxy.tmpl
ExecStart=/path/to/haproxy-generator > /etc/haproxy/haproxy.cfg ExecStart=/bin/bash /etc/haproxy/haproxy-generator.sh
ExecStartPost=/bin/systemctl restart haproxy
``` ```
@ -61,3 +67,33 @@ Persistent=true
WantedBy=timers.target WantedBy=timers.target
``` ```
# Reload Script for post start
Copy the script to /etc/haproxy/haproxy-generator.sh
```
#!/bin/bash
/usr/local/bin/haproxy-generator > /etc/haproxy/haproxy.cfg.new
DIFF=$(diff /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.new)
/sbin/haproxy -f /etc/haproxy/haproxy.cfg.new -c
VALID=$?
if [ "$DIFF" != "" ] && [ $VALID -eq 0 ]
then
mv /etc/haproxy/haproxy.cfg.new /etc/haproxy/haproxy.cfg
/usr/sbin/service haproxy restart
fi
```
# Service Annotations
| Annotation-Key | Beschreibung | Typ | Beispielwert |
|--------------------------------|-----------------------------------------------------------------------------|---------|---------------------------|
| `haproxy/enabled` | **Aktiviert** die Aufnahme des Services in die HAProxy-Konfiguration | `bool` | `"true"` |
| `haproxy/mode` | Betriebsmodus des Backends (`tcp` oder `http`) | `string`| `"tcp"` / `"http"` |
| `haproxy/cookie-name` | Name des Cookies zur Session-Persistenz | `string`| `"SRVCOOKIE"` |
| `haproxy/cookie-flags` | Zusätzliche Cookie-Optionen für HAProxy | `string`| `"insert indirect nocache"`|
| `haproxy/health-check` | Deaktiviert Health-Checks, wenn `"false"` gesetzt wird | `bool` | `"false"` |
| `haproxy/server-options` | Zusätzliche Optionen für die einzelnen `server`-Zeilen in HAProxy | `string`| `"ssl verify none"` |
| `haproxy/port` | Nutze nur **diesen Port** des Services, wenn mehrere Ports definiert sind | `int` |`"8080"` |

94
main.go
View file

@ -28,9 +28,14 @@ type BackendServer struct {
} }
type Backend struct { type Backend struct {
Name string Name string
Balance string Balance string
Servers []BackendServer Mode string
CookieName string
CookieFlags string
HealthCheck string
ServerOptions string
Servers []BackendServer
} }
func getEnv(key, fallback string) string { func getEnv(key, fallback string) string {
@ -96,7 +101,6 @@ func main() {
k8sToken := getEnv("KUBERNETES_TOKEN", "") k8sToken := getEnv("KUBERNETES_TOKEN", "")
verifySSLStr := getEnv("KUBERNETES_VERIFYSSL", "false") verifySSLStr := getEnv("KUBERNETES_VERIFYSSL", "false")
templatePath := getEnv("HAPROXY_TEMPLATE", "haproxy.tmpl") templatePath := getEnv("HAPROXY_TEMPLATE", "haproxy.tmpl")
annotationCheckEnabled := os.Getenv("KUBERNETES_ANNOTATION_HAPROXY") != ""
if k8sHost == "" || k8sToken == "" { if k8sHost == "" || k8sToken == "" {
log.Fatal("KUBERNETES_HOST and KUBERNETES_TOKEN must be set") log.Fatal("KUBERNETES_HOST and KUBERNETES_TOKEN must be set")
@ -118,8 +122,8 @@ func main() {
type Service struct { type Service struct {
Metadata struct { Metadata struct {
Name string `json:"name"` Name string `json:"name"`
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
Annotations map[string]string `json:"annotations"` Annotations map[string]string `json:"annotations"`
} `json:"metadata"` } `json:"metadata"`
Spec struct { Spec struct {
@ -133,16 +137,13 @@ func main() {
type EndpointSubsetAddress struct { type EndpointSubsetAddress struct {
IP string `json:"ip"` IP string `json:"ip"`
} }
type EndpointSubsetPort struct { type EndpointSubsetPort struct {
Port int `json:"port"` Port int `json:"port"`
} }
type EndpointSubset struct { type EndpointSubset struct {
Addresses []EndpointSubsetAddress `json:"addresses"` Addresses []EndpointSubsetAddress `json:"addresses"`
Ports []EndpointSubsetPort `json:"ports"` Ports []EndpointSubsetPort `json:"ports"`
} }
type Endpoint struct { type Endpoint struct {
Metadata struct { Metadata struct {
Name string `json:"name"` Name string `json:"name"`
@ -180,48 +181,67 @@ func main() {
backends := []Backend{} backends := []Backend{}
for _, svc := range services { for _, svc := range services {
if svc.Spec.Type != "ClusterIP" { ann := svc.Metadata.Annotations
if val, ok := ann["haproxy/enabled"]; !ok || val != "true" {
continue continue
} }
if annotationCheckEnabled {
val, ok := svc.Metadata.Annotations["haproxy/enabled"]
if !ok || val != "true" {
continue
}
}
key := svc.Metadata.Namespace + "/" + svc.Metadata.Name key := svc.Metadata.Namespace + "/" + svc.Metadata.Name
ep, found := endpointMap[key] ep, found := endpointMap[key]
if !found || len(ep.Subsets) == 0 {
continue
}
b := Backend{ b := Backend{
Name: "SRV_" + strings.ReplaceAll(svc.Metadata.Name, " ", "-"), Name: "SRV_" + strings.ReplaceAll(svc.Metadata.Name, " ", "-"),
Balance: "leastconn", Balance: "leastconn",
Mode: "tcp",
HealthCheck: "",
}
if val, ok := ann["haproxy/mode"]; ok && val != "" {
b.Mode = val
}
if val, ok := ann["haproxy/cookie-name"]; ok && val != "" {
b.CookieName = val
}
if val, ok := ann["haproxy/cookie-flags"]; ok && val != "" {
b.CookieFlags = val
}
if val, ok := ann["haproxy/health-check"]; ok && val != "" {
b.HealthCheck = val
}
if val, ok := ann["haproxy/server-options"]; ok && val != "" {
b.ServerOptions = val
}
targetPort := 0
if val, ok := ann["haproxy/port"]; ok && val != "" {
if p, err := strconv.Atoi(val); err == nil {
targetPort = p
}
} }
servers := []BackendServer{} 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{ if found && len(ep.Subsets) > 0 {
Name: serverName, for _, subset := range ep.Subsets {
Address: addr.IP, for _, addr := range subset.Addresses {
Port: port.Port, for _, port := range subset.Ports {
Cookie: cookie, if targetPort != 0 && port.Port != targetPort {
}) continue
}
cookie := hashString(fmt.Sprintf("%s-%s-%d", svc.Metadata.Name, addr.IP, port.Port))
serverName := fmt.Sprintf("%s_%d", svc.Metadata.Name, cookie)
servers = append(servers, BackendServer{
Name: serverName,
Address: addr.IP,
Port: port.Port,
Cookie: cookie,
})
}
} }
} }
} }
b.Servers = servers
b.Servers = servers
backends = append(backends, b) backends = append(backends, b)
} }
@ -229,11 +249,11 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("Failed to get absolute path: %v", err) 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)
} }
err = tmpl.Execute(os.Stdout, map[string]interface{}{ err = tmpl.Execute(os.Stdout, map[string]interface{}{
"backends": backends, "backends": backends,
}) })