Add ingress status for ClusterIP and NodePort Service Type
This commit is contained in:
parent
845d0b5ac7
commit
6d3a685d5a
9 changed files with 505 additions and 58 deletions
|
@ -398,11 +398,17 @@ providers:
|
|||
|
||||
_Optional, Default: ""_
|
||||
|
||||
The Kubernetes service to copy status from.
|
||||
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the ingresses.
|
||||
|
||||
Format: `namespace/servicename`.
|
||||
|
||||
The Kubernetes service to copy status from,
|
||||
depending on the service type:
|
||||
|
||||
- **ClusterIP:** The ExternalIPs of the service will be propagated to the ingress status.
|
||||
- **NodePort:** The ExternalIP addresses of the nodes in the cluster will be propagated to the ingress status.
|
||||
- **LoadBalancer:** The IPs from the service's `loadBalancer.status` field (which contains the endpoints provided by the load balancer) will be propagated to the ingress status.
|
||||
|
||||
When using third-party tools such as External-DNS, this option enables the copying of external service IPs to the ingress resources.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesIngress:
|
||||
|
|
|
@ -39,25 +39,25 @@ which in turn creates the resulting routers, services, handlers, etc.
|
|||
## Configuration Options
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
|
||||
| Field | Description | Default | Required |
|
||||
|:------|:----------------------------------------------------------|:---------------------|:---------|
|
||||
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
|
||||
| `providers.kubernetesIngress.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
|
||||
| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
|
||||
| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
|
||||
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | | No |
|
||||
| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
|
||||
| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
|
||||
| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.<br />It alleviates the requirement of giving Traefik the rights to look IngressClasses up.<br />Ignore Ingresses with IngressClass.<br />Annotations are not affected by this option. | false | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.hostname` | Hostname used for Kubernetes Ingress endpoints. | "" | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.ip` | This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.publishedService` | The Kubernetes service to copy status from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the ingresses. | "" | No |
|
||||
| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
|
||||
| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
|
||||
| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No |
|
||||
| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No |
|
||||
| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
|
||||
| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
|
||||
| Field | Description | Default | Required |
|
||||
|:-----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||
| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
|
||||
| `providers.kubernetesIngress.endpoint` | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
|
||||
| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No |
|
||||
| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
|
||||
| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.<br />If left empty, watch all namespaces. | | No |
|
||||
| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
|
||||
| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
|
||||
| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.<br />It alleviates the requirement of giving Traefik the rights to look IngressClasses up.<br />Ignore Ingresses with IngressClass.<br />Annotations are not affected by this option. | false | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.hostname` | Hostname used for Kubernetes Ingress endpoints. | "" | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.ip` | This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
|
||||
| `providers.kubernetesIngress.`<br />`ingressEndpoint.publishedService` | The Kubernetes service to copy status from.<br />More information [here](#ingressendpointpublishedservice). | "" | No |
|
||||
| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
|
||||
| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |
|
||||
| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No |
|
||||
| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No |
|
||||
| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.<br />It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No |
|
||||
| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).<br />By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.<br />Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).<br />This will also prevent from using the `NodePortLB` options on services. | false | No |
|
||||
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
|
@ -99,6 +99,38 @@ providers:
|
|||
--providers.kubernetesingress.endpoint=http://localhost:8080
|
||||
```
|
||||
|
||||
### `ingressEndpoint.publishedService`
|
||||
|
||||
Format: `namespace/servicename`.
|
||||
|
||||
The Kubernetes service to copy status from,
|
||||
depending on the service type:
|
||||
|
||||
- **ClusterIP:** The ExternalIPs of the service will be propagated to the ingress status.
|
||||
- **NodePort:** The ExternalIP addresses of the nodes in the cluster will be propagated to the ingress status.
|
||||
- **LoadBalancer:** The IPs from the service's `loadBalancer.status` field (which contains the endpoints provided by the load balancer) will be propagated to the ingress status.
|
||||
|
||||
When using third-party tools such as External-DNS, this option enables the copying of external service IPs to the ingress resources.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
providers:
|
||||
kubernetesIngress:
|
||||
ingressEndpoint:
|
||||
publishedService: "namespace/foo-service"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesIngress.ingressEndpoint]
|
||||
publishedService = "namespace/foo-service"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--providers.kubernetesingress.ingressendpoint.publishedservice=namespace/foo-service
|
||||
```
|
||||
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
See the dedicated section in [routing](../../../../routing/providers/kubernetes-ingress.md).
|
||||
|
|
|
@ -131,7 +131,7 @@ func Test_parseServiceConfig(t *testing.T) {
|
|||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
SameSite: "none",
|
||||
Path: String("foobar"),
|
||||
Path: pointer("foobar"),
|
||||
},
|
||||
},
|
||||
ServersScheme: "protocol",
|
||||
|
@ -150,7 +150,7 @@ func Test_parseServiceConfig(t *testing.T) {
|
|||
Service: &ServiceIng{
|
||||
Sticky: &dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{
|
||||
Path: String("/"),
|
||||
Path: pointer("/"),
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
|
@ -58,7 +57,6 @@ type clientWrapper struct {
|
|||
disableIngressClassInformer bool // Deprecated.
|
||||
disableClusterScopeInformer bool
|
||||
watchedNamespaces []string
|
||||
serverVersion *version.Version
|
||||
}
|
||||
|
||||
// newInClusterClient returns a new Provider client that is expected to run
|
||||
|
@ -141,19 +139,6 @@ func newClientImpl(clientset kclientset.Interface) *clientWrapper {
|
|||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
// Get and store the serverVersion for future use.
|
||||
serverVersionInfo, err := c.clientset.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not retrieve server version: %w", err)
|
||||
}
|
||||
|
||||
serverVersion, err := version.NewVersion(serverVersionInfo.GitVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse server version: %w", err)
|
||||
}
|
||||
|
||||
c.serverVersion = serverVersion
|
||||
|
||||
eventCh := make(chan interface{}, 1)
|
||||
eventHandler := &k8s.ResourceEventHandler{Ev: eventCh}
|
||||
|
||||
|
@ -175,7 +160,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
|||
for _, ns := range namespaces {
|
||||
factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(matchesLabelSelector))
|
||||
|
||||
_, err = factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
_, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -230,7 +215,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
|||
if !c.disableIngressClassInformer || !c.disableClusterScopeInformer {
|
||||
c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod)
|
||||
|
||||
_, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||
_, err := c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: published-service
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
type: ClusterIP
|
||||
externalIPs:
|
||||
- 1.2.3.4
|
||||
- 5.6.7.8
|
||||
|
||||
ports:
|
||||
- port: 9090
|
||||
protocol: TCP
|
||||
name: first
|
||||
|
||||
- port: 9091
|
||||
protocol: TCP
|
||||
name: second
|
||||
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: "*.foo.com"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
|
||||
clusterIP: 10.0.0.1
|
||||
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: service1-abc
|
||||
labels:
|
||||
kubernetes.io/service-name: service1
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- port: 8080
|
||||
name: ""
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.1
|
||||
conditions:
|
||||
ready: true
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: published-service
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- port: 9090
|
||||
protocol: TCP
|
||||
name: first
|
||||
|
||||
- port: 9091
|
||||
protocol: TCP
|
||||
name: second
|
||||
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- ip: 1.2.3.4
|
||||
ports:
|
||||
- port: 9090
|
||||
protocol: TCP
|
||||
- port: 9091
|
||||
protocol: TCP
|
||||
|
||||
- ip: 5.6.7.8
|
||||
ports:
|
||||
- port: 9090
|
||||
protocol: TCP
|
||||
- port: 9091
|
||||
protocol: TCP
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: "*.foo.com"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
|
||||
clusterIP: 10.0.0.1
|
||||
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: service1-abc
|
||||
labels:
|
||||
kubernetes.io/service-name: service1
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- port: 8080
|
||||
name: ""
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.1
|
||||
conditions:
|
||||
ready: true
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
kind: Node
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: node1
|
||||
|
||||
status:
|
||||
addresses:
|
||||
- type: ExternalIP
|
||||
address: 1.2.3.4
|
||||
|
||||
---
|
||||
kind: Node
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: node2
|
||||
|
||||
status:
|
||||
addresses:
|
||||
- type: ExternalIP
|
||||
address: 5.6.7.8
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: published-service
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 9190
|
||||
protocol: TCP
|
||||
name: first
|
||||
nodePort: 9090
|
||||
|
||||
- port: 9191
|
||||
protocol: TCP
|
||||
name: second
|
||||
nodePort: 9091
|
||||
|
||||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: "*.foo.com"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
|
||||
clusterIP: 10.0.0.1
|
||||
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: service1-abc
|
||||
labels:
|
||||
kubernetes.io/service-name: service1
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- port: 8080
|
||||
name: ""
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.1
|
||||
conditions:
|
||||
ready: true
|
|
@ -247,6 +247,10 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
continue
|
||||
}
|
||||
|
||||
if err := p.updateIngressStatus(ingress, client); err != nil {
|
||||
logger.Error().Err(err).Msg("Error while updating ingress status")
|
||||
}
|
||||
|
||||
rtConfig, err := parseRouterConfig(ingress.Annotations)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to parse annotations")
|
||||
|
@ -305,10 +309,6 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
routers := map[string][]*dynamic.Router{}
|
||||
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
if err := p.updateIngressStatus(ingress, client); err != nil {
|
||||
logger.Error().Err(err).Msg("Error while updating ingress status")
|
||||
}
|
||||
|
||||
if rule.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -407,22 +407,75 @@ func (p *Provider) updateIngressStatus(ing *netv1.Ingress, k8sClient Client) err
|
|||
return fmt.Errorf("cannot get service %s, received error: %w", p.IngressEndpoint.PublishedService, err)
|
||||
}
|
||||
|
||||
if exists && service.Status.LoadBalancer.Ingress == nil {
|
||||
// service exists, but has no Load Balancer status
|
||||
log.Debug().Msgf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("missing service: %s", p.IngressEndpoint.PublishedService)
|
||||
}
|
||||
|
||||
ingresses, err := convertSlice[netv1.IngressLoadBalancerIngress](service.Status.LoadBalancer.Ingress)
|
||||
if err != nil {
|
||||
return err
|
||||
var ingressStatus []netv1.IngressLoadBalancerIngress
|
||||
|
||||
switch service.Spec.Type {
|
||||
case corev1.ServiceTypeLoadBalancer:
|
||||
if service.Status.LoadBalancer.Ingress == nil {
|
||||
// service exists, but has no Load Balancer status
|
||||
log.Debug().Msgf("Skipping updating Ingress %s/%s due to service %s having no status set", ing.Namespace, ing.Name, p.IngressEndpoint.PublishedService)
|
||||
return nil
|
||||
}
|
||||
|
||||
ingressStatus, err = convertSlice[netv1.IngressLoadBalancerIngress](service.Status.LoadBalancer.Ingress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("converting ingress loadbalancer status: %w", err)
|
||||
}
|
||||
|
||||
case corev1.ServiceTypeClusterIP:
|
||||
var ports []netv1.IngressPortStatus
|
||||
for _, port := range service.Spec.Ports {
|
||||
ports = append(ports, netv1.IngressPortStatus{
|
||||
Port: port.Port,
|
||||
Protocol: port.Protocol,
|
||||
})
|
||||
}
|
||||
|
||||
for _, ip := range service.Spec.ExternalIPs {
|
||||
ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{
|
||||
IP: ip,
|
||||
Ports: ports,
|
||||
})
|
||||
}
|
||||
|
||||
case corev1.ServiceTypeNodePort:
|
||||
if p.DisableClusterScopeResources {
|
||||
return errors.New("node port service type is not supported when cluster scope resources lookup is disabled")
|
||||
}
|
||||
|
||||
nodes, _, err := k8sClient.GetNodes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting nodes: %w", err)
|
||||
}
|
||||
|
||||
var ports []netv1.IngressPortStatus
|
||||
for _, port := range service.Spec.Ports {
|
||||
ports = append(ports, netv1.IngressPortStatus{
|
||||
Port: port.NodePort,
|
||||
Protocol: port.Protocol,
|
||||
})
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == corev1.NodeExternalIP {
|
||||
ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{
|
||||
IP: address.Address,
|
||||
Ports: ports,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported service type: %s", service.Spec.Type)
|
||||
}
|
||||
|
||||
return k8sClient.UpdateIngressStatus(ing, ingresses)
|
||||
return k8sClient.UpdateIngressStatus(ing, ingressStatus)
|
||||
}
|
||||
|
||||
func (p *Provider) shouldProcessIngress(ingress *netv1.Ingress, ingressClasses []*netv1.IngressClass) bool {
|
||||
|
|
|
@ -4,28 +4,31 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
func pointer[T any](v T) *T { return &v }
|
||||
|
||||
func String(v string) *string { return &v }
|
||||
|
||||
func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -134,7 +137,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
Name: "foobar",
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
Path: String("/"),
|
||||
Path: pointer("/"),
|
||||
},
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
|
@ -2082,3 +2085,123 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressEndpointPublishedService(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
disableClusterScopeResources bool
|
||||
expected []netv1.IngressLoadBalancerIngress
|
||||
}{
|
||||
{
|
||||
desc: "Published Service ClusterIP",
|
||||
expected: []netv1.IngressLoadBalancerIngress{
|
||||
{
|
||||
IP: "1.2.3.4",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
{
|
||||
IP: "5.6.7.8",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Published Service LoadBalancer",
|
||||
expected: []netv1.IngressLoadBalancerIngress{
|
||||
{
|
||||
IP: "1.2.3.4",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
{
|
||||
IP: "5.6.7.8",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Published Service NodePort",
|
||||
disableClusterScopeResources: true,
|
||||
},
|
||||
{
|
||||
desc: "Published Service NodePort",
|
||||
expected: []netv1.IngressLoadBalancerIngress{
|
||||
{
|
||||
IP: "1.2.3.4",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
{
|
||||
IP: "5.6.7.8",
|
||||
Ports: []netv1.IngressPortStatus{
|
||||
{Port: 9090, Protocol: "TCP"},
|
||||
{Port: 9091, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
k8sObjects := readResources(t, []string{generateTestFilename(test.desc)})
|
||||
kubeClient := kubefake.NewClientset(k8sObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
eventCh, err := client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
DisableClusterScopeResources: test.disableClusterScopeResources,
|
||||
IngressEndpoint: &EndpointIngress{
|
||||
PublishedService: "default/published-service",
|
||||
},
|
||||
}
|
||||
p.loadConfigurationFromIngresses(context.Background(), client)
|
||||
|
||||
ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(context.Background(), "foo", metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, ingress.Status.LoadBalancer.Ingress)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readResources(t *testing.T, paths []string) []runtime.Object {
|
||||
t.Helper()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
for _, path := range paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash(path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
k8sObjects = append(k8sObjects, objects...)
|
||||
}
|
||||
|
||||
return k8sObjects
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue