1
0
Fork 0

Upgrade Ingress Handling to work with networkingv1/Ingress

This commit is contained in:
Manuel Zapf 2021-03-15 11:16:04 +01:00 committed by GitHub
parent 702e301990
commit 29908098e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1141 additions and 113 deletions

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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):
}
}

View file

@ -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

View file

@ -0,0 +1,12 @@
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
name: defaultbackend
namespace: testing
spec:
defaultBackend:
service:
name: defaultservice
port:
number: 8080

View file

@ -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

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,6 @@
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: traefik-lb
spec:
controller: traefik.io/ingress-controller

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,12 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- name: foobar
port: 4711
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -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

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIP: 10.0.0.1

View file

@ -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"
}

View file

@ -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