haproxy-kubernetes/main.go
holzi1005 c88d0e2739
All checks were successful
Build Go Binary / build (push) Successful in 33s
Update main.go
2025-06-29 07:39:38 +02:00

233 lines
5.2 KiB
Go

package main
import (
"crypto/sha1"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
)
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, 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 {
return nil, err
}
return template.New("haproxy").Parse(string(content))
}
func getK8sResources(k8sHost, token string, verifySSL bool, resource string) ([]json.RawMessage, error) {
url := fmt.Sprintf("%s/api/v1/%s", k8sHost, resource)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "gateway-config")
client := &http.Client{}
if !verifySSL {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client.Transport = tr
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
var list K8sRawList
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
return nil, err
}
return list.Items, nil
}
func main() {
k8sHost := getEnv("KUBERNETES_HOST", "")
k8sToken := getEnv("KUBERNETES_TOKEN", "")
verifySSLStr := getEnv("KUBERNETES_VERIFYSSL", "false")
templatePath := getEnv("HAPROXY_TEMPLATE", "haproxy.tmpl")
if k8sHost == "" || k8sToken == "" {
log.Fatal("KUBERNETES_HOST and KUBERNETES_TOKEN must be set")
}
verifySSL, err := strconv.ParseBool(verifySSLStr)
if err != nil {
log.Fatalf("Invalid KUBERNETES_VERIFYSSL value: %v", err)
}
servicesRaw, err := getK8sResources(k8sHost, k8sToken, verifySSL, "services")
if err != nil {
log.Fatalf("Failed to get services: %v", err)
}
endpointsRaw, err := getK8sResources(k8sHost, k8sToken, verifySSL, "endpoints")
if err != nil {
log.Fatalf("Failed to get endpoints: %v", err)
}
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)
}
err = tmpl.Execute(os.Stdout, map[string]interface{}{
"backends": backends,
})
if err != nil {
log.Fatalf("Failed to execute template: %v", err)
}
}