mirror of
https://github.com/containous/traefik.git
synced 2024-12-24 21:34:39 +03:00
Upgrade Ingress Handling to work with networkingv1/Ingress
This commit is contained in:
parent
702e301990
commit
29908098e4
@ -348,3 +348,13 @@ After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubern
|
||||
|
||||
Please note that the unknown fields will not be pruned when migrating from `apiextensions.k8s.io/v1beta1` to `apiextensions.k8s.io/v1` CRDs.
|
||||
For more details check out the official [documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema).
|
||||
|
||||
### Kubernetes Ingress
|
||||
|
||||
Traefik v2.5 moves forward for the Ingress provider to support Kubernetes v1.22.
|
||||
|
||||
Traefik now supports only v1.14+ Kubernetes clusters, which means the support of `extensions/v1beta1` API Version ingresses has been dropped.
|
||||
|
||||
The `extensions/v1beta1` API Version should now be replaced either by `networking.k8s.io/v1beta1` or by `networking.k8s.io/v1` (as of Kubernetes v1.19+).
|
||||
|
||||
The support of the `networking.k8s.io/v1beta1` API Version will stop in Kubernetes v1.22.
|
||||
|
@ -6,6 +6,10 @@ The Kubernetes Ingress Controller.
|
||||
The Traefik Kubernetes Ingress provider is a Kubernetes Ingress controller; that is to say,
|
||||
it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification.
|
||||
|
||||
## Requirements
|
||||
|
||||
Traefik supports `1.14+` Kubernetes clusters.
|
||||
|
||||
## Routing Configuration
|
||||
|
||||
See the dedicated section in [routing](../routing/providers/kubernetes-ingress.md).
|
||||
@ -31,9 +35,9 @@ The provider then watches for incoming ingresses events, such as the example bel
|
||||
and derives the corresponding dynamic configuration from it,
|
||||
which in turn creates the resulting routers, services, handlers, etc.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
```yaml tab="Ingress"
|
||||
kind: Ingress
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: "foo"
|
||||
namespace: production
|
||||
@ -53,6 +57,32 @@ spec:
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: "foo"
|
||||
namespace: production
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: example.net
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
- path: /foo
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
## LetsEncrypt Support with the Ingress Provider
|
||||
|
||||
By design, Traefik is a stateless application,
|
||||
@ -220,7 +250,7 @@ Value of `kubernetes.io/ingress.class` annotation that identifies Ingress object
|
||||
If the parameter is set, only Ingresses containing an annotation with the same value are processed.
|
||||
Otherwise, Ingresses missing the annotation, having an empty value, or the value `traefik` are processed.
|
||||
|
||||
!!! info "Kubernetes 1.18+"
|
||||
??? info "Kubernetes 1.18+"
|
||||
|
||||
If the Kubernetes cluster version is 1.18+,
|
||||
the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed.
|
||||
@ -256,6 +286,39 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
??? info "Kubernetes 1.19+"
|
||||
|
||||
If the Kubernetes cluster version is 1.19+,
|
||||
prefer using the `networking.k8s.io/v1` [apiVersion](https://v1-19.docs.kubernetes.io/docs/setup/release/notes/#api-change) of `Ingress` and `IngressClass`.
|
||||
|
||||
```yaml tab="IngressClass"
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
name: traefik-lb
|
||||
spec:
|
||||
controller: traefik.io/ingress-controller
|
||||
```
|
||||
|
||||
```yaml tab="Ingress"
|
||||
apiVersion: "networking.k8s.io/v1"
|
||||
kind: "Ingress"
|
||||
metadata:
|
||||
name: "example-ingress"
|
||||
spec:
|
||||
ingressClassName: "traefik-lb"
|
||||
rules:
|
||||
- host: "*.example.com"
|
||||
http:
|
||||
paths:
|
||||
- path: "/example"
|
||||
backend:
|
||||
service:
|
||||
name: "example-service"
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[providers.kubernetesIngress]
|
||||
ingressClass = "traefik-internal"
|
||||
|
@ -85,6 +85,33 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: myingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
- path: /foo
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
```yaml tab="Traefik"
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@ -434,6 +461,33 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: myingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
- path: /foo
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
```yaml tab="Traefik"
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@ -613,6 +667,34 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: myingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: true
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
- path: /foo
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
```yaml tab="Traefik"
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@ -732,6 +814,31 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||
tls:
|
||||
- secretName: supersecret
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: production
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: example.net
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
# Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS.
|
||||
# Doesn't enable TLS for that ingress (hence for the underlying router).
|
||||
# Please see the TLS annotations on ingress made for that purpose.
|
||||
tls:
|
||||
- secretName: supersecret
|
||||
```
|
||||
|
||||
```yaml tab="Secret"
|
||||
apiVersion: v1
|
||||
@ -777,16 +884,30 @@ and will connect via TLS automatically.
|
||||
|
||||
Ingresses can be created that look like the following:
|
||||
|
||||
```yaml
|
||||
```yaml tab="Ingress"
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheese
|
||||
|
||||
spec:
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: 80
|
||||
defaultBackend:
|
||||
serviceName: stilton
|
||||
serverPort: 80
|
||||
```
|
||||
|
||||
```yaml tab="Ingress Kubernetes v1.19+"
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheese
|
||||
|
||||
spec:
|
||||
defaultBackend:
|
||||
service:
|
||||
name: stilton
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
This ingress follows the Global Default Backend property of ingresses.
|
||||
|
@ -1,11 +1,9 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
)
|
||||
import networkingv1 "k8s.io/api/networking/v1"
|
||||
|
||||
func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress {
|
||||
i := &v1beta1.Ingress{}
|
||||
func buildIngress(opts ...func(*networkingv1.Ingress)) *networkingv1.Ingress {
|
||||
i := &networkingv1.Ingress{}
|
||||
i.Kind = "Ingress"
|
||||
for _, opt := range opts {
|
||||
opt(i)
|
||||
@ -13,15 +11,15 @@ func buildIngress(opts ...func(*v1beta1.Ingress)) *v1beta1.Ingress {
|
||||
return i
|
||||
}
|
||||
|
||||
func iNamespace(value string) func(*v1beta1.Ingress) {
|
||||
return func(i *v1beta1.Ingress) {
|
||||
func iNamespace(value string) func(*networkingv1.Ingress) {
|
||||
return func(i *networkingv1.Ingress) {
|
||||
i.Namespace = value
|
||||
}
|
||||
}
|
||||
|
||||
func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) {
|
||||
return func(i *v1beta1.Ingress) {
|
||||
s := &v1beta1.IngressSpec{}
|
||||
func iRules(opts ...func(*networkingv1.IngressSpec)) func(*networkingv1.Ingress) {
|
||||
return func(i *networkingv1.Ingress) {
|
||||
s := &networkingv1.IngressSpec{}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
@ -29,9 +27,9 @@ func iRules(opts ...func(*v1beta1.IngressSpec)) func(*v1beta1.Ingress) {
|
||||
}
|
||||
}
|
||||
|
||||
func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) {
|
||||
return func(spec *v1beta1.IngressSpec) {
|
||||
r := &v1beta1.IngressRule{}
|
||||
func iRule(opts ...func(*networkingv1.IngressRule)) func(*networkingv1.IngressSpec) {
|
||||
return func(spec *networkingv1.IngressSpec) {
|
||||
r := &networkingv1.IngressRule{}
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
@ -39,24 +37,24 @@ func iRule(opts ...func(*v1beta1.IngressRule)) func(*v1beta1.IngressSpec) {
|
||||
}
|
||||
}
|
||||
|
||||
func iHost(name string) func(*v1beta1.IngressRule) {
|
||||
return func(rule *v1beta1.IngressRule) {
|
||||
func iHost(name string) func(*networkingv1.IngressRule) {
|
||||
return func(rule *networkingv1.IngressRule) {
|
||||
rule.Host = name
|
||||
}
|
||||
}
|
||||
|
||||
func iTLSes(opts ...func(*v1beta1.IngressTLS)) func(*v1beta1.Ingress) {
|
||||
return func(i *v1beta1.Ingress) {
|
||||
func iTLSes(opts ...func(*networkingv1.IngressTLS)) func(*networkingv1.Ingress) {
|
||||
return func(i *networkingv1.Ingress) {
|
||||
for _, opt := range opts {
|
||||
iTLS := v1beta1.IngressTLS{}
|
||||
iTLS := networkingv1.IngressTLS{}
|
||||
opt(&iTLS)
|
||||
i.Spec.TLS = append(i.Spec.TLS, iTLS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func iTLS(secret string, hosts ...string) func(*v1beta1.IngressTLS) {
|
||||
return func(i *v1beta1.IngressTLS) {
|
||||
func iTLS(secret string, hosts ...string) func(*networkingv1.IngressTLS) {
|
||||
return func(i *networkingv1.IngressTLS) {
|
||||
i.SecretName = secret
|
||||
i.Hosts = hosts
|
||||
}
|
||||
|
@ -13,11 +13,12 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
traefikversion "github.com/traefik/traefik/v2/pkg/version"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
@ -54,12 +55,12 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
||||
// The stores can then be accessed via the Get* functions.
|
||||
type Client interface {
|
||||
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
GetIngresses() []*networkingv1beta1.Ingress
|
||||
GetIngresses() []*networkingv1.Ingress
|
||||
GetIngressClasses() ([]*networkingv1beta1.IngressClass, error)
|
||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||
UpdateIngressStatus(ing *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error
|
||||
UpdateIngressStatus(ing *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error
|
||||
GetServerVersion() (*version.Version, error)
|
||||
}
|
||||
|
||||
@ -167,9 +168,20 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||
opts.LabelSelector = c.ingressLabelSelector
|
||||
}
|
||||
|
||||
serverVersion, err := c.GetServerVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get server version: %w", err)
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
factoryIngress := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns), informers.WithTweakListOptions(matchesLabelSelector))
|
||||
factoryIngress.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
|
||||
if supportsNetworkingV1Ingress(serverVersion) {
|
||||
factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
} else {
|
||||
factoryIngress.Networking().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
}
|
||||
|
||||
c.factoriesIngress[ns] = factoryIngress
|
||||
|
||||
factoryKube := informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, informers.WithNamespace(ns))
|
||||
@ -208,12 +220,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||
}
|
||||
}
|
||||
|
||||
serverVersion, err := c.GetServerVersion()
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Failed to get server version: %v", err)
|
||||
return eventCh, nil
|
||||
}
|
||||
|
||||
if supportsIngressClass(serverVersion) {
|
||||
c.clusterFactory = informers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod)
|
||||
c.clusterFactory.Networking().V1beta1().IngressClasses().Informer().AddEventHandler(eventHandler)
|
||||
@ -230,42 +236,59 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
|
||||
}
|
||||
|
||||
// GetIngresses returns all Ingresses for observed namespaces in the cluster.
|
||||
func (c *clientWrapper) GetIngresses() []*networkingv1beta1.Ingress {
|
||||
var results []*networkingv1beta1.Ingress
|
||||
func (c *clientWrapper) GetIngresses() []*networkingv1.Ingress {
|
||||
var results []*networkingv1.Ingress
|
||||
|
||||
serverVersion, err := c.GetServerVersion()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get server version: %v", err)
|
||||
return results
|
||||
}
|
||||
|
||||
isNetworkingV1Supported := supportsNetworkingV1Ingress(serverVersion)
|
||||
|
||||
for ns, factory := range c.factoriesIngress {
|
||||
// extensions
|
||||
ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(labels.Everything())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
|
||||
}
|
||||
|
||||
for _, ing := range ings {
|
||||
n, err := extensionsToNetworking(ing)
|
||||
if isNetworkingV1Supported {
|
||||
// networking
|
||||
listNew, err := factory.Networking().V1().Ingresses().Lister().List(labels.Everything())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to convert ingress %s from extensions/v1beta1 to networking/v1beta1: %v", ns, err)
|
||||
log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
|
||||
continue
|
||||
}
|
||||
results = append(results, n)
|
||||
|
||||
results = append(results, listNew...)
|
||||
continue
|
||||
}
|
||||
|
||||
// networking
|
||||
// networking beta
|
||||
list, err := factory.Networking().V1beta1().Ingresses().Lister().List(labels.Everything())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
|
||||
log.WithoutContext().Errorf("Failed to list ingresses in namespace %s: %v", ns, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ing := range list {
|
||||
n, err := toNetworkingV1(ing)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Failed to convert ingress %s from networking/v1beta1 to networking/v1: %v", ns, err)
|
||||
continue
|
||||
}
|
||||
|
||||
addServiceFromV1Beta1(n, *ing)
|
||||
|
||||
results = append(results, n)
|
||||
}
|
||||
results = append(results, list...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) {
|
||||
func toNetworkingV1(ing marshaler) (*networkingv1.Ingress, error) {
|
||||
data, err := ing.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ni := &networkingv1beta1.Ingress{}
|
||||
ni := &networkingv1.Ingress{}
|
||||
err = ni.Unmarshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -274,16 +297,95 @@ func extensionsToNetworking(ing marshaler) (*networkingv1beta1.Ingress, error) {
|
||||
return ni, nil
|
||||
}
|
||||
|
||||
func addServiceFromV1Beta1(ing *networkingv1.Ingress, old networkingv1beta1.Ingress) {
|
||||
if old.Spec.Backend != nil {
|
||||
port := networkingv1.ServiceBackendPort{}
|
||||
if old.Spec.Backend.ServicePort.Type == intstr.Int {
|
||||
port.Number = old.Spec.Backend.ServicePort.IntVal
|
||||
} else {
|
||||
port.Name = old.Spec.Backend.ServicePort.StrVal
|
||||
}
|
||||
|
||||
if old.Spec.Backend.ServiceName != "" {
|
||||
ing.Spec.DefaultBackend = &networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
Name: old.Spec.Backend.ServiceName,
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for rc, rule := range ing.Spec.Rules {
|
||||
if rule.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
for pc, path := range rule.HTTP.Paths {
|
||||
if path.Backend.Service == nil {
|
||||
oldBackend := old.Spec.Rules[rc].HTTP.Paths[pc].Backend
|
||||
|
||||
port := networkingv1.ServiceBackendPort{}
|
||||
if oldBackend.ServicePort.Type == intstr.Int {
|
||||
port.Number = oldBackend.ServicePort.IntVal
|
||||
} else {
|
||||
port.Name = oldBackend.ServicePort.StrVal
|
||||
}
|
||||
|
||||
svc := networkingv1.IngressServiceBackend{
|
||||
Name: oldBackend.ServiceName,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
ing.Spec.Rules[rc].HTTP.Paths[pc].Backend.Service = &svc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateIngressStatus updates an Ingress with a provided status.
|
||||
func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
|
||||
func (c *clientWrapper) UpdateIngressStatus(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
|
||||
if !c.isWatchedNamespace(src.Namespace) {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", src.Namespace, src.Name)
|
||||
}
|
||||
|
||||
if src.GetObjectKind().GroupVersionKind().Group != "networking.k8s.io" {
|
||||
serverVersion, err := c.GetServerVersion()
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Failed to get server version: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !supportsNetworkingV1Ingress(serverVersion) {
|
||||
return c.updateIngressStatusOld(src, ingStatus)
|
||||
}
|
||||
|
||||
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
|
||||
}
|
||||
|
||||
logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name)
|
||||
|
||||
if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) {
|
||||
logger.Debug("Skipping ingress status update")
|
||||
return nil
|
||||
}
|
||||
|
||||
ingCopy := ing.DeepCopy()
|
||||
ingCopy.Status = networkingv1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err = c.clientset.NetworkingV1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
|
||||
}
|
||||
|
||||
logger.Info("Updated ingress status")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientWrapper) updateIngressStatusOld(src *networkingv1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
|
||||
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
|
||||
@ -306,35 +408,6 @@ func (c *clientWrapper) UpdateIngressStatus(src *networkingv1beta1.Ingress, ingS
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
|
||||
}
|
||||
|
||||
logger.Info("Updated ingress status")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientWrapper) updateIngressStatusOld(src *networkingv1beta1.Ingress, ingStatus []corev1.LoadBalancerIngress) error {
|
||||
ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Extensions().V1beta1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err)
|
||||
}
|
||||
|
||||
logger := log.WithoutContext().WithField("namespace", ing.Namespace).WithField("ingress", ing.Name)
|
||||
|
||||
if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) {
|
||||
logger.Debug("Skipping ingress status update")
|
||||
return nil
|
||||
}
|
||||
|
||||
ingCopy := ing.DeepCopy()
|
||||
ingCopy.Status = extensionsv1beta1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: ingStatus}}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err = c.clientset.ExtensionsV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err)
|
||||
}
|
||||
|
||||
logger.Info("Updated ingress status")
|
||||
return nil
|
||||
}
|
||||
@ -488,3 +561,11 @@ func filterIngressClassByName(ingressClassName string, ics []*networkingv1beta1.
|
||||
|
||||
return ingressClasses
|
||||
}
|
||||
|
||||
// Ingress in networking.k8s.io/v1 is supported starting 1.19.
|
||||
// thus, we query it in K8s starting 1.19.
|
||||
func supportsNetworkingV1Ingress(serverVersion *version.Version) bool {
|
||||
ingressNetworkingVersion := version.Must(version.NewVersion("1.19"))
|
||||
|
||||
return serverVersion.GreaterThanOrEqual(ingressNetworkingVersion)
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ import (
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
)
|
||||
|
||||
var _ Client = (*clientMock)(nil)
|
||||
|
||||
type clientMock struct {
|
||||
ingresses []*networkingv1beta1.Ingress
|
||||
ingresses []*networkingv1.Ingress
|
||||
services []*corev1.Service
|
||||
secrets []*corev1.Secret
|
||||
endpoints []*corev1.Endpoints
|
||||
@ -51,13 +51,14 @@ func newClientMock(serverVersion string, paths ...string) clientMock {
|
||||
case *corev1.Endpoints:
|
||||
c.endpoints = append(c.endpoints, o)
|
||||
case *networkingv1beta1.Ingress:
|
||||
c.ingresses = append(c.ingresses, o)
|
||||
case *extensionsv1beta1.Ingress:
|
||||
ing, err := extensionsToNetworking(o)
|
||||
ing, err := toNetworkingV1(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
addServiceFromV1Beta1(ing, *o)
|
||||
c.ingresses = append(c.ingresses, ing)
|
||||
case *networkingv1.Ingress:
|
||||
c.ingresses = append(c.ingresses, o)
|
||||
case *networkingv1beta1.IngressClass:
|
||||
c.ingressClasses = append(c.ingressClasses, o)
|
||||
default:
|
||||
@ -69,7 +70,7 @@ func newClientMock(serverVersion string, paths ...string) clientMock {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngresses() []*networkingv1beta1.Ingress {
|
||||
func (c clientMock) GetIngresses() []*networkingv1.Ingress {
|
||||
return c.ingresses
|
||||
}
|
||||
|
||||
@ -125,6 +126,6 @@ func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-cha
|
||||
return c.watchChan, nil
|
||||
}
|
||||
|
||||
func (c clientMock) UpdateIngressStatus(_ *networkingv1beta1.Ingress, _ []corev1.LoadBalancerIngress) error {
|
||||
func (c clientMock) UpdateIngressStatus(_ *networkingv1.Ingress, _ []corev1.LoadBalancerIngress) error {
|
||||
return c.apiIngressStatusError
|
||||
}
|
||||
|
@ -8,9 +8,13 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
@ -149,6 +153,11 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(helmSecret, secret)
|
||||
|
||||
discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery)
|
||||
discovery.FakedServerVersion = &version.Info{
|
||||
GitVersion: "v1.19",
|
||||
}
|
||||
|
||||
client := newClientImpl(kubeClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
@ -180,3 +189,72 @@ func TestClientIgnoresHelmOwnedSecrets(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.False(t, found)
|
||||
}
|
||||
|
||||
func TestClientUsesCorrectServerVersion(t *testing.T) {
|
||||
ingressV1Beta := &v1beta1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "ingress-v1beta",
|
||||
},
|
||||
}
|
||||
|
||||
ingressV1 := &networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "ingress-v1",
|
||||
},
|
||||
}
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(ingressV1Beta, ingressV1)
|
||||
|
||||
discovery, _ := kubeClient.Discovery().(*fakediscovery.FakeDiscovery)
|
||||
discovery.FakedServerVersion = &version.Info{
|
||||
GitVersion: "v1.18.12+foobar",
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
client := newClientImpl(kubeClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
select {
|
||||
case event := <-eventCh:
|
||||
ingress, ok := event.(*v1beta1.Ingress)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "ingress-v1beta", ingress.Name)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
assert.Fail(t, "expected to receive event for ingress")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-eventCh:
|
||||
assert.Fail(t, "received more than one event")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
|
||||
discovery.FakedServerVersion = &version.Info{
|
||||
GitVersion: "v1.19",
|
||||
}
|
||||
|
||||
eventCh, err = client.WatchAll(nil, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
select {
|
||||
case event := <-eventCh:
|
||||
ingress, ok := event.(*networkingv1.Ingress)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "ingress-v1", ingress.Name)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
assert.Fail(t, "expected to receive event for ingress")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-eventCh:
|
||||
assert.Fail(t, "received more than one event")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 80
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: defaultservice
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,12 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: defaultbackend
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
defaultBackend:
|
||||
service:
|
||||
name: defaultservice
|
||||
port:
|
||||
number: 8080
|
@ -0,0 +1,22 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
||||
---
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: defaultservice
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,18 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.pathmatcher: Path
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: ""
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,16 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,18 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.pathmatcher: Path
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,17 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: traefik
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,16 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
spec:
|
||||
ingressClassName: traefik-lb
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,6 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
name: traefik-lb
|
||||
spec:
|
||||
controller: traefik.io/ingress-controller
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,16 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
spec:
|
||||
ingressClassName: traefik-lb
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,12 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- name: foobar
|
||||
port: 4711
|
@ -0,0 +1,16 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
name: foobar
|
@ -0,0 +1,12 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: foobar
|
||||
port: 4711
|
||||
clusterIP: 10.0.0.1
|
||||
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,17 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.pathmatcher: Path
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -0,0 +1,11 @@
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
ports:
|
||||
- port: 8080
|
@ -0,0 +1,16 @@
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
@ -0,0 +1,10 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
@ -23,9 +23,9 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -226,17 +226,17 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
log.FromContext(ctx).Errorf("Error configuring TLS: %v", err)
|
||||
}
|
||||
|
||||
if len(ingress.Spec.Rules) == 0 && ingress.Spec.Backend != nil {
|
||||
if len(ingress.Spec.Rules) == 0 && ingress.Spec.DefaultBackend != nil {
|
||||
if _, ok := conf.HTTP.Services["default-backend"]; ok {
|
||||
log.FromContext(ctx).Error("The default backend already exists.")
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := loadService(client, ingress.Namespace, *ingress.Spec.Backend)
|
||||
service, err := loadService(client, ingress.Namespace, *ingress.Spec.DefaultBackend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", ingress.Spec.Backend.ServiceName).
|
||||
WithField("servicePort", ingress.Spec.Backend.ServicePort.String()).
|
||||
WithField("serviceName", ingress.Spec.DefaultBackend.Service.Name).
|
||||
WithField("servicePort", ingress.Spec.DefaultBackend.Service.Port.String()).
|
||||
Errorf("Cannot create service: %v", err)
|
||||
continue
|
||||
}
|
||||
@ -272,13 +272,19 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
service, err := loadService(client, ingress.Namespace, pa.Backend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", pa.Backend.ServiceName).
|
||||
WithField("servicePort", pa.Backend.ServicePort.String()).
|
||||
WithField("serviceName", pa.Backend.Service.Name).
|
||||
WithField("servicePort", pa.Backend.Service.Port.String()).
|
||||
Errorf("Cannot create service: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.ServiceName + "-" + pa.Backend.ServicePort.String())
|
||||
portString := pa.Backend.Service.Port.Name
|
||||
|
||||
if len(pa.Backend.Service.Port.Name) == 0 {
|
||||
portString = fmt.Sprint(pa.Backend.Service.Port.Number)
|
||||
}
|
||||
|
||||
serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString)
|
||||
conf.HTTP.Services[serviceName] = service
|
||||
|
||||
routerKey := strings.TrimPrefix(provider.Normalize(ingress.Name+"-"+ingress.Namespace+"-"+rule.Host+pa.Path), "-")
|
||||
@ -316,7 +322,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
return conf
|
||||
}
|
||||
|
||||
func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient Client) error {
|
||||
func (p *Provider) updateIngressStatus(ing *networkingv1.Ingress, k8sClient Client) error {
|
||||
// Only process if an EndpointIngress has been configured.
|
||||
if p.IngressEndpoint == nil {
|
||||
return nil
|
||||
@ -355,7 +361,7 @@ func (p *Provider) updateIngressStatus(ing *networkingv1beta1.Ingress, k8sClient
|
||||
return k8sClient.UpdateIngressStatus(ing, service.Status.LoadBalancer.Ingress)
|
||||
}
|
||||
|
||||
func (p *Provider) shouldProcessIngress(ingress *networkingv1beta1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool {
|
||||
func (p *Provider) shouldProcessIngress(ingress *networkingv1.Ingress, ingressClasses []*networkingv1beta1.IngressClass) bool {
|
||||
// configuration through the new kubernetes ingressClass
|
||||
if ingress.Spec.IngressClassName != nil {
|
||||
for _, ic := range ingressClasses {
|
||||
@ -379,7 +385,7 @@ func buildHostRule(host string) string {
|
||||
return "Host(`" + host + "`)"
|
||||
}
|
||||
|
||||
func getCertificates(ctx context.Context, ingress *networkingv1beta1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
|
||||
func getCertificates(ctx context.Context, ingress *networkingv1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
|
||||
for _, t := range ingress.Spec.TLS {
|
||||
if t.SecretName == "" {
|
||||
log.FromContext(ctx).Debugf("Skipping TLS sub-section: No secret name provided")
|
||||
@ -464,8 +470,8 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores
|
||||
return configs
|
||||
}
|
||||
|
||||
func loadService(client Client, namespace string, backend networkingv1beta1.IngressBackend) (*dynamic.Service, error) {
|
||||
service, exists, err := client.GetService(namespace, backend.ServiceName)
|
||||
func loadService(client Client, namespace string, backend networkingv1.IngressBackend) (*dynamic.Service, error) {
|
||||
service, exists, err := client.GetService(namespace, backend.Service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -478,8 +484,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||
var portSpec corev1.ServicePort
|
||||
var match bool
|
||||
for _, p := range service.Spec.Ports {
|
||||
if (backend.ServicePort.Type == intstr.Int && backend.ServicePort.IntVal == p.Port) ||
|
||||
(backend.ServicePort.Type == intstr.String && backend.ServicePort.StrVal == p.Name) {
|
||||
if backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0) {
|
||||
portName = p.Name
|
||||
portSpec = p
|
||||
match = true
|
||||
@ -520,7 +525,7 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.ServiceName)
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.Service.Name)
|
||||
if endpointsErr != nil {
|
||||
return nil, endpointsErr
|
||||
}
|
||||
@ -584,7 +589,7 @@ func makeRouterKeyWithHash(key, rule string) (string, error) {
|
||||
return dupKey, nil
|
||||
}
|
||||
|
||||
func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router {
|
||||
func loadRouter(rule networkingv1.IngressRule, pa networkingv1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router {
|
||||
var rules []string
|
||||
if len(rule.Host) > 0 {
|
||||
rules = []string{buildHostRule(rule.Host)}
|
||||
@ -593,11 +598,11 @@ func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIng
|
||||
if len(pa.Path) > 0 {
|
||||
matcher := defaultPathMatcher
|
||||
|
||||
if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1beta1.PathTypeImplementationSpecific {
|
||||
if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == networkingv1.PathTypeImplementationSpecific {
|
||||
if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" {
|
||||
matcher = rtConfig.Router.PathMatcher
|
||||
}
|
||||
} else if *pa.PathType == networkingv1beta1.PathTypeExact {
|
||||
} else if *pa.PathType == networkingv1.PathTypeExact {
|
||||
matcher = "Path"
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -1302,6 +1302,271 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with prefix pathType",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with no pathType",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "Path(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with empty pathType",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "Path(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with exact pathType",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "Path(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with implementationSpecific pathType",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "Path(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with ingress annotation",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with ingressClass",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-80",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with named port",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-foobar",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-foobar": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:4711",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with missing ingressClass",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "v19 Ingress with defaultbackend",
|
||||
serverVersion: "v1.19",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-router": {
|
||||
Rule: "PathPrefix(`/`)",
|
||||
Priority: math.MinInt32,
|
||||
Service: "default-backend",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-backend": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@ -1375,7 +1640,7 @@ func TestGetCertificates(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingress *v1beta1.Ingress
|
||||
ingress *networkingv1.Ingress
|
||||
client Client
|
||||
result map[string]*tls.CertAndStores
|
||||
errResult string
|
||||
|
Loading…
Reference in New Issue
Block a user