tcp stream (und damit: tls passthrough) ist implementiert

This commit is contained in:
br committed 2026-10-11 01:04:45 +02:00
1 parent 6a5cb95f92
commit fdcd06c93f
16 files changed
+1008 -75

No files matched your search

+46 -19
View File
@@ -13,13 +13,19 @@ import (
"sultan/pkg/registry"
)
const servicesPrefix = "traefik.http.services."
// protocols sind die Label-Ebenen mit Routern und Services (HTTP und TCP
// für TLS-Passthrough). Router- und Service-Namen sind je Ebene getrennt.
var protocols = []string{"http", "tcp"}
func routersPrefix(proto string) string { return "traefik." + proto + ".routers." }
func servicesPrefix(proto string) string { return "traefik." + proto + ".services." }
var unsafeName = regexp.MustCompile(`[^A-Za-z0-9_-]`)
// Workloads übersetzt Container in Workloads für service_state.
//
// Berücksichtigt werden nur Container mit traefik.enable=true. Weil der
// Berücksichtigt werden nur Container mit traefik.enable=true. Die Regeln
// gelten für traefik.http.* und traefik.tcp.* (TLS-Passthrough) getrennt. Weil der
// Ingress auf einem anderen Host sitzt, wird loadbalancer.server.port (Port
// im Container) auf den veröffentlichten Host-Port umgerechnet. Fehlt das
// Port-Label, gilt wie bei Traefiks Docker-Provider der einzige
@@ -36,8 +42,8 @@ func Workloads(containers []docker.Container, host string) (workloads []registry
type candidate struct {
workload registry.Workload
running bool
routers []string
services []string
routers []string // "<proto>:<name>"
services []string // "<proto>:<name>"
}
var candidates []candidate
taken := map[string]bool{} // Namen laufender Container, "r:"/"s:" + klein
@@ -53,8 +59,14 @@ func Workloads(containers []docker.Container, host string) (workloads []registry
cand := candidate{
workload: registry.Workload{Name: c.Name(), Host: host, Labels: labels},
running: c.State == "running",
routers: labelNames(labels, "traefik.http.routers."),
services: labelNames(labels, servicesPrefix),
}
for _, proto := range protocols {
for _, r := range labelNames(labels, routersPrefix(proto)) {
cand.routers = append(cand.routers, proto+":"+r)
}
for _, s := range labelNames(labels, servicesPrefix(proto)) {
cand.services = append(cand.services, proto+":"+s)
}
}
if cand.running {
for _, r := range cand.routers {
@@ -84,7 +96,10 @@ func Workloads(containers []docker.Container, host string) (workloads []registry
if collides(c) {
continue
}
down = append(down, c.services...)
for _, s := range c.services {
_, name, _ := strings.Cut(s, ":")
down = append(down, name)
}
}
workloads = append(workloads, c.workload)
}
@@ -142,6 +157,18 @@ type serviceLabels struct {
func translatePorts(c docker.Container) (map[string]string, string) {
labels := maps.Clone(c.Labels)
reachable := reachablePorts(c.Ports)
for _, proto := range protocols {
if warning := translateProtoPorts(c, labels, reachable, proto); warning != "" {
return nil, warning
}
}
return labels, ""
}
// translateProtoPorts rechnet die Service-Ports einer Ebene (http/tcp) in
// labels auf veröffentlichte Host-Ports um.
func translateProtoPorts(c docker.Container, labels map[string]string, reachable map[uint16]uint16, proto string) string {
svcPrefix := servicesPrefix(proto)
single := func() (uint16, bool) {
for _, pub := range reachable {
return pub, len(reachable) == 1
@@ -153,10 +180,10 @@ func translatePorts(c docker.Container) (map[string]string, string) {
hasRouters := false
for k := range labels {
lk := strings.ToLower(k)
if strings.HasPrefix(lk, "traefik.http.routers.") {
if strings.HasPrefix(lk, routersPrefix(proto)) {
hasRouters = true
}
rest, ok := strings.CutPrefix(lk, servicesPrefix)
rest, ok := strings.CutPrefix(lk, svcPrefix)
if !ok {
continue
}
@@ -166,7 +193,7 @@ func translatePorts(c docker.Container) (map[string]string, string) {
}
s := services[svc]
if s == nil {
name, _, _ := strings.Cut(k[len(servicesPrefix):], ".")
name, _, _ := strings.Cut(k[len(svcPrefix):], ".")
s = &serviceLabels{name: name}
services[svc] = s
}
@@ -177,35 +204,35 @@ func translatePorts(c docker.Container) (map[string]string, string) {
if len(services) == 0 {
if !hasRouters {
return labels, ""
return ""
}
pub, ok := single()
if !ok {
return nil, fmt.Sprintf("Container %s: %d erreichbare Ports – Label loadbalancer.server.port angeben", c.Name(), len(reachable))
return fmt.Sprintf("Container %s: %d erreichbare Ports – Label loadbalancer.server.port angeben", c.Name(), len(reachable))
}
svc := unsafeName.ReplaceAllString(c.Name(), "-")
labels[servicesPrefix+svc+".loadbalancer.server.port"] = strconv.Itoa(int(pub))
return labels, ""
labels[svcPrefix+svc+".loadbalancer.server.port"] = strconv.Itoa(int(pub))
return ""
}
for _, s := range services {
if s.portKey == "" {
pub, ok := single()
if !ok {
return nil, fmt.Sprintf("Container %s, Service %s: %d erreichbare Ports – Label loadbalancer.server.port angeben", c.Name(), s.name, len(reachable))
return fmt.Sprintf("Container %s, Service %s: %d erreichbare Ports – Label loadbalancer.server.port angeben", c.Name(), s.name, len(reachable))
}
labels[servicesPrefix+s.name+".loadbalancer.server.port"] = strconv.Itoa(int(pub))
labels[svcPrefix+s.name+".loadbalancer.server.port"] = strconv.Itoa(int(pub))
continue
}
private, err := strconv.Atoi(strings.TrimSpace(labels[s.portKey]))
if err != nil {
return nil, fmt.Sprintf("Container %s: Port %q nicht lesbar", c.Name(), labels[s.portKey])
return fmt.Sprintf("Container %s: Port %q nicht lesbar", c.Name(), labels[s.portKey])
}
pub, ok := reachable[uint16(private)]
if !ok {
return nil, fmt.Sprintf("Container %s: Port %d ist von außen nicht erreichbar (nicht veröffentlicht oder nur 127.0.0.1)", c.Name(), private)
return fmt.Sprintf("Container %s: Port %d ist von außen nicht erreichbar (nicht veröffentlicht oder nur 127.0.0.1)", c.Name(), private)
}
labels[s.portKey] = strconv.Itoa(int(pub))
}
return labels, ""
return ""
}