mirror of
https://github.com/traefik/traefik.git
synced 2026-05-05 12:26:25 +02:00
Do not require a port for ExternalName services
This commit is contained in:
parent
2e80ad282a
commit
28e655452c
@ -0,0 +1,41 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-with-external-name
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: external.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /external-with-matching-port
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: external
|
||||
port:
|
||||
number: 80
|
||||
- path: /external-with-matching-named-port
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: external
|
||||
port:
|
||||
name: http
|
||||
- path: /external-with-non-matching-port
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: external
|
||||
port:
|
||||
number: 3000
|
||||
- path: /external-with-non-matching-named-port
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: external
|
||||
port:
|
||||
name: foo
|
||||
@ -78,3 +78,18 @@ endpoints:
|
||||
- 10.10.0.6
|
||||
conditions:
|
||||
ready: true
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: external
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: external.com
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
@ -375,12 +376,7 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
continue
|
||||
}
|
||||
|
||||
port := backend.Service.Port.Name
|
||||
if len(backend.Service.Port.Name) == 0 {
|
||||
port = strconv.Itoa(int(backend.Service.Port.Number))
|
||||
}
|
||||
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + backend.Service.Name + "-" + port)
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + backend.Service.Name + "-" + portString(backend.Service.Port))
|
||||
conf.TCP.Services[serviceName] = service
|
||||
|
||||
routerKey := strings.TrimPrefix(provider.Normalize(ingress.Namespace+"-"+ingress.Name+"-"+rule.Host), "-")
|
||||
@ -449,13 +445,8 @@ func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration
|
||||
continue
|
||||
}
|
||||
|
||||
portString := pa.Backend.Service.Port.Name
|
||||
if len(pa.Backend.Service.Port.Name) == 0 {
|
||||
portString = strconv.Itoa(int(pa.Backend.Service.Port.Number))
|
||||
}
|
||||
|
||||
// TODO: if no service, do not add middlewares and 503.
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + pa.Backend.Service.Name + "-" + portString)
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-" + pa.Backend.Service.Name + "-" + portString(pa.Backend.Service.Port))
|
||||
|
||||
service, err := p.buildService(ingress.Namespace, pa.Backend, ingressConfig)
|
||||
if err != nil {
|
||||
@ -589,6 +580,24 @@ func (p *Provider) buildPassthroughService(namespace string, backend netv1.Ingre
|
||||
return &dynamic.TCPService{LoadBalancer: lb}, nil
|
||||
}
|
||||
|
||||
func getServicePort(service *corev1.Service, backend netv1.IngressBackend) (corev1.ServicePort, bool) {
|
||||
for _, p := range service.Spec.Ports {
|
||||
// A port with number 0 or an empty name is not allowed, this case is there for the default backend service.
|
||||
if (backend.Service.Port.Number == 0 && backend.Service.Port.Name == "") ||
|
||||
(backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0)) {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
|
||||
// If the port is not found and the service is of type ExternalName, we return the port defined in the backend.
|
||||
// If this is a named port, the port value will be 0 to be consistent with ingress-nginx.
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
return corev1.ServicePort{TargetPort: intstr.Parse(portString(backend.Service.Port))}, true
|
||||
}
|
||||
|
||||
return corev1.ServicePort{}, false
|
||||
}
|
||||
|
||||
func (p *Provider) getBackendAddresses(namespace string, backend netv1.IngressBackend, cfg ingressConfig) ([]backendAddress, error) {
|
||||
service, err := p.k8sClient.GetService(namespace, backend.Service.Name)
|
||||
if err != nil {
|
||||
@ -599,30 +608,18 @@ func (p *Provider) getBackendAddresses(namespace string, backend netv1.IngressBa
|
||||
return nil, errors.New("externalName services not allowed")
|
||||
}
|
||||
|
||||
var portName string
|
||||
var portSpec corev1.ServicePort
|
||||
var match bool
|
||||
for _, p := range service.Spec.Ports {
|
||||
// A port with number 0 or an empty name is not allowed, this case is there for the default backend service.
|
||||
if (backend.Service.Port.Number == 0 && backend.Service.Port.Name == "") ||
|
||||
(backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0)) {
|
||||
portName = p.Name
|
||||
portSpec = p
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
servicePort, match := getServicePort(service, backend)
|
||||
if !match {
|
||||
return nil, errors.New("service port not found")
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
return []backendAddress{{Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))}}, nil
|
||||
return []backendAddress{{Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(servicePort.TargetPort.IntValue()))}}, nil
|
||||
}
|
||||
|
||||
// When service upstream is set to true we return the service ClusterIP as the backend address.
|
||||
if ptr.Deref(cfg.ServiceUpstream, false) {
|
||||
return []backendAddress{{Address: net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(portSpec.Port)))}}, nil
|
||||
return []backendAddress{{Address: net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(servicePort.Port)))}}, nil
|
||||
}
|
||||
|
||||
endpointSlices, err := p.k8sClient.GetEndpointSlicesForService(namespace, backend.Service.Name)
|
||||
@ -635,7 +632,7 @@ func (p *Provider) getBackendAddresses(namespace string, backend netv1.IngressBa
|
||||
for _, endpointSlice := range endpointSlices {
|
||||
var port int32
|
||||
for _, p := range endpointSlice.Ports {
|
||||
if portName == *p.Name {
|
||||
if servicePort.Name == *p.Name {
|
||||
port = *p.Port
|
||||
break
|
||||
}
|
||||
@ -1140,3 +1137,10 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
||||
|
||||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func portString(port netv1.ServiceBackendPort) string {
|
||||
if port.Name == "" {
|
||||
return strconv.Itoa(int(port.Number))
|
||||
}
|
||||
return port.Name
|
||||
}
|
||||
|
||||
@ -703,6 +703,105 @@ func TestLoadIngresses(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "External name service",
|
||||
paths: []string{
|
||||
"services.yml",
|
||||
"ingressclasses.yml",
|
||||
"ingresses/ingress-with-external-name.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-ingress-with-external-name-rule-0-path-0": {
|
||||
Rule: `Host("external.localhost") && Path("/external-with-matching-port")`,
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-external-name-external-80",
|
||||
},
|
||||
"default-ingress-with-external-name-rule-0-path-1": {
|
||||
Rule: `Host("external.localhost") && Path("/external-with-matching-named-port")`,
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-external-name-external-http",
|
||||
},
|
||||
"default-ingress-with-external-name-rule-0-path-2": {
|
||||
Rule: `Host("external.localhost") && Path("/external-with-non-matching-port")`,
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-external-name-external-3000",
|
||||
},
|
||||
"default-ingress-with-external-name-rule-0-path-3": {
|
||||
Rule: `Host("external.localhost") && Path("/external-with-non-matching-named-port")`,
|
||||
RuleSyntax: "default",
|
||||
Service: "default-ingress-with-external-name-external-foo",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-ingress-with-external-name-external-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.com:8080",
|
||||
},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-external-name-external-3000": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.com:3000",
|
||||
},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-external-name-external-http": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.com:8080",
|
||||
},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-ingress-with-external-name-external-foo": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.com:0",
|
||||
},
|
||||
},
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: dynamic.DefaultFlushInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user