From 7fc56454ea9a2baa004db509a5a644bc199297c6 Mon Sep 17 00:00:00 2001 From: Marc Mognol <8171300+marcmognol@users.noreply.github.com> Date: Thu, 30 May 2024 17:18:05 +0200 Subject: [PATCH] Add HealthCheck for KubernetesCRD ExternalName services --- .../kubernetes-crd-definition-v1.yml | 201 ++++++++++++++++++ .../traefik.io_ingressroutes.yaml | 41 ++++ .../traefik.io_middlewares.yaml | 40 ++++ .../traefik.io_traefikservices.yaml | 120 +++++++++++ .../routing/providers/kubernetes-crd.md | 48 +++-- integration/fixtures/k8s/01-traefik-crd.yml | 201 ++++++++++++++++++ ..._one_external_service_and_health_check.yml | 22 ++ ...ernal_svc_and_regular_svc_health_check.yml | 27 +++ ...two_external_services_and_health_check.yml | 27 +++ .../kubernetes/crd/kubernetes_http.go | 5 + .../kubernetes/crd/kubernetes_test.go | 168 +++++++++++++++ .../crd/traefikio/v1alpha1/ingressroute.go | 2 + .../v1alpha1/zz_generated.deepcopy.go | 5 + 13 files changed, 885 insertions(+), 22 deletions(-) create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_one_external_service_and_health_check.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_one_external_svc_and_regular_svc_health_check.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_two_external_services_and_health_check.yml diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 93f14adcf..17f1a57e9 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -98,6 +98,47 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the + service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -919,6 +960,46 @@ spec: Service defines the reference to a Kubernetes Service that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2295,6 +2376,46 @@ spec: mirroring: description: Mirroring defines the Mirroring service configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2314,6 +2435,46 @@ spec: items: description: MirrorService holds the mirror configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2548,6 +2709,46 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 71d437491..984503f04 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -98,6 +98,47 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the + service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 0a1891ea8..7d695362c 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -256,6 +256,46 @@ spec: Service defines the reference to a Kubernetes Service that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index ee99b7b19..8a6454858 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -47,6 +47,46 @@ spec: mirroring: description: Mirroring defines the Mirroring service configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -66,6 +106,46 @@ spec: items: description: MirrorService holds the mirror configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -300,6 +380,46 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 2d325d204..950053a14 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -342,6 +342,9 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne flushInterval: 1ms scheme: https serversTransport: transport # [10] + healthCheck: # [11] + path: /health + interval: 15s sticky: cookie: httpOnly: true @@ -351,17 +354,17 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne maxAge: 42 strategy: RoundRobin weight: 10 - nativeLB: true # [11] - nodePortLB: true # [12] - tls: # [13] - secretName: supersecret # [14] - options: # [15] - name: opt # [16] - namespace: default # [17] - certResolver: foo # [18] - domains: # [19] - - main: example.net # [20] - sans: # [21] + nativeLB: true # [12] + nodePortLB: true # [13] + tls: # [14] + secretName: supersecret # [15] + options: # [16] + name: opt # [17] + namespace: default # [18] + certResolver: foo # [19] + domains: # [20] + - main: example.net # [21] + sans: # [22] - a.example.net - b.example.net ``` @@ -378,17 +381,18 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne | [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) | | [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [10] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). | -| [11] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | -| [12] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. | -| [13] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | -| [14] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) | -| [15] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) | -| [16] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name | -| [17] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace | -| [18] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) | -| [19] | `tls.domains` | List of [domains](../routers/index.md#domains) | -| [20] | `domains[n].main` | Defines the main domain name | -| [21] | `domains[n].sans` | List of SANs (alternative domains) | +| [11] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. | +| [12] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | +| [13] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. | +| [14] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | +| [15] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) | +| [16] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) | +| [17] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name | +| [18] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace | +| [19] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) | +| [20] | `tls.domains` | List of [domains](../routers/index.md#domains) | +| [21] | `domains[n].main` | Defines the main domain name | +| [22] | `domains[n].sans` | List of SANs (alternative domains) | ??? example "Declaring an IngressRoute" diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 93f14adcf..17f1a57e9 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -98,6 +98,47 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the + service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -919,6 +960,46 @@ spec: Service defines the reference to a Kubernetes Service that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2295,6 +2376,46 @@ spec: mirroring: description: Mirroring defines the Mirroring service configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2314,6 +2435,46 @@ spec: items: description: MirrorService holds the mirror configuration. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: @@ -2548,6 +2709,46 @@ spec: description: Service defines an upstream HTTP service to proxy traffic to. properties: + healthCheck: + description: Healthcheck defines health checks for the service. + properties: + followRedirects: + type: boolean + headers: + additionalProperties: + type: string + type: object + hostname: + type: string + interval: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + method: + type: string + mode: + type: string + path: + type: string + port: + type: integer + scheme: + type: string + status: + type: integer + timeout: + description: |- + Duration is a custom type suitable for parsing duration values. + It supports `time.ParseDuration`-compatible values and suffix-less digits; in + the latter case, seconds are assumed. + format: int64 + type: integer + required: + - followRedirects + type: object kind: description: Kind defines the kind of the Service. enum: diff --git a/pkg/provider/kubernetes/crd/fixtures/with_one_external_service_and_health_check.yml b/pkg/provider/kubernetes/crd/fixtures/with_one_external_service_and_health_check.yml new file mode 100644 index 000000000..b0a56d1d1 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_one_external_service_and_health_check.yml @@ -0,0 +1,22 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: Host(`foo.com`) && PathPrefix(`/foo`) + kind: Rule + priority: 12 + + services: + - name: external-svc + port: 443 + healthCheck: + path: /health + interval: 15s \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/with_one_external_svc_and_regular_svc_health_check.yml b/pkg/provider/kubernetes/crd/fixtures/with_one_external_svc_and_regular_svc_health_check.yml new file mode 100644 index 000000000..194d04c39 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_one_external_svc_and_regular_svc_health_check.yml @@ -0,0 +1,27 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: Host(`foo.com`) && PathPrefix(`/foo`) + kind: Rule + priority: 12 + + services: + - name: external-svc + port: 443 + healthCheck: + path: /health1 + interval: 15s + - name: whoami2 + port: 443 + healthCheck: + path: /health3 + interval: 30s \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/with_two_external_services_and_health_check.yml b/pkg/provider/kubernetes/crd/fixtures/with_two_external_services_and_health_check.yml new file mode 100644 index 000000000..10bdf4ee0 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_two_external_services_and_health_check.yml @@ -0,0 +1,27 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: Host(`foo.com`) && PathPrefix(`/foo`) + kind: Rule + priority: 12 + + services: + - name: external-svc + port: 443 + healthCheck: + path: /health1 + interval: 15s + - name: external-svc-with-https + port: 443 + healthCheck: + path: /health2 + interval: 20s \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 760efb1a8..45018f948 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -305,6 +305,7 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load lb := &dynamic.ServersLoadBalancer{} lb.SetDefaults() lb.Servers = servers + lb.HealthCheck = svc.HealthCheck conf := svc lb.PassHostHeader = conf.PassHostHeader @@ -380,6 +381,10 @@ func (c configBuilder) loadServers(parentNamespace string, svc traefikv1alpha1.L } var servers []dynamic.Server + if service.Spec.Type != corev1.ServiceTypeExternalName && svc.HealthCheck != nil { + return nil, fmt.Errorf("HealthCheck allowed only for ExternalName services: %s/%s", namespace, sanitizedName) + } + if service.Spec.Type == corev1.ServiceTypeExternalName { if !c.allowExternalNameServices { return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, sanitizedName) diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index ce7cc9cb4..c224b0c3e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -2432,6 +2432,174 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "with one external service and health check", + paths: []string{"services.yml", "with_one_external_service_and_health_check.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-77c62dfe9517144aeeaa": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-77c62dfe9517144aeeaa", + Rule: "Host(`foo.com`) && PathPrefix(`/foo`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-77c62dfe9517144aeeaa": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "https://external.domain:443", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + HealthCheck: &dynamic.ServerHealthCheck{ + Path: "/health", + Interval: 15000000000, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "with two external services and health check", + paths: []string{"services.yml", "with_two_external_services_and_health_check.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-77c62dfe9517144aeeaa": { + EntryPoints: []string{"foo"}, + Service: "default-test-route-77c62dfe9517144aeeaa", + Rule: "Host(`foo.com`) && PathPrefix(`/foo`)", + Priority: 12, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-77c62dfe9517144aeeaa": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-external-svc-443", + Weight: func(i int) *int { return &i }(1), + }, + { + Name: "default-external-svc-with-https-443", + Weight: func(i int) *int { return &i }(1), + }, + }, + }, + }, + "default-external-svc-443": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "https://external.domain:443", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + HealthCheck: &dynamic.ServerHealthCheck{ + Path: "/health1", + Interval: 15000000000, + }, + }, + }, + "default-external-svc-with-https-443": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "https://external.domain:443", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + HealthCheck: &dynamic.ServerHealthCheck{ + Path: "/health2", + Interval: 20000000000, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "with one external service and one regular service and health check", + paths: []string{"services.yml", "with_one_external_svc_and_regular_svc_health_check.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-external-svc-443": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "https://external.domain:443", + }, + }, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + HealthCheck: &dynamic.ServerHealthCheck{ + Path: "/health1", + Interval: 15000000000, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "services lb, servers lb, and mirror service, all in a wrr with different namespaces", allowCrossNamespace: true, diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index 33fc9c687..89189e611 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -131,6 +131,8 @@ type LoadBalancerSpec struct { // It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. // By default, NodePortLB is false. NodePortLB bool `json:"nodePortLB,omitempty"` + // Healthcheck defines health checks for the service. + HealthCheck *dynamic.ServerHealthCheck `json:"healthCheck,omitempty"` } type ResponseForwarding struct { diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index a17ff5484..a4d9c9074 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -582,6 +582,11 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) { *out = new(bool) **out = **in } + if in.HealthCheck != nil { + in, out := &in.HealthCheck, &out.HealthCheck + *out = new(dynamic.ServerHealthCheck) + (*in).DeepCopyInto(*out) + } return }